fix(whatsapp): use globalThis singleton for active-listener Map (#47433)

Merged via squash.

Prepared head SHA: 1c43dbff399853fd0bd4132886c3394d6659e85b
Co-authored-by: clawdia67 <261743618+clawdia67@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
This commit is contained in:
clawdia 2026-03-19 02:16:31 +01:00 committed by GitHub
parent 0f0cecd2e8
commit 6ae68faf5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 30 additions and 3 deletions

View File

@ -155,6 +155,7 @@ Docs: https://docs.openclaw.ai
- Google Chat/runtime API: thin the private runtime barrel onto the curated public SDK surface while keeping public Google Chat exports intact. (#49504) Thanks @scoootscooob.
- WhatsApp: stabilize inbound monitor and setup tests (#50007) Thanks @joshavant.
- Matrix: make onboarding status runtime-safe (#49995) Thanks @joshavant.
- WhatsApp/active-listener: pin the active listener registry to a `globalThis` singleton so split WhatsApp bundle chunks share one listener map and outbound sends stop missing the registered session. (#47433) Thanks @clawdia67.
### Breaking

View File

@ -28,9 +28,35 @@ export type ActiveWebListener = {
close?: () => Promise<void>;
};
let _currentListener: ActiveWebListener | null = null;
// Use a process-level singleton to survive bundler code-splitting.
// Rolldown duplicates this module across multiple output chunks, each with its
// own module-scoped `listeners` Map. The WhatsApp provider writes to one chunk's
// Map via setActiveWebListener(), but the outbound send path reads from a
// different chunk's Map via requireActiveWebListener() — so the listener is
// never found. Pinning the Map to globalThis ensures all chunks share one
// instance. See: https://github.com/openclaw/openclaw/issues/14406
const GLOBAL_KEY = "__openclaw_wa_listeners" as const;
const GLOBAL_CURRENT_KEY = "__openclaw_wa_current_listener" as const;
const listeners = new Map<string, ActiveWebListener>();
type GlobalWithListeners = typeof globalThis & {
[GLOBAL_KEY]?: Map<string, ActiveWebListener>;
[GLOBAL_CURRENT_KEY]?: ActiveWebListener | null;
};
const _global = globalThis as GlobalWithListeners;
_global[GLOBAL_KEY] ??= new Map<string, ActiveWebListener>();
_global[GLOBAL_CURRENT_KEY] ??= null;
const listeners = _global[GLOBAL_KEY];
function getCurrentListener(): ActiveWebListener | null {
return _global[GLOBAL_CURRENT_KEY] ?? null;
}
function setCurrentListener(listener: ActiveWebListener | null): void {
_global[GLOBAL_CURRENT_KEY] = listener;
}
export function resolveWebAccountId(accountId?: string | null): string {
return (accountId ?? "").trim() || DEFAULT_ACCOUNT_ID;
@ -74,7 +100,7 @@ export function setActiveWebListener(
listeners.set(id, listener);
}
if (id === DEFAULT_ACCOUNT_ID) {
_currentListener = listener;
setCurrentListener(listener);
}
}