Skip to content

Commit 0deddfa

Browse files
authored
feat(whatsapp): support Baileys WebSocket URL override (#97155)
* Allow WhatsApp monitor socket substitution * Refine WhatsApp socket substitution surface * Keep WhatsApp socket seam on existing API * Tighten WhatsApp socket seam type exports * Simplify WhatsApp monitor runtime option state * Use WhatsApp WebSocket URL override
1 parent 6de357a commit 0deddfa

3 files changed

Lines changed: 52 additions & 0 deletions

File tree

extensions/whatsapp/src/session.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const useMultiFileAuthStateMock = vi.mocked(baileys.useMultiFileAuthState);
4141
let createWaSocket: typeof import("./session.js").createWaSocket;
4242
let formatError: typeof import("./session.js").formatError;
4343
let logWebSelfId: typeof import("./session.js").logWebSelfId;
44+
let OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV: typeof import("./session.js").OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV;
4445
let renderQrTerminalMock: ReturnType<typeof vi.fn>;
4546
let waitForWaConnection: typeof import("./session.js").waitForWaConnection;
4647
let waitForCredsSaveQueue: typeof import("./session.js").waitForCredsSaveQueue;
@@ -153,6 +154,7 @@ function readLastSocketOptions(): {
153154
fetchAgent?: unknown;
154155
keepAliveIntervalMs?: number;
155156
printQRInTerminal?: boolean;
157+
waWebSocketUrl?: string | URL;
156158
logger?: { level?: string; trace?: unknown };
157159
} {
158160
const [options] = firstMockCall(
@@ -169,6 +171,7 @@ function readLastSocketOptions(): {
169171
fetchAgent?: unknown;
170172
keepAliveIntervalMs?: number;
171173
printQRInTerminal?: boolean;
174+
waWebSocketUrl?: string | URL;
172175
logger?: { level?: string; trace?: unknown };
173176
};
174177
}
@@ -224,6 +227,7 @@ describe("web session", () => {
224227
createWaSocket,
225228
formatError,
226229
logWebSelfId,
230+
OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV,
227231
waitForWaConnection,
228232
waitForCredsSaveQueue,
229233
writeCredsJsonAtomically,
@@ -411,6 +415,40 @@ describe("web session", () => {
411415
expect(passed.defaultQueryTimeoutMs).toBe(120_000);
412416
});
413417

418+
it("passes explicit Baileys WebSocket URL overrides", async () => {
419+
await createWaSocket(false, false, {
420+
waWebSocketUrl: " ws://127.0.0.1:49152/ws/chat ",
421+
});
422+
423+
expect(readLastSocketOptions().waWebSocketUrl).toBe("ws://127.0.0.1:49152/ws/chat");
424+
});
425+
426+
it("uses OPENCLAW_WHATSAPP_WEB_SOCKET_URL as the default Baileys WebSocket URL", async () => {
427+
vi.stubEnv(OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV, " ws://127.0.0.1:49153/ws/chat ");
428+
429+
await createWaSocket(false, false);
430+
431+
expect(readLastSocketOptions().waWebSocketUrl).toBe("ws://127.0.0.1:49153/ws/chat");
432+
});
433+
434+
it("preserves explicit Baileys WebSocket URL options over environment", async () => {
435+
vi.stubEnv(OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV, "ws://127.0.0.1:49153/ws/chat");
436+
437+
await createWaSocket(false, false, {
438+
waWebSocketUrl: "ws://127.0.0.1:49154/ws/chat",
439+
});
440+
441+
expect(readLastSocketOptions().waWebSocketUrl).toBe("ws://127.0.0.1:49154/ws/chat");
442+
});
443+
444+
it("ignores blank Baileys WebSocket URL environment overrides", async () => {
445+
vi.stubEnv(OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV, " ");
446+
447+
await createWaSocket(false, false);
448+
449+
expect(readLastSocketOptions().waWebSocketUrl).toBeUndefined();
450+
});
451+
414452
it("uses ambient env proxy agent when HTTPS_PROXY is configured", async () => {
415453
vi.stubEnv("HTTPS_PROXY", "http://proxy.test:8080");
416454

extensions/whatsapp/src/session.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const LOGGED_OUT_STATUS = 401;
6868
const WHATSAPP_WEBSOCKET_PROXY_TARGET = "https://mmg.whatsapp.net/";
6969
const CREDS_FLUSH_TIMEOUT_MESSAGE =
7070
"Queued WhatsApp creds save did not finish before auth bootstrap; skipping repair and continuing with primary creds.";
71+
export const OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV = "OPENCLAW_WHATSAPP_WEB_SOCKET_URL";
7172

7273
async function rejectUnsafeWebCredsPath(authDir: string): Promise<void> {
7374
await assertWebCredsPathRegularFileOrMissing(resolveWebCredsPath(authDir));
@@ -125,6 +126,13 @@ async function printTerminalQr(qr: string): Promise<void> {
125126
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
126127
}
127128

129+
function resolveWaWebSocketUrl(value: string | URL | undefined): string | URL | undefined {
130+
if (typeof value !== "string") {
131+
return value;
132+
}
133+
return value.trim() || undefined;
134+
}
135+
128136
/**
129137
* Create a Baileys socket backed by the multi-file auth store we keep on disk.
130138
* Consumers can opt into QR printing for interactive login flows.
@@ -137,6 +145,7 @@ export async function createWaSocket(
137145
onQr?: (qr: string) => void;
138146
getMessage?: (key: WAMessageKey) => Promise<proto.IMessage | undefined>;
139147
cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata | undefined>;
148+
waWebSocketUrl?: string | URL;
140149
} & WhatsAppSocketTimingOptions = {},
141150
): Promise<ReturnType<typeof makeWASocket>> {
142151
const baseLogger = getChildLogger(
@@ -172,6 +181,9 @@ export async function createWaSocket(
172181
defaultQueryTimeoutMs:
173182
opts.defaultQueryTimeoutMs ?? DEFAULT_WHATSAPP_SOCKET_TIMING.defaultQueryTimeoutMs,
174183
};
184+
const waWebSocketUrl =
185+
resolveWaWebSocketUrl(opts.waWebSocketUrl) ??
186+
resolveWaWebSocketUrl(process.env[OPENCLAW_WHATSAPP_WEB_SOCKET_URL_ENV]);
175187
const sock = makeWASocket({
176188
auth: {
177189
creds: state.creds,
@@ -188,6 +200,7 @@ export async function createWaSocket(
188200
// Baileys types still model `fetchAgent` as a Node agent even though the
189201
// runtime path accepts an undici dispatcher for upload fetches.
190202
fetchAgent: fetchAgent as Agent | undefined,
203+
...(waWebSocketUrl ? { waWebSocketUrl } : {}),
191204
...(opts.getMessage ? { getMessage: opts.getMessage } : {}),
192205
...(opts.cachedGroupMetadata ? { cachedGroupMetadata: opts.cachedGroupMetadata } : {}),
193206
});

src/infra/dotenv.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ describe("workspace .env blocklist completeness", () => {
921921
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
922922
"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
923923
"OPENCLAW_BROWSER_EXECUTABLE_PATH",
924+
"OPENCLAW_WHATSAPP_WEB_SOCKET_URL",
924925
"EXAMPLE_API_HOST",
925926
"HOMEBREW_BREW_FILE",
926927
"HOMEBREW_PREFIX",

0 commit comments

Comments
 (0)