mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix(whatsapp): route login qr through runtime
This commit is contained in:
@@ -213,6 +213,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/update: avoid `launchctl kickstart -k` immediately after fresh macOS update bootstraps, and unlink dangling global plugin-runtime symlinks during packaged postinstall and `doctor --fix` so upgrades no longer SIGTERM the newly booted Gateway or leave bundled plugin imports pointed at pruned `plugin-runtime-deps` trees. Completes #76261 and fixes #76466. (#76929)
|
||||
- Google Chat: normalize custom Google auth transport headers before google-auth/gaxios interceptors run, restoring webhook token verification when certificate retrieval expects Fetch `Headers`. Fixes #76742. Thanks @donbowman.
|
||||
- Google Chat: normalize Google auth certificate response headers before google-auth-library reads cache-control, so inbound webhook auth no longer rejects with `res?.headers.get is not a function`. Fixes #76880. Thanks @donbowman.
|
||||
- WhatsApp: route terminal login QR output through the active runtime for initial and restart sockets, so `openclaw channels login --channel whatsapp` does not lose the QR behind direct stdout writes. Fixes #76213. Thanks @dougvk.
|
||||
- Doctor/plugins: reset stale `plugins.slots.memory` and `plugins.slots.contextEngine` references during `doctor --fix`, so cleanup of missing plugin config does not leave unrecoverable slot owners behind. Fixes #76550 and #76551. Thanks @vincentkoc.
|
||||
- Docs/WhatsApp: merge the duplicate top-level `web` objects in the gateway channel config example so copy-pasted WhatsApp config keeps both `web.whatsapp` and reconnect settings. Fixes #76619. Thanks @WadydX.
|
||||
- Plugins/Anthropic: expose Claude thinking profiles from the bundled provider-policy artifact so non-runtime callers keep Opus 4.7 `adaptive`, `xhigh`, and `max` instead of downgrading to `high`. Fixes #76779. Thanks @tomascupr and @iAbhi001.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { rmSync } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { loginWeb } from "./login.js";
|
||||
import { renderQrTerminal } from "./qr-terminal.js";
|
||||
import { createWaSocket, formatError, waitForWaConnection } from "./session.js";
|
||||
|
||||
const rmMock = vi.spyOn(fs, "rm");
|
||||
@@ -63,9 +65,14 @@ vi.mock("./session.js", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./qr-terminal.js", () => ({
|
||||
renderQrTerminal: vi.fn(async (qr: string) => `terminal:${qr}\n`),
|
||||
}));
|
||||
|
||||
const createWaSocketMock = vi.mocked(createWaSocket);
|
||||
const waitForWaConnectionMock = vi.mocked(waitForWaConnection);
|
||||
const formatErrorMock = vi.mocked(formatError);
|
||||
const renderQrTerminalMock = vi.mocked(renderQrTerminal);
|
||||
|
||||
async function flushTasks() {
|
||||
await Promise.resolve();
|
||||
@@ -94,7 +101,7 @@ describe("loginWeb coverage", () => {
|
||||
.mockRejectedValueOnce({ error: { output: { statusCode: 515 } } })
|
||||
.mockResolvedValueOnce(undefined);
|
||||
|
||||
const runtime = { log: vi.fn(), error: vi.fn() } as never;
|
||||
const runtime: RuntimeEnv = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
const pendingLogin = loginWeb(false, waitForWaConnectionMock as never, runtime);
|
||||
await flushTasks();
|
||||
|
||||
@@ -109,6 +116,35 @@ describe("loginWeb coverage", () => {
|
||||
expect(secondSock.ws.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("routes QR output through runtime for initial and restart sockets", async () => {
|
||||
waitForWaConnectionMock
|
||||
.mockRejectedValueOnce({ error: { output: { statusCode: 515 } } })
|
||||
.mockResolvedValueOnce(undefined);
|
||||
|
||||
const runtime: RuntimeEnv = { log: vi.fn(), error: vi.fn(), exit: vi.fn() };
|
||||
await loginWeb(false, waitForWaConnectionMock as never, runtime);
|
||||
|
||||
expect(createWaSocketMock).toHaveBeenCalledTimes(2);
|
||||
expect(createWaSocketMock.mock.calls[0]?.[0]).toBe(false);
|
||||
const initialOpts = createWaSocketMock.mock.calls[0]?.[2] as
|
||||
| { onQr?: (qr: string) => void }
|
||||
| undefined;
|
||||
const restartOpts = createWaSocketMock.mock.calls[1]?.[2] as
|
||||
| { onQr?: (qr: string) => void }
|
||||
| undefined;
|
||||
expect(initialOpts?.onQr).toBe(restartOpts?.onQr);
|
||||
|
||||
initialOpts?.onQr?.("initial-qr");
|
||||
restartOpts?.onQr?.("restart-qr");
|
||||
await flushTasks();
|
||||
|
||||
expect(runtime.log).toHaveBeenCalledWith("Scan this QR in WhatsApp (Linked Devices):");
|
||||
expect(runtime.log).toHaveBeenCalledWith("terminal:initial-qr");
|
||||
expect(runtime.log).toHaveBeenCalledWith("terminal:restart-qr");
|
||||
expect(renderQrTerminalMock).toHaveBeenCalledWith("initial-qr", { small: true });
|
||||
expect(renderQrTerminalMock).toHaveBeenCalledWith("restart-qr", { small: true });
|
||||
});
|
||||
|
||||
it("clears creds and throws when logged out", async () => {
|
||||
waitForWaConnectionMock.mockRejectedValueOnce({
|
||||
output: { statusCode: 401 },
|
||||
|
||||
@@ -6,6 +6,7 @@ import { logInfo } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveWhatsAppAccount } from "./accounts.js";
|
||||
import { restoreCredsFromBackupIfNeeded } from "./auth-store.js";
|
||||
import { closeWaSocketSoon, waitForWhatsAppLoginResult } from "./connection-controller.js";
|
||||
import { renderQrTerminal } from "./qr-terminal.js";
|
||||
import { createWaSocket, waitForWaConnection } from "./session.js";
|
||||
import { resolveWhatsAppSocketTiming } from "./socket-timing.js";
|
||||
|
||||
@@ -19,9 +20,20 @@ export async function loginWeb(
|
||||
const account = resolveWhatsAppAccount({ cfg, accountId });
|
||||
const socketTiming = resolveWhatsAppSocketTiming(cfg);
|
||||
const restoredFromBackup = await restoreCredsFromBackupIfNeeded(account.authDir);
|
||||
let sock = await createWaSocket(true, verbose, {
|
||||
const onQr = (qr: string) => {
|
||||
runtime.log("Scan this QR in WhatsApp (Linked Devices):");
|
||||
void renderQrTerminal(qr, { small: true })
|
||||
.then((output) => {
|
||||
runtime.log(output.endsWith("\n") ? output.slice(0, -1) : output);
|
||||
})
|
||||
.catch((err) => {
|
||||
runtime.error(`failed rendering WhatsApp QR: ${String(err)}`);
|
||||
});
|
||||
};
|
||||
let sock = await createWaSocket(false, verbose, {
|
||||
authDir: account.authDir,
|
||||
...socketTiming,
|
||||
onQr,
|
||||
});
|
||||
logInfo("Waiting for WhatsApp connection...", runtime);
|
||||
try {
|
||||
@@ -33,6 +45,7 @@ export async function loginWeb(
|
||||
runtime,
|
||||
waitForConnection,
|
||||
socketTiming,
|
||||
onQr,
|
||||
onSocketReplaced: (replacementSock) => {
|
||||
sock = replacementSock;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user