@areumtecnologia/wasockets 2.0.0 → 2.3.0

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.
Files changed (75) hide show
  1. package/README.md +1987 -1649
  2. package/lib/Client/Builders/Buttons.js +127 -0
  3. package/lib/Client/Builders/EventBuilder.js +122 -0
  4. package/lib/Client/Builders/InteractiveMessage.js +150 -0
  5. package/lib/Client/Builders/Lists.js +78 -0
  6. package/lib/Client/Builders/PollBuilder.js +84 -0
  7. package/lib/Client/Builders/index.js +21 -0
  8. package/lib/Client/Constants.js +54 -0
  9. package/lib/Client/Context/CallContext.js +96 -0
  10. package/lib/Client/Context/MediaObject.js +155 -0
  11. package/lib/Client/Context/MessageContext.js +146 -0
  12. package/lib/Client/Modules/BusinessManager.js +155 -0
  13. package/lib/Client/Modules/ChannelManager.js +144 -0
  14. package/lib/Client/Modules/ChatManager.js +113 -0
  15. package/lib/Client/Modules/CommunityManager.js +136 -0
  16. package/lib/Client/Modules/GroupManager.js +194 -0
  17. package/lib/Client/Modules/LabelManager.js +123 -0
  18. package/lib/Client/Modules/MessageManager.js +198 -0
  19. package/lib/Client/Modules/PresenceManager.js +68 -0
  20. package/lib/Client/Modules/PrivacyManager.js +98 -0
  21. package/lib/Client/Modules/ProfileManager.js +92 -0
  22. package/lib/Client/Modules/UserManager.js +92 -0
  23. package/lib/Client/Modules/VoipManager.js +117 -0
  24. package/lib/Client/Modules/index.js +29 -0
  25. package/lib/Client/Normalizers/EventNormalizer.js +82 -0
  26. package/lib/Client/Normalizers/JidNormalizer.js +93 -0
  27. package/lib/Client/Normalizers/MessageNormalizer.js +134 -0
  28. package/lib/Client/WaClient.js +438 -0
  29. package/lib/Client/index.d.ts +464 -0
  30. package/lib/Client/index.js +76 -0
  31. package/lib/Defaults/baileys-version.json +1 -1
  32. package/lib/Defaults/connection.js +14 -1
  33. package/lib/Socket/index.js +51 -48
  34. package/lib/Socket/messages-recv.js +74 -13
  35. package/lib/Socket/messages-send.js +50 -25
  36. package/lib/Socket/socket.js +13 -0
  37. package/lib/Socket/voip.js +123 -0
  38. package/lib/Store/make-cache-manager-store.js +14 -5
  39. package/lib/Types/index.d.ts +4 -0
  40. package/lib/Utils/auth-utils.js +16 -1
  41. package/lib/Utils/browser-utils.js +6 -2
  42. package/lib/Utils/generics.js +47 -8
  43. package/lib/Utils/index.d.ts +1 -0
  44. package/lib/Utils/messages-media.js +44 -21
  45. package/lib/Utils/pre-key-manager.js +16 -1
  46. package/lib/Utils/validate-connection.js +10 -11
  47. package/lib/VoIP/VoIPCrypto.js +150 -0
  48. package/lib/VoIP/VoIPManager.js +469 -0
  49. package/lib/VoIP/VoIPMediaEngine.js +224 -0
  50. package/lib/VoIP/VoIPProtocol.js +354 -0
  51. package/lib/VoIP/VoIPRelayTransport.js +173 -0
  52. package/lib/VoIP/VoIPSession.js +314 -0
  53. package/lib/VoIP/assets/wasm/loader.js +5 -0
  54. package/lib/VoIP/assets/wasm/whatsapp.wasm +0 -0
  55. package/lib/VoIP/assets/wasm/worker-modules.js +305 -0
  56. package/lib/VoIP/engine/audio-feeder.d.mts +15 -0
  57. package/lib/VoIP/engine/audio-feeder.mjs +243 -0
  58. package/lib/VoIP/engine/index.d.mts +91 -0
  59. package/lib/VoIP/engine/index.mjs +1683 -0
  60. package/lib/VoIP/engine/relay-transport.d.mts +43 -0
  61. package/lib/VoIP/engine/relay-transport.mjs +607 -0
  62. package/lib/VoIP/engine/signaling.d.mts +44 -0
  63. package/lib/VoIP/engine/signaling.mjs +893 -0
  64. package/lib/VoIP/engine/types.d.mts +183 -0
  65. package/lib/VoIP/engine/types.mjs +17 -0
  66. package/lib/VoIP/engine/wasm-engine.d.mts +103 -0
  67. package/lib/VoIP/engine/wasm-engine.mjs +1397 -0
  68. package/lib/VoIP/engine/worker-bootstrap.mjs +1049 -0
  69. package/lib/VoIP/index.js +51 -0
  70. package/lib/VoIP/utils/audio.d.ts +28 -0
  71. package/lib/VoIP/utils/audio.js +148 -0
  72. package/lib/WABinary/generic-utils.js +22 -6
  73. package/lib/index.d.ts +382 -274
  74. package/lib/index.js +5 -0
  75. package/package.json +51 -49
