const SLACK_AUTH_ERROR_RE = /account_inactive|invalid_auth|token_revoked|token_expired|not_authed|org_login_required|team_access_not_granted|missing_scope|cannot_find_service|invalid_token/i; export const SLACK_SOCKET_RECONNECT_POLICY = { initialMs: 2_000, maxMs: 30_000, factor: 1.8, jitter: 0.25, maxAttempts: 12, } as const; export type SlackSocketDisconnectEvent = "disconnect" | "unable_to_socket_mode_start" | "error"; type EmitterLike = { on: (event: string, listener: (...args: unknown[]) => void) => unknown; off: (event: string, listener: (...args: unknown[]) => void) => unknown; }; export function getSocketEmitter(app: unknown): EmitterLike | null { const receiver = (app as { receiver?: unknown }).receiver; const client = receiver && typeof receiver === "object" ? (receiver as { client?: unknown }).client : undefined; if (!client || typeof client !== "object") { return null; } const on = (client as { on?: unknown }).on; const off = (client as { off?: unknown }).off; if (typeof on !== "function" || typeof off !== "function") { return null; } return { on: (event, listener) => ( on as (this: unknown, event: string, listener: (...args: unknown[]) => void) => unknown ).call(client, event, listener), off: (event, listener) => ( off as (this: unknown, event: string, listener: (...args: unknown[]) => void) => unknown ).call(client, event, listener), }; } export function waitForSlackSocketDisconnect( app: unknown, abortSignal?: AbortSignal, ): Promise<{ event: SlackSocketDisconnectEvent; error?: unknown; }> { return new Promise((resolve) => { const emitter = getSocketEmitter(app); if (!emitter) { abortSignal?.addEventListener("abort", () => resolve({ event: "disconnect" }), { once: true, }); return; } const disconnectListener = () => resolveOnce({ event: "disconnect" }); const startFailListener = (error?: unknown) => resolveOnce({ event: "unable_to_socket_mode_start", error }); const errorListener = (error: unknown) => resolveOnce({ event: "error", error }); const abortListener = () => resolveOnce({ event: "disconnect" }); const cleanup = () => { emitter.off("disconnected", disconnectListener); emitter.off("unable_to_socket_mode_start", startFailListener); emitter.off("error", errorListener); abortSignal?.removeEventListener("abort", abortListener); }; const resolveOnce = (value: { event: SlackSocketDisconnectEvent; error?: unknown }) => { cleanup(); resolve(value); }; emitter.on("disconnected", disconnectListener); emitter.on("unable_to_socket_mode_start", startFailListener); emitter.on("error", errorListener); abortSignal?.addEventListener("abort", abortListener, { once: true }); }); } /** * Detect non-recoverable Slack API / auth errors that should NOT be retried. * These indicate permanent credential problems (revoked bot, deactivated account, etc.) * and retrying will never succeed — continuing to retry blocks the entire gateway. */ export function isNonRecoverableSlackAuthError(error: unknown): boolean { const msg = error instanceof Error ? error.message : typeof error === "string" ? error : ""; return SLACK_AUTH_ERROR_RE.test(msg); } export function formatUnknownError(error: unknown): string { if (error instanceof Error) { return error.message; } if (typeof error === "string") { return error; } try { return JSON.stringify(error); } catch { return "unknown error"; } }