@openclaw/whatsapp 2026.5.2-beta.2 → 2026.5.3-beta.1
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 -334
- 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 -307
- package/src/inbound/send-api.ts +0 -141
- 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 -112
- package/src/normalize.ts +0 -6
- 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 -265
- package/src/resolve-outbound-target.ts +0 -54
- package/src/resolve-target.test.ts +0 -97
- 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 -515
- package/src/send.ts +0 -287
- 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.ts +0 -26
- 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
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { WhatsAppConfigSchema, buildChannelConfigSchema } from "openclaw/plugin-sdk/bundled-channel-config-schema";
|
|
2
|
+
//#endregion
|
|
3
|
+
//#region extensions/whatsapp/src/config-schema.ts
|
|
4
|
+
const WhatsAppChannelConfigSchema = buildChannelConfigSchema(WhatsAppConfigSchema, { uiHints: {
|
|
5
|
+
"": {
|
|
6
|
+
label: "WhatsApp",
|
|
7
|
+
help: "WhatsApp channel provider configuration for access policy and message batching behavior. Use this section to tune responsiveness and direct-message routing safety for WhatsApp chats."
|
|
8
|
+
},
|
|
9
|
+
dmPolicy: {
|
|
10
|
+
label: "WhatsApp DM Policy",
|
|
11
|
+
help: "Direct message access control (\"pairing\" recommended). \"open\" requires channels.whatsapp.allowFrom=[\"*\"]."
|
|
12
|
+
},
|
|
13
|
+
selfChatMode: {
|
|
14
|
+
label: "WhatsApp Self-Phone Mode",
|
|
15
|
+
help: "Same-phone setup (bot uses your personal WhatsApp number)."
|
|
16
|
+
},
|
|
17
|
+
debounceMs: {
|
|
18
|
+
label: "WhatsApp Message Debounce (ms)",
|
|
19
|
+
help: "Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable)."
|
|
20
|
+
},
|
|
21
|
+
configWrites: {
|
|
22
|
+
label: "WhatsApp Config Writes",
|
|
23
|
+
help: "Allow WhatsApp to write config in response to channel events/commands (default: true)."
|
|
24
|
+
}
|
|
25
|
+
} });
|
|
26
|
+
//#endregion
|
|
27
|
+
export { WhatsAppChannelConfigSchema as t };
|
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { n as resolveWebCredsBackupPath, r as resolveWebCredsPath } from "./creds-files-_2V8jzQa.js";
|
|
2
|
+
import { n as registerWhatsAppConnectionController, r as unregisterWhatsAppConnectionController } from "./connection-controller-registry-D_iIGz5m.js";
|
|
3
|
+
import { n as getStatusCode, t as formatError } from "./session-errors-CSuKZPub.js";
|
|
4
|
+
import { C as waitForCredsSaveQueueWithTimeout, l as readCredsJsonRaw, s as logoutWeb, v as resolveDefaultWebAuthDir, w as writeCredsJsonAtomically, x as enqueueCredsSave, y as restoreCredsFromBackupIfNeeded } from "./auth-store-BjCb50Ry.js";
|
|
5
|
+
import { i as makeWASocket, n as fetchLatestBaileysVersion, o as useMultiFileAuthState, r as makeCacheableSignalKeyStore, t as DisconnectReason$1 } from "./session.runtime-C1eSE_KK.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { clamp, ensureDir, resolveUserPath } from "openclaw/plugin-sdk/text-runtime";
|
|
8
|
+
import { VERSION, formatCliCommand } from "openclaw/plugin-sdk/cli-runtime";
|
|
9
|
+
import { computeBackoff, danger, getChildLogger as getChildLogger$1, info, sleepWithAbort, success, toPinoLikeLogger } from "openclaw/plugin-sdk/runtime-env";
|
|
10
|
+
import { renderQrTerminal } from "openclaw/plugin-sdk/media-runtime";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { DisconnectReason } from "@whiskeysockets/baileys";
|
|
13
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
14
|
+
import { resolveEnvHttpProxyUrl, shouldUseEnvHttpProxyForUrl } from "openclaw/plugin-sdk/fetch-runtime";
|
|
15
|
+
const DEFAULT_RECONNECT_POLICY = {
|
|
16
|
+
initialMs: 2e3,
|
|
17
|
+
maxMs: 3e4,
|
|
18
|
+
factor: 1.8,
|
|
19
|
+
jitter: .25,
|
|
20
|
+
maxAttempts: 12
|
|
21
|
+
};
|
|
22
|
+
function resolveHeartbeatSeconds(cfg, overrideSeconds) {
|
|
23
|
+
const candidate = overrideSeconds ?? cfg.web?.heartbeatSeconds;
|
|
24
|
+
if (typeof candidate === "number" && candidate > 0) return candidate;
|
|
25
|
+
return 60;
|
|
26
|
+
}
|
|
27
|
+
function resolveReconnectPolicy(cfg, overrides) {
|
|
28
|
+
const reconnectOverrides = cfg.web?.reconnect ?? {};
|
|
29
|
+
const overrideConfig = overrides ?? {};
|
|
30
|
+
const merged = {
|
|
31
|
+
...DEFAULT_RECONNECT_POLICY,
|
|
32
|
+
...reconnectOverrides,
|
|
33
|
+
...overrideConfig
|
|
34
|
+
};
|
|
35
|
+
merged.initialMs = Math.max(250, merged.initialMs);
|
|
36
|
+
merged.maxMs = Math.max(merged.initialMs, merged.maxMs);
|
|
37
|
+
merged.factor = clamp(merged.factor, 1.1, 10);
|
|
38
|
+
merged.jitter = clamp(merged.jitter, 0, 1);
|
|
39
|
+
merged.maxAttempts = Math.max(0, Math.floor(merged.maxAttempts));
|
|
40
|
+
return merged;
|
|
41
|
+
}
|
|
42
|
+
function newConnectionId$1() {
|
|
43
|
+
return randomUUID();
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region extensions/whatsapp/src/socket-timing.ts
|
|
47
|
+
const DEFAULT_WHATSAPP_SOCKET_TIMING = {
|
|
48
|
+
keepAliveIntervalMs: 25e3,
|
|
49
|
+
connectTimeoutMs: 6e4,
|
|
50
|
+
defaultQueryTimeoutMs: 6e4
|
|
51
|
+
};
|
|
52
|
+
function positiveInteger(value) {
|
|
53
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
54
|
+
}
|
|
55
|
+
function resolveWhatsAppSocketTiming(cfg, overrides) {
|
|
56
|
+
const configured = cfg.web?.whatsapp;
|
|
57
|
+
return {
|
|
58
|
+
keepAliveIntervalMs: positiveInteger(overrides?.keepAliveIntervalMs) ?? positiveInteger(configured?.keepAliveIntervalMs) ?? DEFAULT_WHATSAPP_SOCKET_TIMING.keepAliveIntervalMs,
|
|
59
|
+
connectTimeoutMs: positiveInteger(overrides?.connectTimeoutMs) ?? positiveInteger(configured?.connectTimeoutMs) ?? DEFAULT_WHATSAPP_SOCKET_TIMING.connectTimeoutMs,
|
|
60
|
+
defaultQueryTimeoutMs: positiveInteger(overrides?.defaultQueryTimeoutMs) ?? positiveInteger(configured?.defaultQueryTimeoutMs) ?? DEFAULT_WHATSAPP_SOCKET_TIMING.defaultQueryTimeoutMs
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region extensions/whatsapp/src/session.ts
|
|
65
|
+
const LOGGED_OUT_STATUS$1 = DisconnectReason$1?.loggedOut ?? 401;
|
|
66
|
+
const WHATSAPP_WEBSOCKET_PROXY_TARGET = "https://mmg.whatsapp.net/";
|
|
67
|
+
const CREDS_FLUSH_TIMEOUT_MESSAGE = "Queued WhatsApp creds save did not finish before auth bootstrap; skipping repair and continuing with primary creds.";
|
|
68
|
+
function enqueueSaveCreds(authDir, saveCreds, logger) {
|
|
69
|
+
enqueueCredsSave(authDir, () => safeSaveCreds(authDir, saveCreds, logger), (err) => {
|
|
70
|
+
logger.warn({ error: String(err) }, "WhatsApp creds save queue error");
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function safeSaveCreds(authDir, saveCreds, logger) {
|
|
74
|
+
try {
|
|
75
|
+
const credsPath = resolveWebCredsPath(authDir);
|
|
76
|
+
const backupPath = resolveWebCredsBackupPath(authDir);
|
|
77
|
+
const raw = readCredsJsonRaw(credsPath);
|
|
78
|
+
if (raw) try {
|
|
79
|
+
JSON.parse(raw);
|
|
80
|
+
fs.copyFileSync(credsPath, backupPath);
|
|
81
|
+
try {
|
|
82
|
+
fs.chmodSync(backupPath, 384);
|
|
83
|
+
} catch {}
|
|
84
|
+
} catch {}
|
|
85
|
+
} catch {}
|
|
86
|
+
try {
|
|
87
|
+
await Promise.resolve(saveCreds());
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.warn({ error: String(err) }, "failed saving WhatsApp creds");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function printTerminalQr(qr) {
|
|
93
|
+
const output = await renderQrTerminal(qr, { small: true });
|
|
94
|
+
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a Baileys socket backed by the multi-file auth store we keep on disk.
|
|
98
|
+
* Consumers can opt into QR printing for interactive login flows.
|
|
99
|
+
*/
|
|
100
|
+
async function createWaSocket(printQr, verbose, opts = {}) {
|
|
101
|
+
const logger = toPinoLikeLogger(getChildLogger$1({ module: "baileys" }, { level: verbose ? "info" : "silent" }), verbose ? "info" : "silent");
|
|
102
|
+
const authDir = resolveUserPath(opts.authDir ?? resolveDefaultWebAuthDir());
|
|
103
|
+
await ensureDir(authDir);
|
|
104
|
+
const sessionLogger = getChildLogger$1({ module: "web-session" });
|
|
105
|
+
if (await waitForCredsSaveQueueWithTimeout(authDir) === "timed_out") sessionLogger.warn({ authDir }, CREDS_FLUSH_TIMEOUT_MESSAGE);
|
|
106
|
+
else await restoreCredsFromBackupIfNeeded(authDir);
|
|
107
|
+
const { state } = await useMultiFileAuthState(authDir);
|
|
108
|
+
const saveCreds = async () => {
|
|
109
|
+
await writeCredsJsonAtomically(authDir, state.creds);
|
|
110
|
+
};
|
|
111
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
112
|
+
const agent = await resolveEnvProxyAgent(sessionLogger);
|
|
113
|
+
const fetchAgent = await resolveEnvFetchDispatcher(sessionLogger, agent);
|
|
114
|
+
const socketTiming = {
|
|
115
|
+
keepAliveIntervalMs: opts.keepAliveIntervalMs ?? DEFAULT_WHATSAPP_SOCKET_TIMING.keepAliveIntervalMs,
|
|
116
|
+
connectTimeoutMs: opts.connectTimeoutMs ?? DEFAULT_WHATSAPP_SOCKET_TIMING.connectTimeoutMs,
|
|
117
|
+
defaultQueryTimeoutMs: opts.defaultQueryTimeoutMs ?? DEFAULT_WHATSAPP_SOCKET_TIMING.defaultQueryTimeoutMs
|
|
118
|
+
};
|
|
119
|
+
const sock = makeWASocket({
|
|
120
|
+
auth: {
|
|
121
|
+
creds: state.creds,
|
|
122
|
+
keys: makeCacheableSignalKeyStore(state.keys, logger)
|
|
123
|
+
},
|
|
124
|
+
version,
|
|
125
|
+
logger,
|
|
126
|
+
printQRInTerminal: false,
|
|
127
|
+
browser: [
|
|
128
|
+
"openclaw",
|
|
129
|
+
"cli",
|
|
130
|
+
VERSION
|
|
131
|
+
],
|
|
132
|
+
syncFullHistory: false,
|
|
133
|
+
markOnlineOnConnect: false,
|
|
134
|
+
...socketTiming,
|
|
135
|
+
agent,
|
|
136
|
+
fetchAgent
|
|
137
|
+
});
|
|
138
|
+
sock.ev.on("creds.update", () => enqueueSaveCreds(authDir, saveCreds, sessionLogger));
|
|
139
|
+
sock.ev.on("connection.update", async (update) => {
|
|
140
|
+
try {
|
|
141
|
+
const { connection, lastDisconnect, qr } = update;
|
|
142
|
+
if (qr) {
|
|
143
|
+
opts.onQr?.(qr);
|
|
144
|
+
if (printQr) {
|
|
145
|
+
console.log("Scan this QR in WhatsApp (Linked Devices):");
|
|
146
|
+
printTerminalQr(qr).catch((err) => {
|
|
147
|
+
sessionLogger.warn({ error: String(err) }, "failed rendering WhatsApp QR");
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (connection === "close") {
|
|
152
|
+
if (getStatusCode(lastDisconnect?.error) === LOGGED_OUT_STATUS$1) console.error(danger(`WhatsApp session logged out. Run: ${formatCliCommand("openclaw channels login")}`));
|
|
153
|
+
}
|
|
154
|
+
if (connection === "open" && verbose) console.log(success("WhatsApp Web connected."));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
sessionLogger.error({ error: String(err) }, "connection.update handler error");
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
if (sock.ws && typeof sock.ws.on === "function") sock.ws.on("error", (err) => {
|
|
160
|
+
sessionLogger.error({ error: String(err) }, "WebSocket error");
|
|
161
|
+
});
|
|
162
|
+
return sock;
|
|
163
|
+
}
|
|
164
|
+
async function resolveEnvProxyAgent(logger) {
|
|
165
|
+
if (!shouldUseEnvHttpProxyForUrl(WHATSAPP_WEBSOCKET_PROXY_TARGET)) return;
|
|
166
|
+
const proxyUrl = resolveEnvHttpProxyUrl("https");
|
|
167
|
+
if (!proxyUrl) return;
|
|
168
|
+
try {
|
|
169
|
+
const agent = new HttpsProxyAgent(proxyUrl);
|
|
170
|
+
logger.info("Using ambient env proxy for WhatsApp WebSocket connection");
|
|
171
|
+
return agent;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logger.warn({ error: String(error) }, "Failed to initialize env proxy agent for WhatsApp WebSocket connection");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function resolveEnvFetchDispatcher(logger, agent) {
|
|
178
|
+
const proxyUrl = resolveProxyUrlFromAgent(agent);
|
|
179
|
+
const envProxyUrl = resolveEnvHttpsProxyUrl();
|
|
180
|
+
if (!proxyUrl && !envProxyUrl) return;
|
|
181
|
+
try {
|
|
182
|
+
const { EnvHttpProxyAgent, ProxyAgent } = await import("undici");
|
|
183
|
+
return proxyUrl ? new ProxyAgent({
|
|
184
|
+
allowH2: false,
|
|
185
|
+
uri: proxyUrl
|
|
186
|
+
}) : new EnvHttpProxyAgent({ allowH2: false });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.warn({ error: String(error) }, "Failed to initialize env proxy dispatcher for WhatsApp media uploads");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function resolveProxyUrlFromAgent(agent) {
|
|
193
|
+
if (typeof agent !== "object" || agent === null || !("proxy" in agent)) return;
|
|
194
|
+
const proxy = agent.proxy;
|
|
195
|
+
if (proxy instanceof URL) return proxy.toString();
|
|
196
|
+
return typeof proxy === "string" && proxy.length > 0 ? proxy : void 0;
|
|
197
|
+
}
|
|
198
|
+
function resolveEnvHttpsProxyUrl(env = process.env) {
|
|
199
|
+
const lowerHttpsProxy = normalizeEnvProxyValue(env.https_proxy);
|
|
200
|
+
const lowerHttpProxy = normalizeEnvProxyValue(env.http_proxy);
|
|
201
|
+
const httpsProxy = lowerHttpsProxy !== void 0 ? lowerHttpsProxy : normalizeEnvProxyValue(env.HTTPS_PROXY);
|
|
202
|
+
const httpProxy = lowerHttpProxy !== void 0 ? lowerHttpProxy : normalizeEnvProxyValue(env.HTTP_PROXY);
|
|
203
|
+
return httpsProxy ?? httpProxy ?? void 0;
|
|
204
|
+
}
|
|
205
|
+
function normalizeEnvProxyValue(value) {
|
|
206
|
+
if (typeof value !== "string") return;
|
|
207
|
+
const trimmed = value.trim();
|
|
208
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
209
|
+
}
|
|
210
|
+
async function waitForWaConnection(sock) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const evWithOff = sock.ev;
|
|
213
|
+
const handler = (...args) => {
|
|
214
|
+
const update = args[0] ?? {};
|
|
215
|
+
if (update.connection === "open") {
|
|
216
|
+
evWithOff.off?.("connection.update", handler);
|
|
217
|
+
resolve();
|
|
218
|
+
}
|
|
219
|
+
if (update.connection === "close") {
|
|
220
|
+
evWithOff.off?.("connection.update", handler);
|
|
221
|
+
reject(update.lastDisconnect ?? /* @__PURE__ */ new Error("Connection closed"));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
sock.ev.on("connection.update", handler);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function newConnectionId() {
|
|
228
|
+
return randomUUID();
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region extensions/whatsapp/src/connection-controller.ts
|
|
232
|
+
const LOGGED_OUT_STATUS = DisconnectReason?.loggedOut ?? 401;
|
|
233
|
+
const WHATSAPP_LOGIN_RESTART_MESSAGE = "WhatsApp asked for a restart after pairing (code 515); waiting for creds to save…";
|
|
234
|
+
const WHATSAPP_LOGGED_OUT_RELINK_MESSAGE = "WhatsApp reported the session is logged out. Cleared cached web session; please rerun openclaw channels login and scan the QR again.";
|
|
235
|
+
const WHATSAPP_LOGGED_OUT_QR_MESSAGE = "WhatsApp reported the session is logged out. Cleared cached web session; please scan a new QR.";
|
|
236
|
+
function createNeverResolvePromise() {
|
|
237
|
+
return new Promise(() => {});
|
|
238
|
+
}
|
|
239
|
+
function createLiveConnection(params) {
|
|
240
|
+
let closeResolved = false;
|
|
241
|
+
let resolveClosePromise = (_reason) => {};
|
|
242
|
+
const closePromise = new Promise((resolve) => {
|
|
243
|
+
resolveClosePromise = (reason) => {
|
|
244
|
+
if (closeResolved) return;
|
|
245
|
+
closeResolved = true;
|
|
246
|
+
resolve(reason);
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
connectionId: params.connectionId,
|
|
251
|
+
startedAt: Date.now(),
|
|
252
|
+
sock: params.sock,
|
|
253
|
+
listener: params.listener,
|
|
254
|
+
heartbeat: null,
|
|
255
|
+
watchdogTimer: null,
|
|
256
|
+
lastInboundAt: null,
|
|
257
|
+
lastTransportActivityAt: Date.now(),
|
|
258
|
+
handledMessages: 0,
|
|
259
|
+
unregisterUnhandled: null,
|
|
260
|
+
unregisterTransportActivity: null,
|
|
261
|
+
openedAfterRecentInbound: params.openedAfterRecentInbound,
|
|
262
|
+
backgroundTasks: /* @__PURE__ */ new Set(),
|
|
263
|
+
closePromise,
|
|
264
|
+
resolveClose: resolveClosePromise
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function closeWaSocket(sock) {
|
|
268
|
+
try {
|
|
269
|
+
if (typeof sock?.end === "function") {
|
|
270
|
+
sock.end(/* @__PURE__ */ new Error("OpenClaw WhatsApp socket close"));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
sock?.ws?.close?.();
|
|
274
|
+
} catch {}
|
|
275
|
+
}
|
|
276
|
+
function closeWaSocketSoon(sock, delayMs = 500) {
|
|
277
|
+
setTimeout(() => {
|
|
278
|
+
closeWaSocket(sock);
|
|
279
|
+
}, delayMs);
|
|
280
|
+
}
|
|
281
|
+
async function waitForWhatsAppLoginResult(params) {
|
|
282
|
+
const wait = params.waitForConnection ?? waitForWaConnection;
|
|
283
|
+
const createSocket = params.createSocket ?? createWaSocket;
|
|
284
|
+
let currentSock = params.sock;
|
|
285
|
+
let restarted = false;
|
|
286
|
+
while (true) try {
|
|
287
|
+
await wait(currentSock);
|
|
288
|
+
return {
|
|
289
|
+
outcome: "connected",
|
|
290
|
+
restarted,
|
|
291
|
+
sock: currentSock
|
|
292
|
+
};
|
|
293
|
+
} catch (err) {
|
|
294
|
+
const statusCode = getStatusCode(err);
|
|
295
|
+
if (statusCode === 515 && !restarted) {
|
|
296
|
+
restarted = true;
|
|
297
|
+
params.runtime.log(info(WHATSAPP_LOGIN_RESTART_MESSAGE));
|
|
298
|
+
closeWaSocket(currentSock);
|
|
299
|
+
try {
|
|
300
|
+
currentSock = await createSocket(false, params.verbose, {
|
|
301
|
+
authDir: params.authDir,
|
|
302
|
+
...params.socketTiming,
|
|
303
|
+
onQr: params.onQr
|
|
304
|
+
});
|
|
305
|
+
params.onSocketReplaced?.(currentSock);
|
|
306
|
+
continue;
|
|
307
|
+
} catch (createErr) {
|
|
308
|
+
return {
|
|
309
|
+
outcome: "failed",
|
|
310
|
+
message: formatError(createErr),
|
|
311
|
+
statusCode: getStatusCode(createErr),
|
|
312
|
+
error: createErr
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (statusCode === LOGGED_OUT_STATUS) {
|
|
317
|
+
await logoutWeb({
|
|
318
|
+
authDir: params.authDir,
|
|
319
|
+
isLegacyAuthDir: params.isLegacyAuthDir,
|
|
320
|
+
runtime: params.runtime
|
|
321
|
+
});
|
|
322
|
+
return {
|
|
323
|
+
outcome: "logged-out",
|
|
324
|
+
message: WHATSAPP_LOGGED_OUT_RELINK_MESSAGE,
|
|
325
|
+
statusCode: LOGGED_OUT_STATUS,
|
|
326
|
+
error: err
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
outcome: "failed",
|
|
331
|
+
message: formatError(err),
|
|
332
|
+
statusCode,
|
|
333
|
+
error: err
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
var WhatsAppConnectionController = class {
|
|
338
|
+
constructor(params) {
|
|
339
|
+
this.disconnectRetryController = new AbortController();
|
|
340
|
+
this.current = null;
|
|
341
|
+
this.reconnectAttempts = 0;
|
|
342
|
+
this.lastHandledInboundAt = null;
|
|
343
|
+
this.accountId = params.accountId;
|
|
344
|
+
this.authDir = params.authDir;
|
|
345
|
+
this.verbose = params.verbose;
|
|
346
|
+
this.keepAlive = params.keepAlive;
|
|
347
|
+
this.heartbeatSeconds = params.heartbeatSeconds;
|
|
348
|
+
this.transportTimeoutMs = params.transportTimeoutMs;
|
|
349
|
+
this.messageTimeoutMs = params.messageTimeoutMs;
|
|
350
|
+
this.appSilenceTimeoutMs = Math.max(params.messageTimeoutMs, params.messageTimeoutMs * 4);
|
|
351
|
+
this.watchdogCheckMs = params.watchdogCheckMs;
|
|
352
|
+
this.reconnectPolicy = params.reconnectPolicy;
|
|
353
|
+
this.abortSignal = params.abortSignal;
|
|
354
|
+
this.sleep = params.sleep ?? ((ms, signal) => sleepWithAbort(ms, signal));
|
|
355
|
+
this.isNonRetryableStatus = params.isNonRetryableStatus ?? (() => false);
|
|
356
|
+
this.socketTiming = params.socketTiming ?? {};
|
|
357
|
+
this.socketRef = { current: null };
|
|
358
|
+
this.abortPromise = params.abortSignal && new Promise((resolve) => {
|
|
359
|
+
params.abortSignal?.addEventListener("abort", () => resolve("aborted"), { once: true });
|
|
360
|
+
});
|
|
361
|
+
if (params.abortSignal?.aborted) this.stopDisconnectRetries();
|
|
362
|
+
else params.abortSignal?.addEventListener("abort", () => this.stopDisconnectRetries(), { once: true });
|
|
363
|
+
}
|
|
364
|
+
getActiveListener() {
|
|
365
|
+
return this.current?.listener ?? null;
|
|
366
|
+
}
|
|
367
|
+
getReconnectAttempts() {
|
|
368
|
+
return this.reconnectAttempts;
|
|
369
|
+
}
|
|
370
|
+
isStopRequested() {
|
|
371
|
+
return this.abortSignal?.aborted === true;
|
|
372
|
+
}
|
|
373
|
+
shouldRetryDisconnect() {
|
|
374
|
+
return this.keepAlive && !this.isStopRequested() && !this.disconnectRetryController.signal.aborted;
|
|
375
|
+
}
|
|
376
|
+
getDisconnectRetryAbortSignal() {
|
|
377
|
+
return this.disconnectRetryController.signal;
|
|
378
|
+
}
|
|
379
|
+
noteInbound(timestamp = Date.now()) {
|
|
380
|
+
if (!this.current) return;
|
|
381
|
+
this.current.handledMessages += 1;
|
|
382
|
+
this.current.lastInboundAt = timestamp;
|
|
383
|
+
this.current.lastTransportActivityAt = timestamp;
|
|
384
|
+
this.current.openedAfterRecentInbound = false;
|
|
385
|
+
this.lastHandledInboundAt = timestamp;
|
|
386
|
+
}
|
|
387
|
+
noteTransportActivity(timestamp = Date.now()) {
|
|
388
|
+
if (!this.current) return;
|
|
389
|
+
this.current.lastTransportActivityAt = timestamp;
|
|
390
|
+
}
|
|
391
|
+
getCurrentSnapshot(connection = this.current) {
|
|
392
|
+
if (!connection) return null;
|
|
393
|
+
return {
|
|
394
|
+
connectionId: connection.connectionId,
|
|
395
|
+
startedAt: connection.startedAt,
|
|
396
|
+
lastInboundAt: connection.lastInboundAt,
|
|
397
|
+
lastTransportActivityAt: connection.lastTransportActivityAt,
|
|
398
|
+
handledMessages: connection.handledMessages,
|
|
399
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
400
|
+
uptimeMs: Date.now() - connection.startedAt
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
setUnhandledRejectionCleanup(unregister) {
|
|
404
|
+
if (!this.current) {
|
|
405
|
+
unregister?.();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
this.current.unregisterUnhandled?.();
|
|
409
|
+
this.current.unregisterUnhandled = unregister;
|
|
410
|
+
}
|
|
411
|
+
async openConnection(params) {
|
|
412
|
+
if (this.current) await this.closeCurrentConnection();
|
|
413
|
+
let sock = null;
|
|
414
|
+
let connection = null;
|
|
415
|
+
try {
|
|
416
|
+
sock = await createWaSocket(false, this.verbose, {
|
|
417
|
+
authDir: this.authDir,
|
|
418
|
+
...this.socketTiming
|
|
419
|
+
});
|
|
420
|
+
await waitForWaConnection(sock);
|
|
421
|
+
this.socketRef.current = sock;
|
|
422
|
+
connection = createLiveConnection({
|
|
423
|
+
connectionId: params.connectionId,
|
|
424
|
+
sock,
|
|
425
|
+
listener: {},
|
|
426
|
+
openedAfterRecentInbound: this.isOpeningAfterRecentInbound()
|
|
427
|
+
});
|
|
428
|
+
const listener = await params.createListener({
|
|
429
|
+
sock,
|
|
430
|
+
connection
|
|
431
|
+
});
|
|
432
|
+
connection.listener = listener;
|
|
433
|
+
this.current = connection;
|
|
434
|
+
connection.unregisterTransportActivity = this.attachTransportActivityListener(sock);
|
|
435
|
+
registerWhatsAppConnectionController(this.accountId, this);
|
|
436
|
+
this.startTimers(connection, {
|
|
437
|
+
onHeartbeat: params.onHeartbeat,
|
|
438
|
+
onWatchdogTimeout: params.onWatchdogTimeout
|
|
439
|
+
});
|
|
440
|
+
return connection;
|
|
441
|
+
} catch (err) {
|
|
442
|
+
if (this.socketRef.current === sock) this.socketRef.current = null;
|
|
443
|
+
closeWaSocket(sock);
|
|
444
|
+
if (connection?.unregisterUnhandled) connection.unregisterUnhandled();
|
|
445
|
+
connection?.unregisterTransportActivity?.();
|
|
446
|
+
throw err;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async waitForClose() {
|
|
450
|
+
const connection = this.current;
|
|
451
|
+
if (!connection) return "aborted";
|
|
452
|
+
const listenerClose = connection.listener.onClose?.catch((err) => ({
|
|
453
|
+
status: 500,
|
|
454
|
+
isLoggedOut: false,
|
|
455
|
+
error: err
|
|
456
|
+
})) ?? createNeverResolvePromise();
|
|
457
|
+
return await Promise.race([
|
|
458
|
+
connection.closePromise,
|
|
459
|
+
listenerClose,
|
|
460
|
+
this.abortPromise ?? createNeverResolvePromise()
|
|
461
|
+
]);
|
|
462
|
+
}
|
|
463
|
+
normalizeCloseReason(reason) {
|
|
464
|
+
const statusCode = (typeof reason === "object" && reason && "status" in reason ? reason.status : void 0) ?? void 0;
|
|
465
|
+
return {
|
|
466
|
+
statusCode,
|
|
467
|
+
statusLabel: typeof statusCode === "number" ? statusCode : "unknown",
|
|
468
|
+
isLoggedOut: typeof reason === "object" && reason !== null && "isLoggedOut" in reason && reason.isLoggedOut === true,
|
|
469
|
+
error: reason?.error,
|
|
470
|
+
errorText: formatError(reason)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
resolveCloseDecision(reason) {
|
|
474
|
+
if (reason === "aborted" || this.isStopRequested()) return "aborted";
|
|
475
|
+
const current = this.current;
|
|
476
|
+
if (current && Date.now() - current.startedAt > this.heartbeatSeconds * 1e3) this.reconnectAttempts = 0;
|
|
477
|
+
const normalized = this.normalizeCloseReason(reason);
|
|
478
|
+
if (normalized.isLoggedOut) return {
|
|
479
|
+
action: "stop",
|
|
480
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
481
|
+
healthState: "logged-out",
|
|
482
|
+
normalized
|
|
483
|
+
};
|
|
484
|
+
if (this.isNonRetryableStatus(normalized.statusCode)) return {
|
|
485
|
+
action: "stop",
|
|
486
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
487
|
+
healthState: "conflict",
|
|
488
|
+
normalized
|
|
489
|
+
};
|
|
490
|
+
const retryDecision = this.consumeReconnectAttempt();
|
|
491
|
+
if (retryDecision.action === "stop") return {
|
|
492
|
+
action: "stop",
|
|
493
|
+
reconnectAttempts: retryDecision.reconnectAttempts,
|
|
494
|
+
healthState: retryDecision.healthState,
|
|
495
|
+
normalized
|
|
496
|
+
};
|
|
497
|
+
return {
|
|
498
|
+
action: "retry",
|
|
499
|
+
delayMs: retryDecision.delayMs,
|
|
500
|
+
reconnectAttempts: retryDecision.reconnectAttempts,
|
|
501
|
+
healthState: retryDecision.healthState,
|
|
502
|
+
normalized
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
consumeReconnectAttempt() {
|
|
506
|
+
this.reconnectAttempts += 1;
|
|
507
|
+
if (this.reconnectPolicy.maxAttempts > 0 && this.reconnectAttempts >= this.reconnectPolicy.maxAttempts) return {
|
|
508
|
+
action: "stop",
|
|
509
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
510
|
+
healthState: "stopped"
|
|
511
|
+
};
|
|
512
|
+
return {
|
|
513
|
+
action: "retry",
|
|
514
|
+
delayMs: computeBackoff(this.reconnectPolicy, this.reconnectAttempts),
|
|
515
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
516
|
+
healthState: "reconnecting"
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
forceClose(reason) {
|
|
520
|
+
const connection = this.current;
|
|
521
|
+
if (!connection) return;
|
|
522
|
+
connection.resolveClose(reason);
|
|
523
|
+
connection.listener.signalClose?.(reason);
|
|
524
|
+
}
|
|
525
|
+
async closeCurrentConnection() {
|
|
526
|
+
const connection = this.current;
|
|
527
|
+
if (!connection) return;
|
|
528
|
+
this.current = null;
|
|
529
|
+
if (this.socketRef.current === connection.sock) this.socketRef.current = null;
|
|
530
|
+
connection.unregisterUnhandled?.();
|
|
531
|
+
connection.unregisterTransportActivity?.();
|
|
532
|
+
if (connection.heartbeat) clearInterval(connection.heartbeat);
|
|
533
|
+
if (connection.watchdogTimer) clearInterval(connection.watchdogTimer);
|
|
534
|
+
if (connection.backgroundTasks.size > 0) {
|
|
535
|
+
await Promise.allSettled(connection.backgroundTasks);
|
|
536
|
+
connection.backgroundTasks.clear();
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
await connection.listener.close?.();
|
|
540
|
+
} catch {}
|
|
541
|
+
closeWaSocket(connection.sock);
|
|
542
|
+
}
|
|
543
|
+
async waitBeforeRetry(delayMs) {
|
|
544
|
+
await this.sleep(delayMs, this.abortSignal);
|
|
545
|
+
}
|
|
546
|
+
async shutdown() {
|
|
547
|
+
this.stopDisconnectRetries();
|
|
548
|
+
await this.closeCurrentConnection();
|
|
549
|
+
unregisterWhatsAppConnectionController(this.accountId, this);
|
|
550
|
+
}
|
|
551
|
+
startTimers(connection, hooks) {
|
|
552
|
+
if (!this.keepAlive) return;
|
|
553
|
+
connection.heartbeat = setInterval(() => {
|
|
554
|
+
const snapshot = this.getCurrentSnapshot(connection);
|
|
555
|
+
if (!snapshot) return;
|
|
556
|
+
hooks.onHeartbeat?.(snapshot);
|
|
557
|
+
}, this.heartbeatSeconds * 1e3);
|
|
558
|
+
connection.watchdogTimer = setInterval(() => {
|
|
559
|
+
const now = Date.now();
|
|
560
|
+
const transportStaleForMs = now - connection.lastTransportActivityAt;
|
|
561
|
+
const appSilentForMs = now - (connection.lastInboundAt ?? connection.startedAt);
|
|
562
|
+
const appSilenceTimeoutMs = connection.openedAfterRecentInbound ? this.messageTimeoutMs : this.appSilenceTimeoutMs;
|
|
563
|
+
if (transportStaleForMs <= this.transportTimeoutMs && appSilentForMs <= appSilenceTimeoutMs) return;
|
|
564
|
+
const snapshot = this.getCurrentSnapshot(connection);
|
|
565
|
+
if (!snapshot) return;
|
|
566
|
+
hooks.onWatchdogTimeout?.(snapshot);
|
|
567
|
+
this.forceClose({
|
|
568
|
+
status: 499,
|
|
569
|
+
isLoggedOut: false,
|
|
570
|
+
error: "watchdog-timeout"
|
|
571
|
+
});
|
|
572
|
+
}, this.watchdogCheckMs);
|
|
573
|
+
}
|
|
574
|
+
attachTransportActivityListener(sock) {
|
|
575
|
+
const ws = sock.ws;
|
|
576
|
+
if (!ws || typeof ws.on !== "function") return null;
|
|
577
|
+
const noteActivity = () => this.noteTransportActivity();
|
|
578
|
+
ws.on("frame", noteActivity);
|
|
579
|
+
return () => {
|
|
580
|
+
if (typeof ws.off === "function") {
|
|
581
|
+
ws.off("frame", noteActivity);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
ws.removeListener?.("frame", noteActivity);
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
isOpeningAfterRecentInbound() {
|
|
588
|
+
if (this.reconnectAttempts <= 0 || this.lastHandledInboundAt === null) return false;
|
|
589
|
+
return Date.now() - this.lastHandledInboundAt <= this.appSilenceTimeoutMs;
|
|
590
|
+
}
|
|
591
|
+
stopDisconnectRetries() {
|
|
592
|
+
if (!this.disconnectRetryController.signal.aborted) this.disconnectRetryController.abort();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
//#endregion
|
|
596
|
+
export { waitForWhatsAppLoginResult as a, waitForWaConnection as c, computeBackoff as d, newConnectionId$1 as f, sleepWithAbort as h, closeWaSocketSoon as i, resolveWhatsAppSocketTiming as l, resolveReconnectPolicy as m, WhatsAppConnectionController as n, createWaSocket as o, resolveHeartbeatSeconds as p, closeWaSocket as r, newConnectionId as s, WHATSAPP_LOGGED_OUT_QR_MESSAGE as t, DEFAULT_RECONNECT_POLICY as u };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region extensions/whatsapp/src/connection-controller-registry.ts
|
|
2
|
+
const CONNECTION_REGISTRY_KEY = Symbol.for("openclaw.whatsapp.connectionControllerRegistry");
|
|
3
|
+
function getConnectionRegistryState() {
|
|
4
|
+
const globalState = globalThis;
|
|
5
|
+
const existing = globalState[CONNECTION_REGISTRY_KEY];
|
|
6
|
+
if (existing) return existing;
|
|
7
|
+
const created = { controllers: /* @__PURE__ */ new Map() };
|
|
8
|
+
globalState[CONNECTION_REGISTRY_KEY] = created;
|
|
9
|
+
return created;
|
|
10
|
+
}
|
|
11
|
+
function getRegisteredWhatsAppConnectionController(accountId) {
|
|
12
|
+
return getConnectionRegistryState().controllers.get(accountId) ?? null;
|
|
13
|
+
}
|
|
14
|
+
function registerWhatsAppConnectionController(accountId, controller) {
|
|
15
|
+
getConnectionRegistryState().controllers.set(accountId, controller);
|
|
16
|
+
}
|
|
17
|
+
function unregisterWhatsAppConnectionController(accountId, controller) {
|
|
18
|
+
const controllers = getConnectionRegistryState().controllers;
|
|
19
|
+
if (controllers.get(accountId) === controller) controllers.delete(accountId);
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { registerWhatsAppConnectionController as n, unregisterWhatsAppConnectionController as r, getRegisteredWhatsAppConnectionController as t };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { s as normalizeWhatsAppTarget$1, t as isWhatsAppGroupJid$1 } from "./normalize-target-BPVHNsM5.js";
|
|
2
|
+
import { t as whatsappCommandPolicy$1 } from "./command-policy-O8sRnqUV.js";
|
|
3
|
+
import { t as resolveLegacyGroupSessionKey$1 } from "./group-session-contract-B8QBn0I2.js";
|
|
4
|
+
import { n as unsupportedSecretRefSurfacePatterns, t as collectUnsupportedSecretRefConfigCandidates } from "./security-contract-DqH8JDX6.js";
|
|
5
|
+
import { r as isLegacyGroupSessionKey$1, t as canonicalizeLegacySessionKey$1 } from "./session-contract-B0efQf9P.js";
|
|
6
|
+
import { n as listWhatsAppDirectoryGroupsFromConfig, r as listWhatsAppDirectoryPeersFromConfig } from "./directory-config-DVs4ffXu.js";
|
|
7
|
+
import { t as resolveWhatsAppRuntimeGroupPolicy$1 } from "./runtime-group-policy-Gp1lQ0at.js";
|
|
8
|
+
import { t as __testing } from "./access-control-BQ3tCkH8.js";
|
|
9
|
+
//#region extensions/whatsapp/contract-api.ts
|
|
10
|
+
const canonicalizeLegacySessionKey = canonicalizeLegacySessionKey$1;
|
|
11
|
+
const isLegacyGroupSessionKey = isLegacyGroupSessionKey$1;
|
|
12
|
+
const isWhatsAppGroupJid = isWhatsAppGroupJid$1;
|
|
13
|
+
const normalizeWhatsAppTarget = normalizeWhatsAppTarget$1;
|
|
14
|
+
const resolveLegacyGroupSessionKey = resolveLegacyGroupSessionKey$1;
|
|
15
|
+
const resolveWhatsAppRuntimeGroupPolicy = resolveWhatsAppRuntimeGroupPolicy$1;
|
|
16
|
+
const whatsappAccessControlTesting = __testing;
|
|
17
|
+
const whatsappCommandPolicy = whatsappCommandPolicy$1;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { canonicalizeLegacySessionKey, collectUnsupportedSecretRefConfigCandidates, isLegacyGroupSessionKey, isWhatsAppGroupJid, listWhatsAppDirectoryGroupsFromConfig, listWhatsAppDirectoryPeersFromConfig, normalizeWhatsAppTarget, resolveLegacyGroupSessionKey, resolveWhatsAppRuntimeGroupPolicy, unsupportedSecretRefSurfacePatterns, whatsappAccessControlTesting, whatsappCommandPolicy };
|