Skip to content

Commit 6ae68fa

Browse files
clawdia67mcaxtr
andauthored
fix(whatsapp): use globalThis singleton for active-listener Map (openclaw#47433)
Merged via squash. Prepared head SHA: 1c43dbf Co-authored-by: clawdia67 <261743618+clawdia67@users.noreply.github.com> Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com> Reviewed-by: @mcaxtr
1 parent 0f0cecd commit 6ae68fa

2 files changed

Lines changed: 30 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ Docs: https://docs.openclaw.ai
155155
- 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.
156156
- WhatsApp: stabilize inbound monitor and setup tests (#50007) Thanks @joshavant.
157157
- Matrix: make onboarding status runtime-safe (#49995) Thanks @joshavant.
158+
- 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.
158159

159160
### Breaking
160161

extensions/whatsapp/src/active-listener.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,35 @@ export type ActiveWebListener = {
2828
close?: () => Promise<void>;
2929
};
3030

31-
let _currentListener: ActiveWebListener | null = null;
31+
// Use a process-level singleton to survive bundler code-splitting.
32+
// Rolldown duplicates this module across multiple output chunks, each with its
33+
// own module-scoped `listeners` Map. The WhatsApp provider writes to one chunk's
34+
// Map via setActiveWebListener(), but the outbound send path reads from a
35+
// different chunk's Map via requireActiveWebListener() — so the listener is
36+
// never found. Pinning the Map to globalThis ensures all chunks share one
37+
// instance. See: https://github.com/openclaw/openclaw/issues/14406
38+
const GLOBAL_KEY = "__openclaw_wa_listeners" as const;
39+
const GLOBAL_CURRENT_KEY = "__openclaw_wa_current_listener" as const;
3240

33-
const listeners = new Map<string, ActiveWebListener>();
41+
type GlobalWithListeners = typeof globalThis & {
42+
[GLOBAL_KEY]?: Map<string, ActiveWebListener>;
43+
[GLOBAL_CURRENT_KEY]?: ActiveWebListener | null;
44+
};
45+
46+
const _global = globalThis as GlobalWithListeners;
47+
48+
_global[GLOBAL_KEY] ??= new Map<string, ActiveWebListener>();
49+
_global[GLOBAL_CURRENT_KEY] ??= null;
50+
51+
const listeners = _global[GLOBAL_KEY];
52+
53+
function getCurrentListener(): ActiveWebListener | null {
54+
return _global[GLOBAL_CURRENT_KEY] ?? null;
55+
}
56+
57+
function setCurrentListener(listener: ActiveWebListener | null): void {
58+
_global[GLOBAL_CURRENT_KEY] = listener;
59+
}
3460

3561
export function resolveWebAccountId(accountId?: string | null): string {
3662
return (accountId ?? "").trim() || DEFAULT_ACCOUNT_ID;
@@ -74,7 +100,7 @@ export function setActiveWebListener(
74100
listeners.set(id, listener);
75101
}
76102
if (id === DEFAULT_ACCOUNT_ID) {
77-
_currentListener = listener;
103+
setCurrentListener(listener);
78104
}
79105
}
80106

0 commit comments

Comments
 (0)