fix(synology-chat,twitch,zalouser): clear timeout timers in Promise.race patterns
Several extensions use Promise.race with setTimeout-based timeouts but never clear the timer when the main promise wins the race: - synology-chat webhook-handler: 120s agent response timeout rejects after nobody is listening, causing an unhandled promise rejection - twitch probe: connection timeout rejects after successful connect - zalouser probe: timeout timer keeps running after user info resolves Store timer handles and clear them in finally blocks so timers are always cleaned up regardless of which side wins the race. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aedf3ee68f
commit
cf4d2659d8
@ -369,17 +369,22 @@ export function createWebhookHandler(deps: WebhookHandlerDeps) {
|
||||
chatUserId: replyUserId,
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise<null>((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Agent response timeout (120s)")), 120_000),
|
||||
);
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const timeoutPromise = new Promise<null>((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error("Agent response timeout (120s)")), 120_000);
|
||||
});
|
||||
|
||||
const reply = await Promise.race([deliverPromise, timeoutPromise]);
|
||||
try {
|
||||
const reply = await Promise.race([deliverPromise, timeoutPromise]);
|
||||
|
||||
// Send reply back to Synology Chat using the resolved Chat user_id
|
||||
if (reply) {
|
||||
await sendMessage(account.incomingUrl, reply, replyUserId, account.allowInsecureSsl);
|
||||
const replyPreview = reply.length > 100 ? `${reply.slice(0, 100)}...` : reply;
|
||||
log?.info(`Reply sent to ${payload.username} (${replyUserId}): ${replyPreview}`);
|
||||
// Send reply back to Synology Chat using the resolved Chat user_id
|
||||
if (reply) {
|
||||
await sendMessage(account.incomingUrl, reply, replyUserId, account.allowInsecureSsl);
|
||||
const replyPreview = reply.length > 100 ? `${reply.slice(0, 100)}...` : reply;
|
||||
log?.info(`Reply sent to ${payload.username} (${replyUserId}): ${replyPreview}`);
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = err instanceof Error ? `${err.message}\n${err.stack}` : String(err);
|
||||
|
||||
@ -82,12 +82,17 @@ export async function probeTwitch(
|
||||
});
|
||||
});
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const timeout = new Promise<never>((_, reject) => {
|
||||
setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
||||
timer = setTimeout(() => reject(new Error(`timeout after ${timeoutMs}ms`)), timeoutMs);
|
||||
});
|
||||
|
||||
client.connect();
|
||||
await Promise.race([connectionPromise, timeout]);
|
||||
try {
|
||||
await Promise.race([connectionPromise, timeout]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
client.quit();
|
||||
client = undefined;
|
||||
|
||||
@ -11,14 +11,22 @@ export async function probeZalouser(
|
||||
timeoutMs?: number,
|
||||
): Promise<ZalouserProbeResult> {
|
||||
try {
|
||||
const user = timeoutMs
|
||||
? await Promise.race([
|
||||
let user: ZcaUserInfo | null;
|
||||
if (timeoutMs) {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
user = await Promise.race([
|
||||
getZaloUserInfo(profile),
|
||||
new Promise<null>((resolve) =>
|
||||
setTimeout(() => resolve(null), Math.max(timeoutMs, 1000)),
|
||||
),
|
||||
])
|
||||
: await getZaloUserInfo(profile);
|
||||
new Promise<null>((resolve) => {
|
||||
timer = setTimeout(() => resolve(null), Math.max(timeoutMs, 1000));
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
} else {
|
||||
user = await getZaloUserInfo(profile);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return { ok: false, error: "Not authenticated" };
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user