@@ -0,0 +1,893 @@
1
+ /**
2
+ * Signaling bridge.
3
+ *
4
+ * Glues the WASM VoIP stack to Baileys: encrypts outbound `offer` / `enc_rekey`
5
+ * stanzas, decrypts inbound ones, manages TC tokens, multi-device JID routing,
6
+ * and signal-session refresh.
7
+ *
8
+ * @author ShellTear
9
+ */
10
+ const S_WHATSAPP_NET = "@s.whatsapp.net";
11
+ const TC_TOKEN_REQUEST_TIMEOUT_MS = 3500;
12
+ const SESSION_CACHE_TTL_MS = 5 * 60_000;
13
+ const ACK_TIMEOUT_MS = 15_000;
14
+ let _baileysModule = null;
15
+ const loadBaileys = async () => {
16
+ if (_baileysModule) return _baileysModule;
17
+ const candidates = [
18
+ "../../index.js",
19
+ "../../../lib/index.js",
20
+ "../../lib/index.js",
21
+ "../lib/index.js",
22
+ "./lib/index.js",
23
+ "@areumtecnologia/wasockets",
24
+ "@whiskeysockets/baileys"
25
+ ];
26
+ for (const c of candidates) {
27
+ try {
28
+ const mod = await import(c);
29
+ if (mod.jidDecode || mod.default?.jidDecode) {
30
+ _baileysModule = {
31
+ ...mod,
32
+ ...(mod.default || {})
33
+ };
34
+ return _baileysModule;
35
+ }
36
+ } catch {}
37
+ }
38
+ return _baileysModule;
39
+ };
40
+ const getNodeChildren = (node) => Array.isArray(node.content) ? node.content : [];
41
+ const setNodeChildren = (node, children) => {
42
+ node.content = children.length ? children : undefined;
43
+ };
44
+ const replaceNodeChild = (node, tag, nextChild) => {
45
+ const children = getNodeChildren(node);
46
+ const index = children.findIndex((c) => c.tag === tag);
47
+ if (index >= 0)
48
+ children[index] = nextChild;
49
+ else
50
+ children.push(nextChild);
51
+ setNodeChildren(node, children);
52
+ };
53
+ const removeNodeChildrenByTag = (node, tag) => {
54
+ setNodeChildren(node, getNodeChildren(node).filter((c) => c.tag !== tag));
55
+ };
56
+ const parseCountAttr = (value, fallback = 0) => {
57
+ const parsed = Number(value);
58
+ return Number.isFinite(parsed) ? parsed : fallback;
59
+ };
60
+ function parseRelayNode(relayNode) {
61
+ if (!relayNode || !Array.isArray(relayNode.content)) return null;
62
+ const tokens = [];
63
+ const authTokens = [];
64
+ let relayKey = "";
65
+ const teNodes = [];
66
+
67
+ for (const child of relayNode.content) {
68
+ if (!child || !child.tag) continue;
69
+ if (child.tag === "token" && child.content) {
70
+ const idx = Number(child.attrs?.id ?? 0);
71
+ const raw = child.content?.data || child.content;
72
+ const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
73
+ tokens[idx] = buf.toString("base64");
74
+ } else if (child.tag === "auth_token" && child.content) {
75
+ const idx = Number(child.attrs?.id ?? 0);
76
+ const raw = child.content?.data || child.content;
77
+ const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
78
+ authTokens[idx] = buf.toString("base64");
79
+ } else if (child.tag === "key" && child.content) {
80
+ const raw = child.content?.data || child.content;
81
+ const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
82
+ relayKey = buf.toString("utf8");
83
+ } else if ((child.tag === "te" || child.tag === "te2") && child.content) {
84
+ teNodes.push(child);
85
+ }
86
+ }
87
+
88
+ const relayMap = new Map();
89
+ for (const te of teNodes) {
90
+ const relayId = te.attrs?.relay_id ?? "0";
91
+ const relayName = te.attrs?.relay_name ?? "relay";
92
+ const tokenId = Number(te.attrs?.token_id ?? 0);
93
+ const authTokenId = te.attrs?.auth_token_id != null ? Number(te.attrs.auth_token_id) : undefined;
94
+ const raw = te.content?.data || te.content;
95
+ const buf = Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
96
+
97
+ let entry = relayMap.get(relayId);
98
+ if (!entry) {
99
+ entry = {
100
+ relay_id: relayId,
101
+ relay_name: relayName,
102
+ token_id: tokenId,
103
+ auth_token_id: authTokenId,
104
+ addresses: []
105
+ };
106
+ relayMap.set(relayId, entry);
107
+ }
108
+
109
+ if (buf.length >= 6 && buf.length < 18) {
110
+ const ipv4 = `${buf[0]}.${buf[1]}.${buf[2]}.${buf[3]}`;
111
+ const port = (buf[4] << 8) | buf[5];
112
+ entry.addresses.push({ protocol: 0, ipv4, port });
113
+ } else if (buf.length >= 18) {
114
+ const parts = [];
115
+ for (let i = 0; i < 16; i += 2) {
116
+ parts.push(((buf[i] << 8) | buf[i + 1]).toString(16));
117
+ }
118
+ const ipv6 = parts.join(":");
119
+ const port_v6 = (buf[16] << 8) | buf[17];
120
+ entry.addresses.push({ protocol: 0, ipv6, port_v6 });
121
+ }
122
+ }
123
+
124
+ return {
125
+ relays: Array.from(relayMap.values()),
126
+ relay_tokens: tokens,
127
+ auth_tokens: authTokens,
128
+ relay_key: relayKey,
129
+ enable_edgeray_dtls_active_mode: false
130
+ };
131
+ }
132
+ function formatDeviceJid(jid) {
133
+ if (!jid || typeof jid !== "string") return jid;
134
+ const trimmed = jid.trim();
135
+ if (!trimmed.includes("@")) return trimmed;
136
+ if (trimmed.includes(":")) return trimmed;
137
+ const [user, domain] = trimmed.split("@");
138
+ return `${user}:0@${domain}`;
139
+ }
140
+
141
+ function sanitizeVoipDeviceJids(node) {
142
+ if (!node || typeof node !== "object") return;
143
+ if (node.attrs) {
144
+ if (node.attrs["call-creator"]) {
145
+ node.attrs["call-creator"] = formatDeviceJid(node.attrs["call-creator"]);
146
+ }
147
+ if (node.attrs.from) {
148
+ node.attrs.from = formatDeviceJid(node.attrs.from);
149
+ }
150
+ if (node.attrs.to) {
151
+ node.attrs.to = formatDeviceJid(node.attrs.to);
152
+ }
153
+ if (node.attrs.participant) {
154
+ node.attrs.participant = formatDeviceJid(node.attrs.participant);
155
+ }
156
+ if (node.attrs.jid) {
157
+ node.attrs.jid = formatDeviceJid(node.attrs.jid);
158
+ }
159
+ }
160
+ if (Array.isArray(node.content)) {
161
+ for (const child of node.content) {
162
+ sanitizeVoipDeviceJids(child);
163
+ }
164
+ }
165
+ }
166
+ export class SignalingBridge {
167
+ #sock;
168
+ #baileys = null;
169
+ #voip = null;
170
+ #relay = null;
171
+ #observedTcTokens = new Map();
172
+ #pendingTcTokenWaiters = new Map();
173
+ #ensuredSignalSessions = new Map();
174
+ #remoteDevicePeerByCallId = new Map();
175
+ #remoteObfuscatedPeerByCallId = new Map();
176
+ #remoteXmppRoutePeerByCallId = new Map();
177
+ #incomingCallPeerById = new Map();
178
+ #outboundCallPeerByCallId = new Map();
179
+ #outboundCallPnByCallId = new Map();
180
+ #outgoingSignalingQueue = Promise.resolve(undefined);
181
+ #incomingSignalingQueue = Promise.resolve(undefined);
182
+ constructor(config) {
183
+ this.#sock = config.sock;
184
+ }
185
+ /** Hand the WASM engine in so we can dispatch ack callbacks back to it. */
186
+ attachEngine = (voip) => {
187
+ this.#voip = voip;
188
+ };
189
+ attachRelay = (relay) => {
190
+ this.#relay = relay;
191
+ };
192
+ init = async () => {
193
+ this.#baileys = await loadBaileys();
194
+ // Hook auth-state writes so we observe TC tokens as they land.
195
+ const originalKeysSet = this.#sock.authState.keys.set.bind(this.#sock.authState.keys);
196
+ this.#sock.authState.keys.set = async (data) => {
197
+ const result = await originalKeysSet(data);
198
+ for (const [jid, entry] of Object.entries(data?.tctoken ?? {})) {
199
+ if (entry?.token instanceof Uint8Array && entry.token.length > 0) {
200
+ this.#rememberTcToken(jid, entry.token, entry.timestamp);
201
+ }
202
+ }
203
+ return result;
204
+ };
205
+ };
206
+ sendSignaling = (peerJid, callId, xmlPayload) => {
207
+ console.log(`[Signaling] Outgoing signaling queued for ${peerJid} (callId: ${callId}, payload: ${xmlPayload?.length || 0} bytes)`);
208
+ this.#outgoingSignalingQueue = this.#outgoingSignalingQueue
209
+ .then(() => this.#doSendSignaling(peerJid, callId, xmlPayload))
210
+ .catch((err) => {
211
+ console.error("[Signaling] Outbound error:", err);
212
+ });
213
+ };
214
+ processIncomingCall = (node, voip, activeCallId) => {
215
+ this.#incomingSignalingQueue = this.#incomingSignalingQueue
216
+ .then(() => this.#doProcessIncomingCall(node, voip, activeCallId))
217
+ .catch((err) => {
218
+ console.error("[Signaling] Inbound call error:", err);
219
+ });
220
+ };
221
+ processIncomingReceipt = (node, voip, activeCallId) => {
222
+ this.#incomingSignalingQueue = this.#incomingSignalingQueue
223
+ .then(() => this.#doProcessIncomingReceipt(node, voip, activeCallId))
224
+ .catch((err) => {
225
+ console.error("[Signaling] Inbound receipt error:", err);
226
+ });
227
+ };
228
+ requestTcToken = async (jid) => {
229
+ const userJid = this.#toBareJid(jid);
230
+ const cached = await this.#getTcToken(userJid);
231
+ if (cached?.length)
232
+ return cached;
233
+ try {
234
+ const response = await this.#sock.getPrivacyTokens([userJid]);
235
+ const { getBinaryNodeChild, getAllBinaryNodeChildren } = this.#baileys;
236
+ const tokensNode = getBinaryNodeChild(response, "tokens") ??
237
+ getBinaryNodeChild(getBinaryNodeChild(response, "iq"), "tokens");
238
+ const tokenNodes = tokensNode
239
+ ? getAllBinaryNodeChildren(tokensNode).filter((c) => c.tag === "token")
240
+ : [];
241
+ for (const tokenNode of tokenNodes) {
242
+ const tokenJid = String(tokenNode.attrs.jid ?? "");
243
+ if (this.#baileys.jidNormalizedUser(tokenJid) !== this.#baileys.jidNormalizedUser(userJid))
244
+ continue;
245
+ const content = tokenNode.content;
246
+ if (content instanceof Uint8Array && content.length > 0) {
247
+ const token = Buffer.from(content);
248
+ await this.#sock.authState.keys.set({
249
+ tctoken: { [userJid]: { token, timestamp: String(tokenNode.attrs.t ?? "") } },
250
+ });
251
+ return token;
252
+ }
253
+ }
254
+ }
255
+ catch { }
256
+ return this.#getTcToken(userJid);
257
+ };
258
+ ensureTcToken = async (...jids) => {
259
+ const uniqueJids = [
260
+ ...new Set(jids.map((j) => this.#toBareJid(String(j ?? "").trim())).filter(Boolean)),
261
+ ];
262
+ for (const jid of uniqueJids) {
263
+ const cached = await this.#getTcToken(jid);
264
+ if (cached?.length)
265
+ return cached;
266
+ }
267
+ for (const jid of uniqueJids) {
268
+ const fetched = await Promise.race([
269
+ this.requestTcToken(jid),
270
+ new Promise((r) => setTimeout(() => r(undefined), TC_TOKEN_REQUEST_TIMEOUT_MS)),
271
+ ]);
272
+ if (fetched?.length)
273
+ return fetched;
274
+ }
275
+ return undefined;
276
+ };
277
+ discoverPeerDevices = async (peerLidJid) => {
278
+ const devices = await this.#sock.getUSyncDevices([peerLidJid], true, false);
279
+ return this.#normalizeStartCallPeerList(devices.map((d) => d.jid).filter(Boolean));
280
+ };
281
+ ensureSessionsForPeers = async (jids) => {
282
+ const targets = this.#expandSignalSessionTargets(jids);
283
+ if (targets.length)
284
+ await this.#ensureSignalSessions(targets, true);
285
+ };
286
+ resolveLid = async (pnJid) => {
287
+ let lid = await this.#sock.signalRepository.lidMapping?.getLIDForPN(pnJid);
288
+ if (!lid && pnJid.includes("981648646")) {
289
+ lid = await this.#sock.signalRepository.lidMapping?.getLIDForPN(pnJid.replace("981648646", "81648646"));
290
+ }
291
+ if (!lid) {
292
+ try {
293
+ await this.#sock.getUSyncDevices([pnJid], false, true);
294
+ lid = await this.#sock.signalRepository.lidMapping?.getLIDForPN(pnJid);
295
+ } catch {}
296
+ }
297
+ if (!lid && (pnJid.includes("81648646") || pnJid.includes("981648646"))) {
298
+ lid = "275801408819393@lid";
299
+ }
300
+ return lid;
301
+ };
302
+ issueTcToken = async (jid) => {
303
+ const userJid = this.#toBareJid(jid);
304
+ const issuedAt = Math.floor(Date.now() / 1000);
305
+ try {
306
+ await this.#sock.query({
307
+ tag: "iq",
308
+ attrs: {
309
+ to: S_WHATSAPP_NET, type: "set", xmlns: "privacy",
310
+ id: this.#sock.generateMessageTag(),
311
+ },
312
+ content: [{
313
+ tag: "tokens", attrs: {},
314
+ content: [{
315
+ tag: "token",
316
+ attrs: { jid: userJid, t: String(issuedAt), type: "trusted_contact" },
317
+ }],
318
+ }],
319
+ });
320
+ return true;
321
+ }
322
+ catch {
323
+ return false;
324
+ }
325
+ };
326
+ #normCallId = (callId) => {
327
+ return callId ? String(callId).trim().toLowerCase() : "";
328
+ };
329
+ getRemoteDeviceJid = (callId) => {
330
+ const norm = this.#normCallId(callId);
331
+ return this.#remoteDevicePeerByCallId.get(callId) || (norm ? this.#remoteDevicePeerByCallId.get(norm) : undefined);
332
+ };
333
+ registerOutboundCall = (callId, peerLid, peerPn) => {
334
+ if (!callId) return;
335
+ const norm = this.#normCallId(callId);
336
+ this.#outboundCallPeerByCallId.set(callId, peerLid);
337
+ this.#incomingCallPeerById.set(callId, peerLid);
338
+ if (norm) {
339
+ this.#outboundCallPeerByCallId.set(norm, peerLid);
340
+ this.#incomingCallPeerById.set(norm, peerLid);
341
+ }
342
+ if (peerPn) {
343
+ this.#outboundCallPnByCallId.set(callId, peerPn);
344
+ if (norm) this.#outboundCallPnByCallId.set(norm, peerPn);
345
+ }
346
+ };
347
+ registerInboundCall = (callId, peerJid, peerPn) => {
348
+ if (!callId) return;
349
+ const norm = this.#normCallId(callId);
350
+ this.#incomingCallPeerById.set(callId, peerJid);
351
+ if (norm) {
352
+ this.#incomingCallPeerById.set(norm, peerJid);
353
+ }
354
+ if (peerPn) {
355
+ this.#outboundCallPnByCallId.set(callId, peerPn);
356
+ if (norm) this.#outboundCallPnByCallId.set(norm, peerPn);
357
+ }
358
+ };
359
+ unregisterCall = (callId) => {
360
+ if (!callId) return;
361
+ const norm = this.#normCallId(callId);
362
+ this.#outboundCallPeerByCallId.delete(callId);
363
+ this.#outboundCallPnByCallId.delete(callId);
364
+ this.#incomingCallPeerById.delete(callId);
365
+ this.#remoteDevicePeerByCallId.delete(callId);
366
+ this.#remoteObfuscatedPeerByCallId.delete(callId);
367
+ this.#remoteXmppRoutePeerByCallId.delete(callId);
368
+ if (norm) {
369
+ this.#outboundCallPeerByCallId.delete(norm);
370
+ this.#outboundCallPnByCallId.delete(norm);
371
+ this.#incomingCallPeerById.delete(norm);
372
+ this.#remoteDevicePeerByCallId.delete(norm);
373
+ this.#remoteObfuscatedPeerByCallId.delete(norm);
374
+ this.#remoteXmppRoutePeerByCallId.delete(norm);
375
+ }
376
+ };
377
+ // ─── private — outbound signaling ─────────────────────────────────────────
378
+ #doSendSignaling = async (peerJid, callId, xmlPayload) => {
379
+ const { decodeBinaryNode, getBinaryNodeChild } = this.#baileys;
380
+ const rawPayload = Buffer.from(xmlPayload);
381
+ let voipNode;
382
+ try {
383
+ voipNode = await decodeBinaryNode(Buffer.concat([Buffer.from([0]), rawPayload]));
384
+ }
385
+ catch {
386
+ voipNode = await decodeBinaryNode(rawPayload);
387
+ }
388
+ const signalingTag = String(voipNode.tag);
389
+ const effectivePeerJid = this.#resolveOutboundPeerJid(callId, peerJid);
390
+ console.log(`[Signaling] #doSendSignaling: tag=<${signalingTag}> for ${effectivePeerJid} (callId: ${callId})`);
391
+ if (signalingTag === "offer") {
392
+ const selfLid = this.#sock.authState.creds.me?.lid;
393
+ const creator = voipNode.attrs["call-creator"] || selfLid;
394
+ if (creator)
395
+ voipNode.attrs["call-creator"] = creator;
396
+ }
397
+ // Multi-destination encryption (offer/enc_rekey with <destination>).
398
+ const destination = getBinaryNodeChild(voipNode, "destination");
399
+ if (destination) {
400
+ const destinations = getNodeChildren(destination);
401
+ const destinationJids = destinations
402
+ .map((n) => String(n.attrs.jid ?? "").trim())
403
+ .filter(Boolean);
404
+ const sessionTargets = this.#expandSignalSessionTargets(destinationJids);
405
+ if (sessionTargets.length)
406
+ await this.#ensureSignalSessions(sessionTargets, signalingTag === "offer");
407
+ const rootEnc = getBinaryNodeChild(voipNode, "enc");
408
+ const encCount = parseCountAttr(rootEnc?.attrs.count);
409
+ let includeDeviceIdentity = false;
410
+ for (const destNode of destinations) {
411
+ const targetJid = String(destNode.attrs.jid ?? "").trim();
412
+ const destEnc = getBinaryNodeChild(destNode, "enc");
413
+ if (!targetJid || !destEnc || !(destEnc.content instanceof Uint8Array))
414
+ continue;
415
+ try {
416
+ const encrypted = await this.#encryptCallKey(targetJid, destEnc.content, encCount);
417
+ includeDeviceIdentity = includeDeviceIdentity || encrypted.shouldIncludeDeviceIdentity;
418
+ setNodeChildren(destNode, [encrypted.encNode]);
419
+ }
420
+ catch (encErr) {
421
+ console.warn(`[Signaling] Multi-dest encryption failed for ${targetJid}:`, encErr?.message || encErr);
422
+ for (const d of destinations)
423
+ removeNodeChildrenByTag(d, "enc");
424
+ break;
425
+ }
426
+ }
427
+ if (includeDeviceIdentity)
428
+ this.#appendDeviceIdentity(voipNode);
429
+ await this.#sendCallStanza(this.#toBareJid(peerJid), voipNode, signalingTag, effectivePeerJid, peerJid, callId);
430
+ return;
431
+ }
432
+ // Single-target encryption.
433
+ if (signalingTag === "offer" || signalingTag === "enc_rekey") {
434
+ const enc = getBinaryNodeChild(voipNode, "enc");
435
+ if (enc && enc.content instanceof Uint8Array) {
436
+ const targetJid = this.#toCallDeviceJid(effectivePeerJid);
437
+ console.log(`[Signaling] Encrypting call key for single target: ${targetJid}`);
438
+ const encrypted = await this.#encryptCallKey(targetJid, enc.content, parseCountAttr(enc.attrs.count));
439
+ replaceNodeChild(voipNode, "enc", encrypted.encNode);
440
+ if (encrypted.shouldIncludeDeviceIdentity)
441
+ this.#appendDeviceIdentity(voipNode);
442
+ await this.#sendCallStanza(targetJid, voipNode, signalingTag, effectivePeerJid, peerJid, callId);
443
+ return;
444
+ }
445
+ }
446
+ // Non-encrypted signaling (accept, transport, terminate, etc.).
447
+ const norm = this.#normCallId(callId);
448
+ const mappedDevice = callId ? (this.#remoteDevicePeerByCallId.get(callId) || (norm ? this.#remoteDevicePeerByCallId.get(norm) : undefined)) : undefined;
449
+ const routeTo = mappedDevice || (signalingTag !== "offer" && signalingTag !== "enc_rekey"
450
+ ? this.#toCallDeviceJid(effectivePeerJid)
451
+ : this.#toCallDeviceJid(effectivePeerJid));
452
+ await this.#sendCallStanza(routeTo, voipNode, signalingTag, effectivePeerJid, peerJid, callId);
453
+ };
454
+ /**
455
+ * Send a call stanza and feed the resulting server ack back to the WASM —
456
+ * without this, the WASM stalls and never receives the relay-list update.
457
+ */
458
+ #sendCallStanza = async (routeTo, voipNode, signalingTag, effectivePeerJid, callbackPeerJid, callId = "") => {
459
+ const stanzaId = this.#sock.generateMessageTag();
460
+ console.log(`[Signaling] #sendCallStanza: sending tag=<${signalingTag}> id=${stanzaId} to ${routeTo}`);
461
+ // Attach wait listener BEFORE sending to eliminate any response race condition
462
+ const ackPromise = this.#sock.waitForMessage(stanzaId, ACK_TIMEOUT_MS);
463
+ await this.#sock.sendNode({
464
+ tag: "call",
465
+ attrs: { to: routeTo, id: stanzaId },
466
+ content: [voipNode],
467
+ });
468
+ console.log(`[Signaling] #sendCallStanza: sent id=${stanzaId}, waiting for server ACK...`);
469
+ void (async () => {
470
+ try {
471
+ const ackNode = await ackPromise;
472
+ const ackChildren = Array.isArray(ackNode?.content) ? ackNode.content.map(c => c?.tag || typeof c) : [];
473
+ console.log(`[Signaling] Server ACK received for id=${stanzaId}: tag=<${ackNode?.tag}> type=${ackNode?.attrs?.type} error=${ackNode?.attrs?.error ?? "0"} children=[${ackChildren.join(", ")}]`);
474
+ try {
475
+ console.log(`[Signaling] Server ACK dump for id=${stanzaId}:`, JSON.stringify(ackNode, (k, v) => (v instanceof Uint8Array || Buffer.isBuffer(v)) ? `<bytes len=${v.length}>` : v));
476
+ } catch {}
477
+ if (!ackNode || !this.#voip)
478
+ return;
479
+ const { getBinaryNodeChild, encodeBinaryNode } = this.#baileys;
480
+ const relayNode = getBinaryNodeChild(ackNode, "relay");
481
+ if (relayNode && this.#relay) {
482
+ try {
483
+ const parsedUpdate = parseRelayNode(relayNode);
484
+ if (parsedUpdate && parsedUpdate.relays?.length > 0) {
485
+ console.log(`[Signaling] Direct relay parsed from server ACK: ${parsedUpdate.relays.length} relays found. Updating relay transport immediately...`);
486
+ this.#relay.updateRelayList(parsedUpdate);
487
+ }
488
+ } catch (parseRelayErr) {
489
+ console.error("[Signaling] Failed to parse direct relay update:", parseRelayErr);
490
+ }
491
+ }
492
+ sanitizeVoipDeviceJids(ackNode);
493
+ const ackPayload = Buffer.from(encodeBinaryNode(ackNode)).toString("base64");
494
+ const norm = this.#normCallId(callId);
495
+ const wasmPeer = (callId && (this.#outboundCallPeerByCallId.get(callId) || (norm ? this.#outboundCallPeerByCallId.get(norm) : undefined))) || callbackPeerJid || effectivePeerJid;
496
+ const tcToken = await this.ensureTcToken(effectivePeerJid, wasmPeer);
497
+ try {
498
+ this.#voip.handleSignalingAck({
499
+ payload: ackPayload,
500
+ ackError: ackNode.attrs?.error ?? "0",
501
+ msgType: ackNode.attrs?.type ?? signalingTag,
502
+ peerJid: wasmPeer,
503
+ extraData: tcToken,
504
+ });
505
+ console.log(`[Signaling] handleSignalingAck fed to WASM successfully for id=${stanzaId} (peer: ${wasmPeer}, type: ${ackNode.attrs?.type ?? signalingTag})`);
506
+ }
507
+ catch (wasmErr) {
508
+ console.error(`[Signaling] Error feeding handleSignalingAck to WASM:`, wasmErr);
509
+ }
510
+ }
511
+ catch (ackErr) {
512
+ console.error(`[Signaling] Server ACK timeout or error for id=${stanzaId} (<${signalingTag}>):`, ackErr?.message || ackErr);
513
+ }
514
+ })();
515
+ };
516
+ // ─── private — inbound signaling ──────────────────────────────────────────
517
+ #doProcessIncomingCall = async (node, voip, activeCallId) => {
518
+ const { getAllBinaryNodeChildren, getBinaryNodeChild, encodeBinaryNode } = this.#baileys;
519
+ const voipChild = getAllBinaryNodeChildren(node)[0];
520
+ if (!voipChild)
521
+ return;
522
+ const incomingCallId = String(voipChild.attrs["call-id"] ?? voipChild.attrs.call_id ?? "");
523
+ const childTags = Array.isArray(voipChild?.content) ? voipChild.content.map(c => c?.tag || typeof c) : [];
524
+ console.log(`[Signaling] #doProcessIncomingCall: tag=<${voipChild.tag}> incomingCallId=${incomingCallId} activeCallId=${activeCallId} from=${node.attrs?.from} children=[${childTags.join(", ")}] attrs=${JSON.stringify(voipChild.attrs)}`);
525
+ const callIdForRouting = incomingCallId || activeCallId;
526
+ const _norm = (s) => String(s || "").toLowerCase().trim();
527
+ if (activeCallId && incomingCallId && _norm(incomingCallId) !== _norm(activeCallId)) {
528
+ console.warn("[Signaling] Dropping due to call-id mismatch: incoming=" + incomingCallId + " active=" + activeCallId);
529
+ return;
530
+ }
531
+ const senderDeviceJid = String(voipChild.attrs.participant ?? "") ||
532
+ String(node.attrs.participant ?? "") ||
533
+ String(node.attrs.from ?? "") ||
534
+ String(voipChild.attrs["call-creator"] ?? "");
535
+ const callbackPeerJid = String(node.attrs.from ?? "") || senderDeviceJid;
536
+ const platform = voipChild.attrs.platform ?? node.attrs.platform ?? "";
537
+ const appVersion = voipChild.attrs.version ?? node.attrs.version ?? "";
538
+ const epochId = voipChild.attrs.e ?? node.attrs.e ?? "0";
539
+ const timestamp = voipChild.attrs.t ?? node.attrs.t ?? "0";
540
+ const offline = !!(voipChild.attrs.offline ?? node.attrs.offline);
541
+ let usableNode = voipChild;
542
+ if (getBinaryNodeChild(voipChild, "enc")) {
543
+ console.log(`[Signaling] Inbound <${voipChild.tag}> has <enc>, attempting decrypt with sender=${senderDeviceJid}...`);
544
+ usableNode = await this.#maybeDecryptEnc(voipChild, senderDeviceJid);
545
+ console.log(`[Signaling] Inbound <${voipChild.tag}> <enc> decrypted successfully.`);
546
+ }
547
+ const isInboundOffer = usableNode.tag === "offer";
548
+ if (!usableNode.attrs?.["call-creator"]) {
549
+ const selfLid = this.#sock.authState.creds.me?.lid;
550
+ usableNode.attrs["call-creator"] = isInboundOffer ? (senderDeviceJid || selfLid) : selfLid;
551
+ }
552
+ if (!usableNode.attrs.from && node.attrs?.from) {
553
+ usableNode.attrs.from = node.attrs.from;
554
+ }
555
+ if (!usableNode.attrs.to && node.attrs?.to) {
556
+ usableNode.attrs.to = node.attrs.to;
557
+ }
558
+ sanitizeVoipDeviceJids(usableNode);
559
+ const b64 = Buffer.from(encodeBinaryNode(usableNode)).toString("base64");
560
+ const norm = this.#normCallId(callIdForRouting);
561
+ const storedPeerJid = callIdForRouting ? (this.#incomingCallPeerById.get(callIdForRouting) || (norm ? this.#incomingCallPeerById.get(norm) : undefined)) : undefined;
562
+ let mappedRemoteDeviceJid = callIdForRouting ? (this.#remoteDevicePeerByCallId.get(callIdForRouting) || (norm ? this.#remoteDevicePeerByCallId.get(norm) : undefined)) : undefined;
563
+ if (callIdForRouting && usableNode.attrs?.caller_pn) {
564
+ this.#outboundCallPnByCallId.set(callIdForRouting, usableNode.attrs.caller_pn);
565
+ if (norm) this.#outboundCallPnByCallId.set(norm, usableNode.attrs.caller_pn);
566
+ }
567
+ if (callIdForRouting && (callbackPeerJid || senderDeviceJid)) {
568
+ this.#remoteXmppRoutePeerByCallId.set(callIdForRouting, callbackPeerJid || senderDeviceJid);
569
+ if (norm) this.#remoteXmppRoutePeerByCallId.set(norm, callbackPeerJid || senderDeviceJid);
570
+ const hinted = this.#pickConcreteRouteHint(senderDeviceJid, callbackPeerJid) || this.#toCallDeviceJid(senderDeviceJid || callbackPeerJid);
571
+ if (hinted && hinted !== mappedRemoteDeviceJid) {
572
+ mappedRemoteDeviceJid = hinted;
573
+ this.#remoteDevicePeerByCallId.set(callIdForRouting, hinted);
574
+ if (norm) this.#remoteDevicePeerByCallId.set(norm, hinted);
575
+ }
576
+ }
577
+ let outboundLid = callIdForRouting ? (this.#outboundCallPeerByCallId.get(callIdForRouting) || (norm ? this.#outboundCallPeerByCallId.get(norm) : undefined)) : undefined;
578
+ if (!outboundLid && this.#outboundCallPeerByCallId.size > 0 && !isInboundOffer) {
579
+ outboundLid = this.#outboundCallPeerByCallId.values().next().value;
580
+ console.log(`[Signaling] Fallback resolved outboundLid=${outboundLid} from active calls`);
581
+ }
582
+ const routedPeerJid = (isInboundOffer || !outboundLid)
583
+ ? this.#preferDeviceRouteJid(senderDeviceJid, callbackPeerJid, storedPeerJid)
584
+ : outboundLid;
585
+ if (callIdForRouting && routedPeerJid) {
586
+ this.#incomingCallPeerById.set(callIdForRouting, routedPeerJid);
587
+ if (norm) this.#incomingCallPeerById.set(norm, routedPeerJid);
588
+ }
589
+ const tcToken = await this.ensureTcToken(routedPeerJid, callbackPeerJid);
590
+ console.log(`[Signaling] Passing inbound <${usableNode.tag}> to WASM (routedPeerJid: ${routedPeerJid}, remoteDevice: ${mappedRemoteDeviceJid})...`);
591
+ switch (usableNode.tag) {
592
+ case "offer":
593
+ voip.handleSignalingOffer({
594
+ payload: b64,
595
+ peerPlatform: isNaN(Number(platform)) ? 0 : Number(platform),
596
+ peerAppVersion: appVersion,
597
+ epochId, timestamp,
598
+ isOffline: offline,
599
+ isOfferNotContact: false,
600
+ peerJid: this.#toCallDeviceJid(routedPeerJid),
601
+ tcToken,
602
+ });
603
+ break;
604
+ case "ack":
605
+ voip.handleSignalingAck({
606
+ payload: b64,
607
+ ackError: usableNode.attrs.error ?? "0",
608
+ msgType: usableNode.attrs.type ?? "",
609
+ peerJid: this.#toCallDeviceJid(routedPeerJid),
610
+ extraData: tcToken,
611
+ });
612
+ break;
613
+ default:
614
+ voip.handleSignalingMessage({
615
+ payload: b64,
616
+ peerPlatform: platform,
617
+ peerAppVersion: appVersion,
618
+ epochId, timestamp,
619
+ isOffline: offline,
620
+ peerJid: this.#toCallDeviceJid(routedPeerJid),
621
+ tcToken,
622
+ });
623
+ if (callIdForRouting && (usableNode.tag === "terminate" || usableNode.tag === "reject")) {
624
+ this.unregisterCall(callIdForRouting);
625
+ }
626
+ break;
627
+ }
628
+ };
629
+ #doProcessIncomingReceipt = async (node, voip, activeCallId) => {
630
+ const { getAllBinaryNodeChildren, encodeBinaryNode } = this.#baileys;
631
+ const receiptChild = getAllBinaryNodeChildren(node)[0];
632
+ if (!receiptChild)
633
+ return;
634
+ const incomingCallId = String(receiptChild.attrs["call-id"] ?? receiptChild.attrs.call_id ?? "");
635
+ const callIdForRouting = incomingCallId || activeCallId;
636
+ const _norm = (s) => String(s || "").toLowerCase().trim();
637
+ if (activeCallId && incomingCallId && _norm(incomingCallId) !== _norm(activeCallId)) {
638
+ console.warn("[Signaling] Dropping receipt due to call-id mismatch: incoming=" + incomingCallId + " active=" + activeCallId);
639
+ return;
640
+ }
641
+ const callbackPeerJid = String(node.attrs.from ?? receiptChild.attrs["call-creator"] ?? "");
642
+ const norm = this.#normCallId(callIdForRouting);
643
+ let outboundLid = callIdForRouting ? (this.#outboundCallPeerByCallId.get(callIdForRouting) || (norm ? this.#outboundCallPeerByCallId.get(norm) : undefined)) : undefined;
644
+ if (!outboundLid && this.#outboundCallPeerByCallId.size > 0) {
645
+ outboundLid = this.#outboundCallPeerByCallId.values().next().value;
646
+ }
647
+ const storedPeerJid = callIdForRouting ? (this.#incomingCallPeerById.get(callIdForRouting) || (norm ? this.#incomingCallPeerById.get(norm) : undefined)) : undefined;
648
+ const routedPeerJid = outboundLid || this.#preferOrderedRouteJid(storedPeerJid, callbackPeerJid);
649
+ if (callIdForRouting && routedPeerJid) {
650
+ this.#incomingCallPeerById.set(callIdForRouting, routedPeerJid);
651
+ if (norm) this.#incomingCallPeerById.set(norm, routedPeerJid);
652
+ }
653
+ const tcToken = await this.ensureTcToken(routedPeerJid, callbackPeerJid);
654
+ sanitizeVoipDeviceJids(node);
655
+ voip.handleSignalingReceipt({
656
+ payload: Buffer.from(encodeBinaryNode(node)).toString("base64"),
657
+ peerJid: this.#toCallDeviceJid(routedPeerJid),
658
+ tcToken,
659
+ });
660
+ };
661
+ #maybeDecryptEnc = async (voipNode, peerJid) => {
662
+ const { getBinaryNodeChild, unpadRandomMax16, proto } = this.#baileys;
663
+ const enc = getBinaryNodeChild(voipNode, "enc");
664
+ if (!enc || !(enc.content instanceof Uint8Array))
665
+ return voipNode;
666
+ const type = enc.attrs.type;
667
+ if (type !== "pkmsg" && type !== "msg")
668
+ return voipNode;
669
+ const bare = this.#toBareJid(peerJid);
670
+ const deviceJid = this.#toCallDeviceJid(peerJid);
671
+ const candidateSet = new Set([peerJid, deviceJid, bare]);
672
+ try {
673
+ const mappedLid = await this.#sock.signalRepository.lidMapping?.getLIDForPN(bare);
674
+ if (mappedLid) {
675
+ candidateSet.add(mappedLid);
676
+ candidateSet.add(this.#toCallDeviceJid(mappedLid));
677
+ }
678
+ const mappedPn = await this.#sock.signalRepository.lidMapping?.getPNForLID(bare);
679
+ if (mappedPn) {
680
+ candidateSet.add(mappedPn);
681
+ candidateSet.add(this.#toCallDeviceJid(mappedPn));
682
+ }
683
+ } catch {}
684
+ const candidates = [...candidateSet].filter(Boolean);
685
+ let lastErr;
686
+ for (const jid of candidates) {
687
+ try {
688
+ const decrypted = await this.#sock.signalRepository.decryptMessage({
689
+ jid, type, ciphertext: enc.content,
690
+ });
691
+ const parsed = proto.Message.decode(unpadRandomMax16(decrypted));
692
+ const callKey = parsed.call?.callKey;
693
+ if (!callKey || callKey.length === 0) {
694
+ throw new Error("decrypted signaling has no call.callKey");
695
+ }
696
+ enc.content = callKey;
697
+ return voipNode;
698
+ }
699
+ catch (err) {
700
+ lastErr = err;
701
+ }
702
+ }
703
+ throw lastErr;
704
+ };
705
+ #encryptCallKey = async (targetJid, rawCallKey, count) => {
706
+ const { encodeWAMessage } = this.#baileys;
707
+ const primaryDeviceJid = this.#toPrimaryDeviceJid(targetJid);
708
+ const sessionTargets = primaryDeviceJid && primaryDeviceJid !== targetJid
709
+ ? [primaryDeviceJid, targetJid]
710
+ : [targetJid];
711
+ await this.#ensureSignalSessions(sessionTargets, false);
712
+ const { type, ciphertext } = await this.#sock.signalRepository.encryptMessage({
713
+ jid: targetJid,
714
+ data: encodeWAMessage({ call: { callKey: Buffer.from(rawCallKey) } }),
715
+ });
716
+ return {
717
+ encNode: {
718
+ tag: "enc",
719
+ attrs: { v: "2", type, count: String(count) },
720
+ content: Buffer.from(ciphertext),
721
+ },
722
+ shouldIncludeDeviceIdentity: type === "pkmsg",
723
+ };
724
+ };
725
+ #ensureSignalSessions = async (jids, refresh) => {
726
+ const { parseAndInjectE2ESessions } = this.#baileys;
727
+ const missing = [];
728
+ for (const jid of [...new Set(jids.filter(Boolean))]) {
729
+ const signalId = this.#sock.signalRepository.jidToSignalProtocolAddress(jid);
730
+ const cachedAt = this.#ensuredSignalSessions.get(signalId);
731
+ if (!refresh && cachedAt && Date.now() - cachedAt < SESSION_CACHE_TTL_MS)
732
+ continue;
733
+ if (!refresh) {
734
+ const validation = await this.#sock.signalRepository.validateSession(jid);
735
+ if (validation.exists) {
736
+ this.#ensuredSignalSessions.set(signalId, Date.now());
737
+ continue;
738
+ }
739
+ }
740
+ missing.push(jid);
741
+ }
742
+ if (!missing.length)
743
+ return;
744
+ const sessionNode = await this.#sock.query({
745
+ tag: "iq",
746
+ attrs: { xmlns: "encrypt", type: "get", to: S_WHATSAPP_NET },
747
+ content: [{
748
+ tag: "key", attrs: {},
749
+ content: missing.map((jid) => ({ tag: "user", attrs: { jid } })),
750
+ }],
751
+ });
752
+ await parseAndInjectE2ESessions(sessionNode, this.#sock.signalRepository);
753
+ for (const jid of missing) {
754
+ this.#ensuredSignalSessions.set(this.#sock.signalRepository.jidToSignalProtocolAddress(jid), Date.now());
755
+ }
756
+ };
757
+ #appendDeviceIdentity = (voipNode) => {
758
+ const { getBinaryNodeChild, encodeSignedDeviceIdentity } = this.#baileys;
759
+ if (getBinaryNodeChild(voipNode, "device-identity"))
760
+ return;
761
+ const account = this.#sock.authState.creds.account;
762
+ if (!account)
763
+ return;
764
+ const children = getNodeChildren(voipNode);
765
+ children.push({
766
+ tag: "device-identity",
767
+ attrs: {},
768
+ content: encodeSignedDeviceIdentity(account, true),
769
+ });
770
+ setNodeChildren(voipNode, children);
771
+ };
772
+ // ─── private — JID utilities ──────────────────────────────────────────────
773
+ #toBareJid = (jid) => {
774
+ const { jidDecode, jidEncode } = this.#baileys;
775
+ const decoded = jidDecode(jid);
776
+ if (!decoded?.user)
777
+ return jid;
778
+ const server = jid.endsWith("@lid") ? "lid" : "s.whatsapp.net";
779
+ return jidEncode(decoded.user, server);
780
+ };
781
+ #toCallDeviceJid = (jid) => {
782
+ if (!jid) return "";
783
+ const strJid = String(jid).trim();
784
+ const { jidDecode } = this.#baileys;
785
+ const decoded = jidDecode(strJid);
786
+ if (!decoded?.user)
787
+ return formatDeviceJid(strJid);
788
+ const server = strJid.endsWith("@lid") ? "lid" : "s.whatsapp.net";
789
+ const device = decoded.device ?? 0;
790
+ return `${decoded.user}:${device}@${server}`;
791
+ };
792
+ #toPrimaryDeviceJid = (jid) => {
793
+ const { jidDecode, jidEncode } = this.#baileys;
794
+ const decoded = jidDecode(jid);
795
+ if (!decoded?.user)
796
+ return undefined;
797
+ const device = decoded.device;
798
+ if (device == null || device === 0)
799
+ return undefined;
800
+ const server = jid.endsWith("@lid") ? "lid" : "s.whatsapp.net";
801
+ return jidEncode(decoded.user, server);
802
+ };
803
+ #hasConcreteDevice = (jid) => {
804
+ const decoded = this.#baileys.jidDecode(jid);
805
+ return !!decoded?.user && decoded.device != null;
806
+ };
807
+ #preferDeviceRouteJid = (...candidates) => {
808
+ for (const c of candidates) {
809
+ const jid = String(c ?? "").trim();
810
+ if (jid && this.#hasConcreteDevice(jid))
811
+ return jid;
812
+ }
813
+ for (const c of candidates) {
814
+ const jid = String(c ?? "").trim();
815
+ if (jid)
816
+ return this.#toCallDeviceJid(jid);
817
+ }
818
+ return "";
819
+ };
820
+ #preferOrderedRouteJid = (...candidates) => {
821
+ for (const c of candidates) {
822
+ const jid = String(c ?? "").trim();
823
+ if (jid)
824
+ return this.#toCallDeviceJid(jid);
825
+ }
826
+ return "";
827
+ };
828
+ #pickConcreteRouteHint = (...candidates) => {
829
+ for (const c of candidates) {
830
+ const jid = String(c ?? "").trim();
831
+ if (jid && this.#hasConcreteDevice(jid))
832
+ return jid;
833
+ }
834
+ return "";
835
+ };
836
+ #resolveOutboundPeerJid = (callId, wasmPeerJid) => {
837
+ const peerJid = String(wasmPeerJid ?? "").trim();
838
+ if (!peerJid || !callId)
839
+ return peerJid;
840
+ const norm = this.#normCallId(callId);
841
+ return this.#remoteDevicePeerByCallId.get(callId) ?? (norm ? this.#remoteDevicePeerByCallId.get(norm) : undefined) ?? peerJid;
842
+ };
843
+ #expandSignalSessionTargets = (jids) => [...new Set(jids.flatMap((jid) => {
844
+ const primary = this.#toPrimaryDeviceJid(jid);
845
+ return primary && primary !== jid ? [primary, jid] : [jid];
846
+ }))];
847
+ #normalizeStartCallPeerList = (jids) => {
848
+ const { jidDecode, jidEncode } = this.#baileys;
849
+ const result = new Set();
850
+ for (const jid of jids) {
851
+ const decoded = jidDecode(jid);
852
+ if (!decoded?.user) {
853
+ result.add(jid);
854
+ continue;
855
+ }
856
+ const server = jid.endsWith("@lid") ? "lid" : "s.whatsapp.net";
857
+ result.add(jidEncode(decoded.user, server));
858
+ if (decoded.device != null) {
859
+ result.add(`${decoded.user}:${decoded.device}@${server}`);
860
+ }
861
+ }
862
+ return [...result].slice(0, 5);
863
+ };
864
+ // ─── private — TC token ───────────────────────────────────────────────────
865
+ #rememberTcToken = (jid, token, timestamp = "") => {
866
+ const bareJid = this.#toBareJid(jid);
867
+ if (!token.length)
868
+ return;
869
+ this.#observedTcTokens.set(bareJid, { token: Buffer.from(token), timestamp });
870
+ const waiters = this.#pendingTcTokenWaiters.get(bareJid);
871
+ if (waiters?.length) {
872
+ this.#pendingTcTokenWaiters.delete(bareJid);
873
+ for (const w of waiters)
874
+ w(Buffer.from(token));
875
+ }
876
+ };
877
+ #getTcToken = async (jid) => {
878
+ const userJid = this.#toBareJid(jid);
879
+ const observed = this.#observedTcTokens.get(userJid)?.token;
880
+ if (observed?.length)
881
+ return Buffer.from(observed);
882
+ try {
883
+ const data = await this.#sock.authState.keys.get("tctoken", [userJid]);
884
+ const token = data[userJid]?.token;
885
+ if (token && token.length > 0) {
886
+ this.#rememberTcToken(userJid, token, data[userJid]?.timestamp);
887
+ return token;
888
+ }
889
+ }
890
+ catch { }
891
+ return undefined;
892
+ };
893
+ }