@openclaw/whatsapp 2026.5.2 → 2026.5.3-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-control-BQ3tCkH8.js +288 -0
- package/dist/account-config-CNQ33oav.js +47 -0
- package/dist/account-ids-DG18xddX.js +5 -0
- package/dist/accounts-BDqgpFEB.js +116 -0
- package/dist/action-runtime-DTGNgG-i.js +74 -0
- package/dist/action-runtime-api.js +2 -0
- package/dist/action-runtime.runtime.js +2 -0
- package/dist/active-listener-N7GFKsuN.js +11 -0
- package/dist/agent-tools-login-DpRxLNRB.js +89 -0
- package/dist/api.js +148 -0
- package/dist/audio-preflight.runtime-Bcdsl_ri.js +7 -0
- package/dist/auth-presence.js +32 -0
- package/dist/auth-store-BjCb50Ry.js +381 -0
- package/dist/channel-DQ-5Xoj9.js +542 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel-react-action-Df20qAy9.js +42 -0
- package/dist/channel.runtime-DKXRnA8x.js +55 -0
- package/dist/channel.setup-DOHR2BGp.js +20 -0
- package/dist/command-policy-O8sRnqUV.js +8 -0
- package/dist/config-schema-Dmo66Ubb.js +27 -0
- package/dist/connection-controller-BiHAsTM8.js +596 -0
- package/dist/connection-controller-registry-D_iIGz5m.js +22 -0
- package/dist/constants-DLGCIbfl.js +4 -0
- package/dist/constants.js +2 -0
- package/dist/contract-api.js +19 -0
- package/dist/creds-files-_2V8jzQa.js +19 -0
- package/dist/directory-config-DVs4ffXu.js +37 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/doctor-A9tXm9Om.js +48 -0
- package/dist/doctor-contract-api.js +10 -0
- package/dist/group-session-contract-B8QBn0I2.js +15 -0
- package/dist/identity-QHAlV7Fu.js +75 -0
- package/dist/index.js +18 -0
- package/dist/legacy-session-surface-api.js +8 -0
- package/dist/legacy-state-migrations-api.js +2 -0
- package/dist/light-runtime-api.js +5 -0
- package/dist/login-DymdrKca.js +52 -0
- package/dist/login-qr-DIS0Iv-7.js +378 -0
- package/dist/login-qr-api.js +2 -0
- package/dist/login-qr-runtime.js +16 -0
- package/dist/monitor-C4Fhg_lD.js +3349 -0
- package/dist/normalize-CWYiGkXw.js +2 -0
- package/dist/normalize-target-BPVHNsM5.js +74 -0
- package/dist/outbound-adapter-BssVnZdz.js +29 -0
- package/dist/outbound-base-Bq1UYEK5.js +119 -0
- package/dist/outbound-media-contract-CGss_QHy.js +163 -0
- package/dist/outbound-payload-test-api.js +2 -0
- package/dist/quoted-message-TENQzyxV.js +108 -0
- package/dist/reaction-level-CRVEVAfi.js +16 -0
- package/dist/reply-resolver.runtime-BRCgoO4C.js +2 -0
- package/dist/resolve-outbound-target-CN-Wo6IY.js +39 -0
- package/dist/rolldown-runtime-RkAeH_Qm.js +27 -0
- package/dist/runtime-6fZ67abR.js +8 -0
- package/dist/runtime-api.js +15 -0
- package/dist/runtime-group-policy-Gp1lQ0at.js +11 -0
- package/dist/secret-contract-api.js +5 -0
- package/dist/security-contract-DqH8JDX6.js +26 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/send-CIyLFxdZ.js +234 -0
- package/dist/session-contract-B0efQf9P.js +27 -0
- package/dist/session-errors-CSuKZPub.js +63 -0
- package/dist/session.runtime-C1eSE_KK.js +13 -0
- package/dist/setup-core-DZKvpzbH.js +46 -0
- package/dist/setup-entry.js +23 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/setup-surface-ieFnIW4g.js +344 -0
- package/dist/shared-D_wzSVar.js +307 -0
- package/dist/state-migrations-DnCdvoYD.js +39 -0
- package/dist/targets.js +2 -0
- package/dist/test-api.js +3 -0
- package/dist/text-runtime-C1zNiZeO.js +116 -0
- package/package.json +14 -6
- package/action-runtime-api.ts +0 -1
- package/action-runtime.runtime.ts +0 -1
- package/api.ts +0 -213
- package/auth-presence.ts +0 -80
- package/channel-config-api.ts +0 -1
- package/channel-plugin-api.ts +0 -3
- package/config-api.ts +0 -4
- package/constants.ts +0 -1
- package/contract-api.ts +0 -29
- package/directory-contract-api.ts +0 -4
- package/doctor-contract-api.ts +0 -8
- package/index.test.ts +0 -13
- package/index.ts +0 -16
- package/legacy-session-surface-api.ts +0 -6
- package/legacy-state-migrations-api.ts +0 -1
- package/light-runtime-api.ts +0 -12
- package/login-qr-api.ts +0 -1
- package/login-qr-runtime.ts +0 -23
- package/outbound-payload-test-api.ts +0 -1
- package/runtime-api.ts +0 -84
- package/secret-contract-api.ts +0 -4
- package/security-contract-api.ts +0 -4
- package/setup-entry.test.ts +0 -21
- package/setup-entry.ts +0 -21
- package/setup-plugin-api.ts +0 -3
- package/src/account-config.ts +0 -77
- package/src/account-ids.ts +0 -13
- package/src/account-types.ts +0 -5
- package/src/accounts.test.ts +0 -182
- package/src/accounts.ts +0 -176
- package/src/accounts.whatsapp-auth.test.ts +0 -59
- package/src/action-runtime-target-auth.ts +0 -27
- package/src/action-runtime.test.ts +0 -330
- package/src/action-runtime.ts +0 -76
- package/src/active-listener.test.ts +0 -65
- package/src/active-listener.ts +0 -17
- package/src/agent-tools-login.test.ts +0 -81
- package/src/agent-tools-login.ts +0 -113
- package/src/approval-auth.test.ts +0 -24
- package/src/approval-auth.ts +0 -27
- package/src/auth-store.runtime.ts +0 -1
- package/src/auth-store.test.ts +0 -311
- package/src/auth-store.ts +0 -502
- package/src/auto-reply/config.runtime.ts +0 -16
- package/src/auto-reply/constants.ts +0 -1
- package/src/auto-reply/deliver-reply.test.ts +0 -843
- package/src/auto-reply/deliver-reply.ts +0 -279
- package/src/auto-reply/loggers.ts +0 -6
- package/src/auto-reply/mentions.ts +0 -131
- package/src/auto-reply/monitor/ack-reaction.test.ts +0 -170
- package/src/auto-reply/monitor/ack-reaction.ts +0 -99
- package/src/auto-reply/monitor/audio-preflight.runtime.ts +0 -9
- package/src/auto-reply/monitor/broadcast.ts +0 -153
- package/src/auto-reply/monitor/commands.ts +0 -19
- package/src/auto-reply/monitor/echo.ts +0 -64
- package/src/auto-reply/monitor/group-activation.runtime.ts +0 -1
- package/src/auto-reply/monitor/group-activation.test.ts +0 -189
- package/src/auto-reply/monitor/group-activation.ts +0 -73
- package/src/auto-reply/monitor/group-gating.audio-preflight.test.ts +0 -103
- package/src/auto-reply/monitor/group-gating.runtime.ts +0 -8
- package/src/auto-reply/monitor/group-gating.ts +0 -217
- package/src/auto-reply/monitor/group-members.test.ts +0 -56
- package/src/auto-reply/monitor/group-members.ts +0 -65
- package/src/auto-reply/monitor/inbound-context.test.ts +0 -97
- package/src/auto-reply/monitor/inbound-context.ts +0 -92
- package/src/auto-reply/monitor/inbound-dispatch.runtime.ts +0 -21
- package/src/auto-reply/monitor/inbound-dispatch.test.ts +0 -895
- package/src/auto-reply/monitor/inbound-dispatch.ts +0 -422
- package/src/auto-reply/monitor/last-route.test.ts +0 -37
- package/src/auto-reply/monitor/last-route.ts +0 -61
- package/src/auto-reply/monitor/message-line.runtime.ts +0 -38
- package/src/auto-reply/monitor/message-line.ts +0 -54
- package/src/auto-reply/monitor/on-message.audio-preflight.test.ts +0 -354
- package/src/auto-reply/monitor/on-message.ts +0 -291
- package/src/auto-reply/monitor/peer.ts +0 -17
- package/src/auto-reply/monitor/process-message.audio-preflight.test.ts +0 -420
- package/src/auto-reply/monitor/process-message.test.ts +0 -355
- package/src/auto-reply/monitor/process-message.ts +0 -531
- package/src/auto-reply/monitor/runtime-api.ts +0 -30
- package/src/auto-reply/monitor-state.test.ts +0 -64
- package/src/auto-reply/monitor-state.ts +0 -106
- package/src/auto-reply/monitor.ts +0 -600
- package/src/auto-reply/reply-resolver.runtime.ts +0 -1
- package/src/auto-reply/types.ts +0 -48
- package/src/auto-reply/util.ts +0 -63
- package/src/auto-reply/web-auto-reply-monitor.test.ts +0 -687
- package/src/auto-reply/web-auto-reply-utils.test.ts +0 -392
- package/src/auto-reply.broadcast-groups.combined.test.ts +0 -246
- package/src/auto-reply.broadcast-groups.test-harness.ts +0 -47
- package/src/auto-reply.impl.ts +0 -6
- package/src/auto-reply.test-harness.ts +0 -421
- package/src/auto-reply.ts +0 -1
- package/src/auto-reply.web-auto-reply.compresses-common-formats-jpeg-cap.test.ts +0 -427
- package/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts +0 -908
- package/src/auto-reply.web-auto-reply.last-route.test.ts +0 -184
- package/src/channel-actions.runtime.ts +0 -7
- package/src/channel-actions.test.ts +0 -216
- package/src/channel-actions.ts +0 -84
- package/src/channel-outbound.test.ts +0 -131
- package/src/channel-outbound.ts +0 -36
- package/src/channel-react-action.runtime.ts +0 -7
- package/src/channel-react-action.test.ts +0 -294
- package/src/channel-react-action.ts +0 -84
- package/src/channel.runtime.ts +0 -117
- package/src/channel.setup.test.ts +0 -461
- package/src/channel.setup.ts +0 -28
- package/src/channel.ts +0 -339
- package/src/command-policy.ts +0 -7
- package/src/config-accessors.test.ts +0 -34
- package/src/config-accessors.ts +0 -22
- package/src/config-schema.test.ts +0 -121
- package/src/config-schema.ts +0 -6
- package/src/config-ui-hints.ts +0 -24
- package/src/connection-controller-registry.test.ts +0 -27
- package/src/connection-controller-registry.ts +0 -49
- package/src/connection-controller.test.ts +0 -282
- package/src/connection-controller.ts +0 -679
- package/src/creds-files.ts +0 -19
- package/src/creds-persistence.ts +0 -102
- package/src/directory-config.test.ts +0 -60
- package/src/directory-config.ts +0 -40
- package/src/directory-contract.test.ts +0 -56
- package/src/doctor-contract.ts +0 -11
- package/src/doctor.test.ts +0 -74
- package/src/doctor.ts +0 -56
- package/src/group-intro.ts +0 -15
- package/src/group-policy.test.ts +0 -36
- package/src/group-policy.ts +0 -40
- package/src/group-session-contract.ts +0 -20
- package/src/group-session-key.test.ts +0 -53
- package/src/group-session-key.ts +0 -41
- package/src/heartbeat.ts +0 -34
- package/src/identity.ts +0 -164
- package/src/inbound/access-control.test-harness.ts +0 -37
- package/src/inbound/access-control.test.ts +0 -375
- package/src/inbound/access-control.ts +0 -237
- package/src/inbound/dedupe.ts +0 -132
- package/src/inbound/extract.test.ts +0 -282
- package/src/inbound/extract.ts +0 -488
- package/src/inbound/lifecycle.ts +0 -39
- package/src/inbound/media.node.test.ts +0 -83
- package/src/inbound/media.ts +0 -102
- package/src/inbound/monitor.ts +0 -890
- package/src/inbound/runtime-api.ts +0 -7
- package/src/inbound/save-media.runtime.ts +0 -1
- package/src/inbound/send-api.test.ts +0 -319
- package/src/inbound/send-api.ts +0 -145
- package/src/inbound/send-result.ts +0 -61
- package/src/inbound/types.ts +0 -106
- package/src/inbound-context.contract.test.ts +0 -32
- package/src/inbound-policy.ts +0 -231
- package/src/inbound.media.test.ts +0 -320
- package/src/inbound.test.ts +0 -290
- package/src/inbound.ts +0 -9
- package/src/login-qr.test.ts +0 -483
- package/src/login-qr.ts +0 -542
- package/src/login.coverage.test.ts +0 -133
- package/src/login.test.ts +0 -89
- package/src/login.ts +0 -70
- package/src/logout.test.ts +0 -162
- package/src/media.test.ts +0 -480
- package/src/media.ts +0 -10
- package/src/monitor-inbox.allows-messages-from-senders-allowfrom-list.test-support.ts +0 -400
- package/src/monitor-inbox.append-upsert.test-support.ts +0 -133
- package/src/monitor-inbox.behavior.test.ts +0 -5
- package/src/monitor-inbox.blocks-messages-from-unauthorized-senders-not-allowfrom.test-support.ts +0 -362
- package/src/monitor-inbox.captures-media-path-image-messages.test-support.ts +0 -306
- package/src/monitor-inbox.streams-inbound-messages.test-support.ts +0 -729
- package/src/monitor-inbox.test-harness.ts +0 -304
- package/src/normalize-target.ts +0 -123
- package/src/normalize.ts +0 -7
- package/src/outbound-adapter.poll.test.ts +0 -62
- package/src/outbound-adapter.sendpayload.test.ts +0 -195
- package/src/outbound-adapter.ts +0 -36
- package/src/outbound-base.test.ts +0 -560
- package/src/outbound-base.ts +0 -244
- package/src/outbound-media-contract.ts +0 -278
- package/src/outbound-media.runtime.ts +0 -36
- package/src/outbound-payload.contract.test.ts +0 -61
- package/src/outbound-send-deps.ts +0 -1
- package/src/outbound-test-support.ts +0 -16
- package/src/pairing-security.test-harness.ts +0 -59
- package/src/qr-image.ts +0 -1
- package/src/qr-terminal.ts +0 -1
- package/src/quoted-message.test.ts +0 -83
- package/src/quoted-message.ts +0 -184
- package/src/reaction-level.test.ts +0 -94
- package/src/reaction-level.ts +0 -21
- package/src/reconnect.test.ts +0 -51
- package/src/reconnect.ts +0 -55
- package/src/resolve-outbound-target.test.ts +0 -288
- package/src/resolve-outbound-target.ts +0 -58
- package/src/resolve-target.test.ts +0 -120
- package/src/runtime-api.ts +0 -59
- package/src/runtime-group-policy.ts +0 -16
- package/src/runtime.ts +0 -9
- package/src/security-contract.ts +0 -47
- package/src/security-fix.ts +0 -71
- package/src/send.test.ts +0 -534
- package/src/send.ts +0 -292
- package/src/session-contract.test.ts +0 -31
- package/src/session-contract.ts +0 -43
- package/src/session-errors.ts +0 -125
- package/src/session-route.test.ts +0 -41
- package/src/session-route.ts +0 -32
- package/src/session.runtime.ts +0 -8
- package/src/session.test.ts +0 -559
- package/src/session.ts +0 -330
- package/src/setup-core.ts +0 -52
- package/src/setup-finalize.ts +0 -446
- package/src/setup-surface.test.ts +0 -367
- package/src/setup-surface.ts +0 -69
- package/src/setup-test-helpers.ts +0 -216
- package/src/shared.ts +0 -291
- package/src/socket-timing.test.ts +0 -49
- package/src/socket-timing.ts +0 -38
- package/src/state-migrations.ts +0 -54
- package/src/status-issues.test.ts +0 -137
- package/src/status-issues.ts +0 -185
- package/src/system-prompt.test.ts +0 -199
- package/src/system-prompt.ts +0 -31
- package/src/targets-runtime.ts +0 -180
- package/src/test-helpers.ts +0 -690
- package/src/text-runtime.test.ts +0 -162
- package/src/text-runtime.ts +0 -11
- package/src/vcard.ts +0 -84
- package/targets.ts +0 -5
- package/test-api.ts +0 -2
- package/tsconfig.json +0 -16
package/src/login-qr.test.ts
DELETED
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { startWebLoginWithQr, waitForWebLogin } from "./login-qr.js";
|
|
3
|
-
import { renderQrPngDataUrl } from "./qr-image.js";
|
|
4
|
-
import {
|
|
5
|
-
createWaSocket,
|
|
6
|
-
logoutWeb,
|
|
7
|
-
readWebAuthExistsForDecision,
|
|
8
|
-
readWebSelfId,
|
|
9
|
-
WHATSAPP_AUTH_UNSTABLE_CODE,
|
|
10
|
-
waitForWaConnection,
|
|
11
|
-
} from "./session.js";
|
|
12
|
-
|
|
13
|
-
vi.mock("./session.js", async () => {
|
|
14
|
-
const actual = await vi.importActual<typeof import("./session.js")>("./session.js");
|
|
15
|
-
const createWaSocket = vi.fn();
|
|
16
|
-
const waitForWaConnection = vi.fn();
|
|
17
|
-
const formatError = vi.fn((err: unknown) => `formatted:${String(err)}`);
|
|
18
|
-
const getStatusCode = vi.fn(
|
|
19
|
-
(err: unknown) =>
|
|
20
|
-
(err as { output?: { statusCode?: number } })?.output?.statusCode ??
|
|
21
|
-
(err as { status?: number })?.status ??
|
|
22
|
-
(err as { error?: { output?: { statusCode?: number } } })?.error?.output?.statusCode,
|
|
23
|
-
);
|
|
24
|
-
const readWebAuthExistsForDecision = vi.fn(async () => ({
|
|
25
|
-
outcome: "stable" as const,
|
|
26
|
-
exists: false,
|
|
27
|
-
}));
|
|
28
|
-
const readWebSelfId = vi.fn(() => ({ e164: null, jid: null, lid: null }));
|
|
29
|
-
const logoutWeb = vi.fn(async () => true);
|
|
30
|
-
return {
|
|
31
|
-
...actual,
|
|
32
|
-
createWaSocket,
|
|
33
|
-
waitForWaConnection,
|
|
34
|
-
formatError,
|
|
35
|
-
getStatusCode,
|
|
36
|
-
readWebAuthExistsForDecision,
|
|
37
|
-
readWebSelfId,
|
|
38
|
-
logoutWeb,
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
vi.mock("./qr-image.js", () => ({
|
|
43
|
-
renderQrPngBase64: vi.fn(async () => "base64"),
|
|
44
|
-
renderQrPngDataUrl: vi.fn(async (input: string) => `data:image/png;base64,encoded:${input}`),
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
const createWaSocketMock = vi.mocked(createWaSocket);
|
|
48
|
-
const readWebAuthExistsForDecisionMock = vi.mocked(readWebAuthExistsForDecision);
|
|
49
|
-
const readWebSelfIdMock = vi.mocked(readWebSelfId);
|
|
50
|
-
const waitForWaConnectionMock = vi.mocked(waitForWaConnection);
|
|
51
|
-
const logoutWebMock = vi.mocked(logoutWeb);
|
|
52
|
-
const renderQrPngDataUrlMock = vi.mocked(renderQrPngDataUrl);
|
|
53
|
-
|
|
54
|
-
async function flushTasks() {
|
|
55
|
-
await Promise.resolve();
|
|
56
|
-
await Promise.resolve();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function waitMs(ms: number) {
|
|
60
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function waitForQrRenderCallCount(count: number) {
|
|
64
|
-
const deadline = Date.now() + 1000;
|
|
65
|
-
while (renderQrPngDataUrlMock.mock.calls.length < count && Date.now() < deadline) {
|
|
66
|
-
await waitMs(0);
|
|
67
|
-
await flushTasks();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
describe("login-qr", () => {
|
|
72
|
-
const rotatingAccountId = "rotating-qr";
|
|
73
|
-
const concurrentAccountId = "concurrent-qr";
|
|
74
|
-
|
|
75
|
-
beforeEach(() => {
|
|
76
|
-
vi.clearAllMocks();
|
|
77
|
-
createWaSocketMock
|
|
78
|
-
.mockReset()
|
|
79
|
-
.mockImplementation(
|
|
80
|
-
async (
|
|
81
|
-
_printQr: boolean,
|
|
82
|
-
_verbose: boolean,
|
|
83
|
-
opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
84
|
-
) => {
|
|
85
|
-
const sock = { ws: { close: vi.fn() } };
|
|
86
|
-
if (opts?.onQr) {
|
|
87
|
-
setImmediate(() => opts.onQr?.("qr-data"));
|
|
88
|
-
}
|
|
89
|
-
return sock as never;
|
|
90
|
-
},
|
|
91
|
-
);
|
|
92
|
-
waitForWaConnectionMock.mockReset();
|
|
93
|
-
readWebAuthExistsForDecisionMock.mockReset().mockResolvedValue({
|
|
94
|
-
outcome: "stable",
|
|
95
|
-
exists: false,
|
|
96
|
-
});
|
|
97
|
-
readWebSelfIdMock.mockReset().mockReturnValue({ e164: null, jid: null, lid: null });
|
|
98
|
-
logoutWebMock.mockReset().mockResolvedValue(true);
|
|
99
|
-
renderQrPngDataUrlMock
|
|
100
|
-
.mockReset()
|
|
101
|
-
.mockImplementation(async (input) => `data:image/png;base64,encoded:${input}`);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("restarts login once on status 515 and completes", async () => {
|
|
105
|
-
waitForWaConnectionMock
|
|
106
|
-
// Baileys v7 wraps the error: { error: BoomError(515) }
|
|
107
|
-
.mockRejectedValueOnce({ error: { output: { statusCode: 515 } } })
|
|
108
|
-
.mockResolvedValueOnce(undefined);
|
|
109
|
-
|
|
110
|
-
const start = await startWebLoginWithQr({
|
|
111
|
-
timeoutMs: 5000,
|
|
112
|
-
accountId: rotatingAccountId,
|
|
113
|
-
});
|
|
114
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
115
|
-
|
|
116
|
-
const resultPromise = waitForWebLogin({
|
|
117
|
-
timeoutMs: 5000,
|
|
118
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
119
|
-
accountId: rotatingAccountId,
|
|
120
|
-
});
|
|
121
|
-
await flushTasks();
|
|
122
|
-
await flushTasks();
|
|
123
|
-
|
|
124
|
-
expect(createWaSocketMock).toHaveBeenCalledTimes(2);
|
|
125
|
-
const result = await resultPromise;
|
|
126
|
-
|
|
127
|
-
expect(result.connected).toBe(true);
|
|
128
|
-
expect(createWaSocketMock).toHaveBeenCalledTimes(2);
|
|
129
|
-
expect(logoutWebMock).not.toHaveBeenCalled();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("clears auth and reports a relink message when WhatsApp is logged out", async () => {
|
|
133
|
-
waitForWaConnectionMock.mockRejectedValueOnce({
|
|
134
|
-
output: { statusCode: 401 },
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const start = await startWebLoginWithQr({ timeoutMs: 5000 });
|
|
138
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
139
|
-
|
|
140
|
-
const result = await waitForWebLogin({
|
|
141
|
-
timeoutMs: 5000,
|
|
142
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
expect(result).toEqual({
|
|
146
|
-
connected: false,
|
|
147
|
-
message:
|
|
148
|
-
"WhatsApp reported the session is logged out. Cleared cached web session; please scan a new QR.",
|
|
149
|
-
});
|
|
150
|
-
expect(logoutWebMock).toHaveBeenCalledOnce();
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("turns unexpected login cleanup failures into a normal login error", async () => {
|
|
154
|
-
waitForWaConnectionMock.mockRejectedValueOnce({
|
|
155
|
-
output: { statusCode: 401 },
|
|
156
|
-
});
|
|
157
|
-
logoutWebMock.mockRejectedValueOnce(new Error("cleanup failed"));
|
|
158
|
-
|
|
159
|
-
const start = await startWebLoginWithQr({ timeoutMs: 5000 });
|
|
160
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
161
|
-
|
|
162
|
-
const result = await waitForWebLogin({
|
|
163
|
-
timeoutMs: 5000,
|
|
164
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
expect(result).toEqual({
|
|
168
|
-
connected: false,
|
|
169
|
-
message: "WhatsApp login failed: cleanup failed",
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("returns an unstable-auth result when creds flush does not settle", async () => {
|
|
174
|
-
readWebAuthExistsForDecisionMock.mockResolvedValueOnce({ outcome: "unstable" });
|
|
175
|
-
|
|
176
|
-
const result = await startWebLoginWithQr({ timeoutMs: 5000 });
|
|
177
|
-
|
|
178
|
-
expect(result).toEqual({
|
|
179
|
-
code: WHATSAPP_AUTH_UNSTABLE_CODE,
|
|
180
|
-
message: "WhatsApp auth state is still stabilizing. Retry login in a moment.",
|
|
181
|
-
});
|
|
182
|
-
expect(createWaSocketMock).not.toHaveBeenCalled();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it("reports a recovered linked session when socket bootstrap restores auth without a QR", async () => {
|
|
186
|
-
createWaSocketMock.mockImplementationOnce(
|
|
187
|
-
async (
|
|
188
|
-
_printQr: boolean,
|
|
189
|
-
_verbose: boolean,
|
|
190
|
-
_opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
191
|
-
) =>
|
|
192
|
-
({
|
|
193
|
-
ws: { close: vi.fn() },
|
|
194
|
-
}) as never,
|
|
195
|
-
);
|
|
196
|
-
waitForWaConnectionMock.mockResolvedValueOnce(undefined);
|
|
197
|
-
readWebSelfIdMock.mockReturnValueOnce({ e164: "+5511977000000", jid: null, lid: null });
|
|
198
|
-
|
|
199
|
-
const result = await startWebLoginWithQr({ timeoutMs: 5000 });
|
|
200
|
-
|
|
201
|
-
expect(result).toEqual({
|
|
202
|
-
connected: true,
|
|
203
|
-
message: "WhatsApp recovered the existing linked session (+5511977000000).",
|
|
204
|
-
});
|
|
205
|
-
expect(createWaSocketMock).toHaveBeenCalledOnce();
|
|
206
|
-
await expect(waitForWebLogin({ timeoutMs: 1000 })).resolves.toEqual({
|
|
207
|
-
connected: false,
|
|
208
|
-
message: "No active WhatsApp login in progress.",
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("surfaces the latest QR after the socket rotates it", async () => {
|
|
213
|
-
createWaSocketMock.mockImplementationOnce(
|
|
214
|
-
async (
|
|
215
|
-
_printQr: boolean,
|
|
216
|
-
_verbose: boolean,
|
|
217
|
-
opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
218
|
-
) => {
|
|
219
|
-
const sock = { ws: { close: vi.fn() } };
|
|
220
|
-
setImmediate(() => opts?.onQr?.("qr-data"));
|
|
221
|
-
setTimeout(() => opts?.onQr?.("qr-data-2"), 100);
|
|
222
|
-
return sock as never;
|
|
223
|
-
},
|
|
224
|
-
);
|
|
225
|
-
waitForWaConnectionMock.mockImplementation(() => new Promise(() => {}));
|
|
226
|
-
|
|
227
|
-
const start = await startWebLoginWithQr({ timeoutMs: 5000 });
|
|
228
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
229
|
-
|
|
230
|
-
const resultPromise = waitForWebLogin({
|
|
231
|
-
timeoutMs: 5000,
|
|
232
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
233
|
-
});
|
|
234
|
-
await flushTasks();
|
|
235
|
-
await waitMs(140);
|
|
236
|
-
await flushTasks();
|
|
237
|
-
|
|
238
|
-
await expect(resultPromise).resolves.toEqual({
|
|
239
|
-
connected: false,
|
|
240
|
-
message: "QR refreshed. Scan the latest code in WhatsApp → Linked Devices.",
|
|
241
|
-
qrDataUrl: "data:image/png;base64,encoded:qr-data-2",
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it("does not short-circuit on an existing QR when the waiter has no current QR image", async () => {
|
|
246
|
-
const accountId = "wait-without-current-qr";
|
|
247
|
-
waitForWaConnectionMock.mockImplementationOnce(
|
|
248
|
-
() => new Promise((resolve) => setTimeout(() => resolve(undefined), 20)),
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
const start = await startWebLoginWithQr({
|
|
252
|
-
timeoutMs: 5000,
|
|
253
|
-
accountId,
|
|
254
|
-
});
|
|
255
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
256
|
-
|
|
257
|
-
await expect(
|
|
258
|
-
waitForWebLogin({
|
|
259
|
-
timeoutMs: 5000,
|
|
260
|
-
accountId,
|
|
261
|
-
}),
|
|
262
|
-
).resolves.toEqual({
|
|
263
|
-
connected: true,
|
|
264
|
-
message: "✅ Linked! WhatsApp is ready.",
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("returns a terminal login result before a stale QR refresh", async () => {
|
|
269
|
-
const accountId = "connected-before-refresh";
|
|
270
|
-
let resolveLogin: () => void = () => {
|
|
271
|
-
throw new Error("Expected login wait to be pending");
|
|
272
|
-
};
|
|
273
|
-
createWaSocketMock.mockImplementationOnce(
|
|
274
|
-
async (
|
|
275
|
-
_printQr: boolean,
|
|
276
|
-
_verbose: boolean,
|
|
277
|
-
opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
278
|
-
) => {
|
|
279
|
-
const sock = { ws: { close: vi.fn() } };
|
|
280
|
-
setImmediate(() => opts?.onQr?.("qr-data"));
|
|
281
|
-
setTimeout(() => opts?.onQr?.("qr-data-2"), 20);
|
|
282
|
-
return sock as never;
|
|
283
|
-
},
|
|
284
|
-
);
|
|
285
|
-
waitForWaConnectionMock.mockImplementationOnce(
|
|
286
|
-
() =>
|
|
287
|
-
new Promise<void>((resolve) => {
|
|
288
|
-
resolveLogin = resolve;
|
|
289
|
-
}),
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
const start = await startWebLoginWithQr({
|
|
293
|
-
timeoutMs: 5000,
|
|
294
|
-
accountId,
|
|
295
|
-
});
|
|
296
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
297
|
-
|
|
298
|
-
await waitMs(50);
|
|
299
|
-
await flushTasks();
|
|
300
|
-
resolveLogin();
|
|
301
|
-
await flushTasks();
|
|
302
|
-
|
|
303
|
-
await expect(
|
|
304
|
-
waitForWebLogin({
|
|
305
|
-
timeoutMs: 5000,
|
|
306
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
307
|
-
accountId,
|
|
308
|
-
}),
|
|
309
|
-
).resolves.toEqual({
|
|
310
|
-
connected: true,
|
|
311
|
-
message: "✅ Linked! WhatsApp is ready.",
|
|
312
|
-
});
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("returns a terminal result when an older replaced waiter resolves without state", async () => {
|
|
316
|
-
const accountId = "replaced-login-waiter";
|
|
317
|
-
let resolveFirstConnection: () => void = () => {
|
|
318
|
-
throw new Error("Expected first login wait to be pending");
|
|
319
|
-
};
|
|
320
|
-
waitForWaConnectionMock
|
|
321
|
-
.mockImplementationOnce(
|
|
322
|
-
() =>
|
|
323
|
-
new Promise<void>((resolve) => {
|
|
324
|
-
resolveFirstConnection = resolve;
|
|
325
|
-
}),
|
|
326
|
-
)
|
|
327
|
-
.mockImplementation(() => new Promise(() => {}));
|
|
328
|
-
|
|
329
|
-
const start = await startWebLoginWithQr({
|
|
330
|
-
timeoutMs: 5000,
|
|
331
|
-
accountId,
|
|
332
|
-
});
|
|
333
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
334
|
-
|
|
335
|
-
const waiter = waitForWebLogin({
|
|
336
|
-
timeoutMs: 1000,
|
|
337
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
338
|
-
accountId,
|
|
339
|
-
});
|
|
340
|
-
await flushTasks();
|
|
341
|
-
|
|
342
|
-
const now = Date.now();
|
|
343
|
-
const dateNowSpy = vi.spyOn(Date, "now").mockReturnValue(now + 3 * 60_000 + 1000);
|
|
344
|
-
try {
|
|
345
|
-
const replacement = await startWebLoginWithQr({
|
|
346
|
-
timeoutMs: 5000,
|
|
347
|
-
accountId,
|
|
348
|
-
});
|
|
349
|
-
expect(replacement.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
350
|
-
|
|
351
|
-
resolveFirstConnection();
|
|
352
|
-
|
|
353
|
-
await expect(waiter).resolves.toEqual({
|
|
354
|
-
connected: false,
|
|
355
|
-
message: "Login ended without a connection.",
|
|
356
|
-
});
|
|
357
|
-
} finally {
|
|
358
|
-
dateNowSpy.mockRestore();
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("keeps an active login reusable while a rotated QR image renders", async () => {
|
|
363
|
-
const accountId = "reuse-during-qr-render";
|
|
364
|
-
let onQr: (qr: string) => void = () => {
|
|
365
|
-
throw new Error("Expected QR callback to be registered");
|
|
366
|
-
};
|
|
367
|
-
createWaSocketMock.mockImplementation(
|
|
368
|
-
async (
|
|
369
|
-
_printQr: boolean,
|
|
370
|
-
_verbose: boolean,
|
|
371
|
-
opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
372
|
-
) => {
|
|
373
|
-
const sock = { ws: { close: vi.fn() } };
|
|
374
|
-
onQr = (qr) => opts?.onQr?.(qr);
|
|
375
|
-
setImmediate(() => onQr("qr-data"));
|
|
376
|
-
return sock as never;
|
|
377
|
-
},
|
|
378
|
-
);
|
|
379
|
-
waitForWaConnectionMock.mockImplementation(() => new Promise(() => {}));
|
|
380
|
-
renderQrPngDataUrlMock.mockImplementation((qr) =>
|
|
381
|
-
qr === "qr-data-2"
|
|
382
|
-
? new Promise<string>(() => {})
|
|
383
|
-
: Promise.resolve(`data:image/png;base64,encoded:${qr}`),
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
const start = await startWebLoginWithQr({
|
|
387
|
-
timeoutMs: 5000,
|
|
388
|
-
accountId,
|
|
389
|
-
});
|
|
390
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
391
|
-
|
|
392
|
-
onQr("qr-data-2");
|
|
393
|
-
await flushTasks();
|
|
394
|
-
|
|
395
|
-
const reused = await startWebLoginWithQr({
|
|
396
|
-
timeoutMs: 5000,
|
|
397
|
-
accountId,
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
expect(createWaSocketMock).toHaveBeenCalledTimes(1);
|
|
401
|
-
expect(reused).toEqual({
|
|
402
|
-
qrDataUrl: "data:image/png;base64,encoded:qr-data",
|
|
403
|
-
message: "QR already active. Scan it in WhatsApp → Linked Devices.",
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("deduplicates initial QR rendering while the start path awaits the same image", async () => {
|
|
408
|
-
const accountId = "single-flight-qr";
|
|
409
|
-
let resolveRender: (value: string) => void = () => {
|
|
410
|
-
throw new Error("Expected QR render promise to be pending");
|
|
411
|
-
};
|
|
412
|
-
renderQrPngDataUrlMock.mockImplementationOnce(
|
|
413
|
-
() =>
|
|
414
|
-
new Promise<string>((resolve) => {
|
|
415
|
-
resolveRender = resolve;
|
|
416
|
-
}),
|
|
417
|
-
);
|
|
418
|
-
waitForWaConnectionMock.mockImplementation(() => new Promise(() => {}));
|
|
419
|
-
|
|
420
|
-
const resultPromise = startWebLoginWithQr({
|
|
421
|
-
timeoutMs: 5000,
|
|
422
|
-
accountId,
|
|
423
|
-
});
|
|
424
|
-
await waitForQrRenderCallCount(1);
|
|
425
|
-
|
|
426
|
-
expect(renderQrPngDataUrlMock).toHaveBeenCalledTimes(1);
|
|
427
|
-
|
|
428
|
-
resolveRender("data:image/png;base64,encoded:qr-data");
|
|
429
|
-
await expect(resultPromise).resolves.toEqual({
|
|
430
|
-
qrDataUrl: "data:image/png;base64,encoded:qr-data",
|
|
431
|
-
message: "Scan this QR in WhatsApp → Linked Devices.",
|
|
432
|
-
});
|
|
433
|
-
expect(renderQrPngDataUrlMock).toHaveBeenCalledTimes(1);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it("returns the same rotated QR to concurrent waiters that share the same current image", async () => {
|
|
437
|
-
createWaSocketMock.mockImplementationOnce(
|
|
438
|
-
async (
|
|
439
|
-
_printQr: boolean,
|
|
440
|
-
_verbose: boolean,
|
|
441
|
-
opts?: { authDir?: string; onQr?: (qr: string) => void },
|
|
442
|
-
) => {
|
|
443
|
-
const sock = { ws: { close: vi.fn() } };
|
|
444
|
-
setImmediate(() => opts?.onQr?.("qr-data"));
|
|
445
|
-
setTimeout(() => opts?.onQr?.("qr-data-2"), 100);
|
|
446
|
-
return sock as never;
|
|
447
|
-
},
|
|
448
|
-
);
|
|
449
|
-
waitForWaConnectionMock.mockImplementation(() => new Promise(() => {}));
|
|
450
|
-
|
|
451
|
-
const start = await startWebLoginWithQr({
|
|
452
|
-
timeoutMs: 5000,
|
|
453
|
-
accountId: concurrentAccountId,
|
|
454
|
-
});
|
|
455
|
-
expect(start.qrDataUrl).toBe("data:image/png;base64,encoded:qr-data");
|
|
456
|
-
|
|
457
|
-
const waiterA = waitForWebLogin({
|
|
458
|
-
timeoutMs: 5000,
|
|
459
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
460
|
-
accountId: concurrentAccountId,
|
|
461
|
-
});
|
|
462
|
-
const waiterB = waitForWebLogin({
|
|
463
|
-
timeoutMs: 5000,
|
|
464
|
-
currentQrDataUrl: start.qrDataUrl,
|
|
465
|
-
accountId: concurrentAccountId,
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
await flushTasks();
|
|
469
|
-
await waitMs(140);
|
|
470
|
-
await flushTasks();
|
|
471
|
-
|
|
472
|
-
await expect(waiterA).resolves.toEqual({
|
|
473
|
-
connected: false,
|
|
474
|
-
message: "QR refreshed. Scan the latest code in WhatsApp → Linked Devices.",
|
|
475
|
-
qrDataUrl: "data:image/png;base64,encoded:qr-data-2",
|
|
476
|
-
});
|
|
477
|
-
await expect(waiterB).resolves.toEqual({
|
|
478
|
-
connected: false,
|
|
479
|
-
message: "QR refreshed. Scan the latest code in WhatsApp → Linked Devices.",
|
|
480
|
-
qrDataUrl: "data:image/png;base64,encoded:qr-data-2",
|
|
481
|
-
});
|
|
482
|
-
});
|
|
483
|
-
});
|