fix(commands): /kick and /ban failures are no longer silent (#216)
rateLimitedActions now collects non-429 failures (and a 429 that exhausted its retries) and returns them instead of swallowing them; existing callers ignore the return. /kick and /ban turn the list into a CommandError whose message names who and why, using the server's own sentence (MatrixError.data.error), never the URL-bearing toString(); RoomInput's toast shows it verbatim. Verified headless as a non-moderator: '/kick @alice' → "Could not kick @alice:localhost: You cannot kick user @alice:localhost."; '/ban @nobody @alice' → "Could not ban @nobody:localhost, @alice:localhost: You don't have permission to ban". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
+14
-1
@@ -467,15 +467,24 @@ export const declineInvite = async (mx: MatrixClient, roomId: string): Promise<v
|
||||
await mx.forget(roomId).catch(() => undefined);
|
||||
};
|
||||
|
||||
export type RateLimitedFailure<T> = { item: T; error: MatrixError };
|
||||
|
||||
/**
|
||||
* Run `callback` over `data` sequentially, backing off on 429. Other errors do
|
||||
* not stop the loop — they are collected and returned so callers can tell the
|
||||
* user which items failed (a failed /kick used to vanish silently, #216).
|
||||
*/
|
||||
export const rateLimitedActions = async <T, R = void>(
|
||||
data: T[],
|
||||
callback: (item: T, index: number) => Promise<R>,
|
||||
maxRetryCount?: number,
|
||||
) => {
|
||||
): Promise<{ failures: RateLimitedFailure<T>[] }> => {
|
||||
let retryCount = 0;
|
||||
|
||||
let actionInterval = 0;
|
||||
|
||||
const failures: RateLimitedFailure<T>[] = [];
|
||||
|
||||
const sleepForMs = (ms: number) =>
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
@@ -486,6 +495,7 @@ export const rateLimitedActions = async <T, R = void>(
|
||||
|
||||
if (err?.httpStatus === 429) {
|
||||
if (retryCount === maxRetryCount) {
|
||||
failures.push({ item: dataItem, error: err });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -496,6 +506,8 @@ export const rateLimitedActions = async <T, R = void>(
|
||||
retryCount += 1;
|
||||
|
||||
await performAction(dataItem, index);
|
||||
} else if (err) {
|
||||
failures.push({ item: dataItem, error: err });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -509,6 +521,7 @@ export const rateLimitedActions = async <T, R = void>(
|
||||
await sleepForMs(actionInterval);
|
||||
}
|
||||
}
|
||||
return { failures };
|
||||
};
|
||||
|
||||
export const knockSupported = (version: string): boolean => {
|
||||
|
||||
Reference in New Issue
Block a user