@japofc/baileys 2.1.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.
- package/NOTICE.md +111 -0
- package/README.md +1254 -0
- package/WAProto/index.d.ts +9 -0
- package/WAProto/index.js +142276 -0
- package/engine-requirements.js +13 -0
- package/lib/Builders/A2UI.d.ts +242 -0
- package/lib/Builders/A2UI.js +308 -0
- package/lib/Builders/AIRich.d.ts +105 -0
- package/lib/Builders/AIRich.js +2286 -0
- package/lib/Builders/Button.d.ts +103 -0
- package/lib/Builders/Button.js +883 -0
- package/lib/Builders/ButtonV2.d.ts +28 -0
- package/lib/Builders/ButtonV2.js +144 -0
- package/lib/Builders/ButtonV3.d.ts +25 -0
- package/lib/Builders/ButtonV3.js +184 -0
- package/lib/Builders/Carousel.d.ts +22 -0
- package/lib/Builders/Carousel.js +111 -0
- package/lib/Builders/JapBaileys.d.ts +34 -0
- package/lib/Builders/JapBaileys.js +108 -0
- package/lib/Builders/Poll.d.ts +25 -0
- package/lib/Builders/Poll.js +128 -0
- package/lib/Builders/index.d.ts +17 -0
- package/lib/Builders/index.js +11 -0
- package/lib/Builders/shared.d.ts +67 -0
- package/lib/Builders/shared.js +652 -0
- package/lib/Defaults/index.d.ts +154 -0
- package/lib/Defaults/index.js +178 -0
- package/lib/Framework/Bot.d.ts +61 -0
- package/lib/Framework/Bot.js +248 -0
- package/lib/Framework/Context.d.ts +21 -0
- package/lib/Framework/Context.js +89 -0
- package/lib/Framework/MediaManager.d.ts +17 -0
- package/lib/Framework/MediaManager.js +151 -0
- package/lib/Framework/SessionManager.d.ts +13 -0
- package/lib/Framework/SessionManager.js +33 -0
- package/lib/Framework/StatsManager.d.ts +26 -0
- package/lib/Framework/StatsManager.js +119 -0
- package/lib/Framework/Store/SQLiteStore.d.ts +11 -0
- package/lib/Framework/Store/SQLiteStore.js +73 -0
- package/lib/Framework/index.d.ts +9 -0
- package/lib/Framework/index.js +10 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.d.ts +7 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.d.ts +10 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.d.ts +6 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.d.ts +11 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.d.ts +12 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.d.ts +16 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.d.ts +54 -0
- package/lib/Signal/libsignal.js +430 -0
- package/lib/Signal/lid-mapping.d.ts +20 -0
- package/lib/Signal/lid-mapping.js +276 -0
- package/lib/Socket/Client/index.d.ts +2 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.d.ts +5 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.d.ts +12 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/business.d.ts +395 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.d.ts +136 -0
- package/lib/Socket/chats.js +1327 -0
- package/lib/Socket/communities.d.ts +562 -0
- package/lib/Socket/communities.js +439 -0
- package/lib/Socket/dugong.d.ts +117 -0
- package/lib/Socket/dugong.js +798 -0
- package/lib/Socket/groups.d.ts +285 -0
- package/lib/Socket/groups.js +354 -0
- package/lib/Socket/index.d.ts +585 -0
- package/lib/Socket/index.js +52 -0
- package/lib/Socket/messages-recv.d.ts +333 -0
- package/lib/Socket/messages-recv.js +1781 -0
- package/lib/Socket/messages-send.d.ts +353 -0
- package/lib/Socket/messages-send.js +1524 -0
- package/lib/Socket/mex.d.ts +1 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.d.ts +286 -0
- package/lib/Socket/newsletter.js +364 -0
- package/lib/Socket/socket.d.ts +60 -0
- package/lib/Socket/socket.js +1012 -0
- package/lib/Socket/username.d.ts +82 -0
- package/lib/Socket/username.js +159 -0
- package/lib/Store/index.d.ts +3 -0
- package/lib/Store/index.js +3 -0
- package/lib/Store/make-in-memory-store.d.ts +62 -0
- package/lib/Store/make-in-memory-store.js +428 -0
- package/lib/Store/make-ordered-dictionary.d.ts +12 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Store/object-repository.js +23 -0
- package/lib/Types/Auth.d.ts +117 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Call.d.ts +15 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.d.ts +124 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.d.ts +26 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.d.ts +256 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +71 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.d.ts +5 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.d.ts +6 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.d.ts +62 -0
- package/lib/Types/Message.js +17 -0
- package/lib/Types/Mex.d.ts +2 -0
- package/lib/Types/Mex.js +38 -0
- package/lib/Types/Product.d.ts +79 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/RichType.d.ts +2 -0
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/Signal.d.ts +87 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +138 -0
- package/lib/Types/Socket.js +1 -0
- package/lib/Types/State.d.ts +8 -0
- package/lib/Types/State.js +55 -0
- package/lib/Types/USync.d.ts +26 -0
- package/lib/Types/index.d.ts +68 -0
- package/lib/Types/index.js +27 -0
- package/lib/Utils/MessageBuilder.d.ts +2 -0
- package/lib/Utils/MessageBuilder.js +2 -0
- package/lib/Utils/PersistentStore.d.ts +60 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/anti-delete.d.ts +74 -0
- package/lib/Utils/anti-delete.js +221 -0
- package/lib/Utils/auth-secure.d.ts +86 -0
- package/lib/Utils/auth-secure.js +764 -0
- package/lib/Utils/auth-utils.d.ts +57 -0
- package/lib/Utils/auth-utils.js +306 -0
- package/lib/Utils/auto-reply.d.ts +47 -0
- package/lib/Utils/auto-reply.js +157 -0
- package/lib/Utils/baileys-event-stream.d.ts +8 -0
- package/lib/Utils/baileys-event-stream.js +62 -0
- package/lib/Utils/browser-utils.d.ts +8 -0
- package/lib/Utils/browser-utils.js +27 -0
- package/lib/Utils/business.d.ts +49 -0
- package/lib/Utils/business.js +239 -0
- package/lib/Utils/button-helper-utils.d.ts +53 -0
- package/lib/Utils/button-helper-utils.js +312 -0
- package/lib/Utils/button-sender.d.ts +54 -0
- package/lib/Utils/button-sender.js +822 -0
- package/lib/Utils/chat-control.d.ts +159 -0
- package/lib/Utils/chat-control.js +230 -0
- package/lib/Utils/chat-history-helpers.d.ts +21 -0
- package/lib/Utils/chat-history-helpers.js +70 -0
- package/lib/Utils/chat-utils.d.ts +407 -0
- package/lib/Utils/chat-utils.js +885 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +4 -0
- package/lib/Utils/companion-reg-client-utils.js +39 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +147 -0
- package/lib/Utils/decode-wa-message.d.ts +83 -0
- package/lib/Utils/decode-wa-message.js +316 -0
- package/lib/Utils/event-buffer.d.ts +12 -0
- package/lib/Utils/event-buffer.js +635 -0
- package/lib/Utils/generics.d.ts +69 -0
- package/lib/Utils/generics.js +438 -0
- package/lib/Utils/history-share.d.ts +11 -0
- package/lib/Utils/history-share.js +70 -0
- package/lib/Utils/history.d.ts +90 -0
- package/lib/Utils/history.js +146 -0
- package/lib/Utils/humanizer.d.ts +12 -0
- package/lib/Utils/humanizer.js +73 -0
- package/lib/Utils/identity-change-handler.d.ts +13 -0
- package/lib/Utils/identity-change-handler.js +49 -0
- package/lib/Utils/index.d.ts +57 -0
- package/lib/Utils/index.js +56 -0
- package/lib/Utils/jid-tools.d.ts +6 -0
- package/lib/Utils/jid-tools.js +77 -0
- package/lib/Utils/link-preview.d.ts +12 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.d.ts +4 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.d.ts +12 -0
- package/lib/Utils/lt-hash.js +26 -0
- package/lib/Utils/make-mutex.d.ts +6 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/media-messages.d.ts +18 -0
- package/lib/Utils/media-messages.js +70 -0
- package/lib/Utils/media-set.d.ts +13 -0
- package/lib/Utils/media-set.js +171 -0
- package/lib/Utils/message-kind.d.ts +32 -0
- package/lib/Utils/message-kind.js +136 -0
- package/lib/Utils/message-retry-manager.d.ts +79 -0
- package/lib/Utils/message-retry-manager.js +264 -0
- package/lib/Utils/message-search.d.ts +44 -0
- package/lib/Utils/message-search.js +188 -0
- package/lib/Utils/messages-media.d.ts +135 -0
- package/lib/Utils/messages-media.js +868 -0
- package/lib/Utils/messages.d.ts +44 -0
- package/lib/Utils/messages.js +2326 -0
- package/lib/Utils/meta-ai.d.ts +13 -0
- package/lib/Utils/meta-ai.js +74 -0
- package/lib/Utils/mini-app.d.ts +35 -0
- package/lib/Utils/mini-app.js +213 -0
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.d.ts +9 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/past-participants.d.ts +14 -0
- package/lib/Utils/past-participants.js +40 -0
- package/lib/Utils/pre-key-manager.d.ts +25 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.d.ts +82 -0
- package/lib/Utils/process-message.js +808 -0
- package/lib/Utils/qr-render.d.ts +59 -0
- package/lib/Utils/qr-render.js +179 -0
- package/lib/Utils/qrcodegen.d.ts +79 -0
- package/lib/Utils/qrcodegen.js +848 -0
- package/lib/Utils/reporting-utils.d.ts +12 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-message-utils.d.ts +48 -0
- package/lib/Utils/rich-message-utils.js +444 -0
- package/lib/Utils/router.d.ts +37 -0
- package/lib/Utils/router.js +165 -0
- package/lib/Utils/scheduled-posts.d.ts +30 -0
- package/lib/Utils/scheduled-posts.js +110 -0
- package/lib/Utils/scheduling.d.ts +42 -0
- package/lib/Utils/scheduling.js +139 -0
- package/lib/Utils/signal.d.ts +82 -0
- package/lib/Utils/signal.js +200 -0
- package/lib/Utils/stanza-ack.d.ts +16 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/status.d.ts +50 -0
- package/lib/Utils/status.js +107 -0
- package/lib/Utils/stickerpack.d.ts +51 -0
- package/lib/Utils/stickerpack.js +274 -0
- package/lib/Utils/sync-action-utils.d.ts +2 -0
- package/lib/Utils/sync-action-utils.js +48 -0
- package/lib/Utils/tag.d.ts +12 -0
- package/lib/Utils/tag.js +44 -0
- package/lib/Utils/tc-token-utils.d.ts +29 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/templates.d.ts +76 -0
- package/lib/Utils/templates.js +150 -0
- package/lib/Utils/transcribe.d.ts +12 -0
- package/lib/Utils/transcribe.js +107 -0
- package/lib/Utils/use-cache-manager-auth-state.d.ts +13 -0
- package/lib/Utils/use-cache-manager-auth-state.js +81 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-multi-file-auth-state.js +166 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-single-file-auth-state.js +113 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
- package/lib/Utils/use-sqlite-auth-state.js +169 -0
- package/lib/Utils/validate-connection.d.ts +42 -0
- package/lib/Utils/validate-connection.js +198 -0
- package/lib/Utils/vcard.d.ts +58 -0
- package/lib/Utils/vcard.js +104 -0
- package/lib/Utils/voice-note.d.ts +15 -0
- package/lib/Utils/voice-note.js +63 -0
- package/lib/VoIP/audio-feeder.d.ts +18 -0
- package/lib/VoIP/audio-feeder.js +160 -0
- package/lib/VoIP/call-recorder.d.ts +13 -0
- package/lib/VoIP/call-recorder.js +73 -0
- package/lib/VoIP/index.d.ts +86 -0
- package/lib/VoIP/index.js +611 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +563 -0
- package/lib/VoIP/signaling.d.ts +26 -0
- package/lib/VoIP/signaling.js +662 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +120 -0
- package/lib/VoIP/wasm-engine.js +1358 -0
- package/lib/VoIP/worker-bootstrap.d.ts +4 -0
- package/lib/VoIP/worker-bootstrap.js +1046 -0
- package/lib/WABinary/constants.d.ts +61 -0
- package/lib/WABinary/constants.js +1466 -0
- package/lib/WABinary/decode.d.ts +9 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.d.ts +2 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.d.ts +74 -0
- package/lib/WABinary/generic-utils.js +276 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.d.ts +28 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.d.ts +19 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.d.ts +7 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.d.ts +34926 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.d.ts +1 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +36 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +51 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +29 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +62 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +38 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +14 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.d.ts +7 -0
- package/lib/WAUSync/Protocols/index.js +7 -0
- package/lib/WAUSync/USyncQuery.d.ts +20 -0
- package/lib/WAUSync/USyncQuery.js +121 -0
- package/lib/WAUSync/USyncUser.d.ts +16 -0
- package/lib/WAUSync/USyncUser.js +30 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/assets/wasm/loader.d.ts +5 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.d.ts +5 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +23 -0
- package/package.json +180 -0
- package/postinstall-banner.js +82 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { randomBytes, createHmac } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
import { WasmEngine } from "./wasm-engine.js";
|
|
5
|
+
import { RelayRtcTransport } from "./relay-transport.js";
|
|
6
|
+
import { SignalingBridge } from "./signaling.js";
|
|
7
|
+
import { AudioFeeder } from "./audio-feeder.js";
|
|
8
|
+
import { CallState } from "./types.js";
|
|
9
|
+
import { createWavRecorder } from "./call-recorder.js";
|
|
10
|
+
|
|
11
|
+
export { CallState } from "./types.js";
|
|
12
|
+
export { createWavRecorder } from "./call-recorder.js"; // JAP@Add
|
|
13
|
+
|
|
14
|
+
const SHA256_LEN = 32;
|
|
15
|
+
|
|
16
|
+
const toBareJid = (jid) => {
|
|
17
|
+
if (!jid) return jid;
|
|
18
|
+
const at = jid.indexOf("@");
|
|
19
|
+
if (at < 0) return jid;
|
|
20
|
+
const user = jid.slice(0, at).split(":")[0];
|
|
21
|
+
return `${user}@${jid.slice(at + 1)}`;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const computeHkdf = (key, salt, info, length) => {
|
|
25
|
+
const effectiveSalt = salt && salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(SHA256_LEN, 0);
|
|
26
|
+
const prk = createHmac("sha256", effectiveSalt).update(key).digest();
|
|
27
|
+
const blocks = Math.ceil(length / SHA256_LEN);
|
|
28
|
+
const okm = Buffer.alloc(blocks * SHA256_LEN);
|
|
29
|
+
let prev = Buffer.alloc(0);
|
|
30
|
+
for (let i = 1; i <= blocks; i += 1) {
|
|
31
|
+
prev = createHmac("sha256", prk)
|
|
32
|
+
.update(prev)
|
|
33
|
+
.update(info)
|
|
34
|
+
.update(Buffer.from([i]))
|
|
35
|
+
.digest();
|
|
36
|
+
prev.copy(okm, (i - 1) * SHA256_LEN);
|
|
37
|
+
}
|
|
38
|
+
return new Uint8Array(okm.buffer, okm.byteOffset, length);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const computeHmacSha256 = (data, key) => {
|
|
42
|
+
const result = createHmac("sha256", Buffer.from(key)).update(data).digest();
|
|
43
|
+
return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const isCallReceiptNode = (node) => {
|
|
47
|
+
if (node?.tag !== "receipt") return false;
|
|
48
|
+
const child = Array.isArray(node.content) ? node.content[0] : null;
|
|
49
|
+
return !!(child?.attrs?.["call-id"] || child?.attrs?.call_id);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export class ActiveCall extends EventEmitter {
|
|
53
|
+
#state = CallState.Idle;
|
|
54
|
+
#endResolver;
|
|
55
|
+
#endPromise;
|
|
56
|
+
#endTimer = null;
|
|
57
|
+
#ended = false;
|
|
58
|
+
_audioSource = "silence";
|
|
59
|
+
|
|
60
|
+
constructor(callId, engine, durationMs, meta = {}) {
|
|
61
|
+
super();
|
|
62
|
+
this.callId = callId;
|
|
63
|
+
this.startedAt = Date.now(); // JAP@Add
|
|
64
|
+
this._recorder = null; // JAP@Add
|
|
65
|
+
this.engine = engine;
|
|
66
|
+
this.direction = meta.direction || 'outbound'; // JAP@Add: outbound|inbound|group-outbound|group-inbound
|
|
67
|
+
this.from = meta.from || null; // JAP@Add: inbound caller
|
|
68
|
+
this.groupJid = meta.groupJid || null; // JAP@Add: group calls
|
|
69
|
+
this.#endPromise = new Promise((res) => { this.#endResolver = res; });
|
|
70
|
+
if (durationMs > 0) {
|
|
71
|
+
this.#endTimer = setTimeout(() => this.end(), durationMs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get state() { return this.#state; }
|
|
76
|
+
|
|
77
|
+
end = () => {
|
|
78
|
+
if (this.#ended) return;
|
|
79
|
+
this.#ended = true;
|
|
80
|
+
if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
|
|
81
|
+
try { this.engine.endCall(0, true); } catch {}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
mute = (muted) => {
|
|
85
|
+
try { this.engine.setMute(muted); } catch {}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** JAP@Add --- group-call participant management (no-op false when unsupported). */
|
|
89
|
+
invite = (pnUserJid, lidUserJid = '', deviceJids = []) => {
|
|
90
|
+
try { this.engine.inviteToCall({ invitedParticipantPnUserJid: pnUserJid, invitedParticipantLidUserJid: lidUserJid, deviceJids }); return true; } catch { return false; }
|
|
91
|
+
};
|
|
92
|
+
removeParticipant = (peerJid) => {
|
|
93
|
+
try { this.engine.removeCallParticipant(peerJid); return true; } catch { return false; }
|
|
94
|
+
};
|
|
95
|
+
requestMute = (peerJid) => {
|
|
96
|
+
try { this.engine.requestPeerMute(peerJid); return true; } catch { return false; }
|
|
97
|
+
};
|
|
98
|
+
react = (reaction) => {
|
|
99
|
+
try { return this.engine.sendReaction(reaction); } catch { return false; }
|
|
100
|
+
};
|
|
101
|
+
setHandRaised = (raised) => {
|
|
102
|
+
try { return this.engine.raiseHand(raised); } catch { return false; }
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** JAP@Add --- ms elapsed since this call object was created. */
|
|
106
|
+
getDurationMs = () => Date.now() - (this.startedAt || Date.now());
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* JAP@Add --- record the INBOUND (remote peer) audio stream to a .wav file.
|
|
110
|
+
* Returns a stop() function; the file is also finalized automatically on call end.
|
|
111
|
+
*/
|
|
112
|
+
recordToFile = (filePath, opts = {}) => {
|
|
113
|
+
if (this._recorder) throw new Error("Already recording this call.");
|
|
114
|
+
this._recorder = createWavRecorder(filePath, opts);
|
|
115
|
+
const onAudio = (pcm) => { try { this._recorder.write(pcm); } catch {} };
|
|
116
|
+
const onEnd = () => { try { this._recorder?.close(); } catch {} this._recorder = null; };
|
|
117
|
+
this.on("audio", onAudio);
|
|
118
|
+
this.once("ended", onEnd);
|
|
119
|
+
return () => {
|
|
120
|
+
this.off("audio", onAudio);
|
|
121
|
+
this.off("ended", onEnd);
|
|
122
|
+
onEnd();
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
waitForEnd = () => this.#endPromise;
|
|
127
|
+
|
|
128
|
+
_updateState = (state) => {
|
|
129
|
+
this.#state = state;
|
|
130
|
+
if (state === CallState.PreacceptReceived) this.emit("ringing");
|
|
131
|
+
else if (state === CallState.Active) this.emit("connected");
|
|
132
|
+
else if (state === CallState.Idle || state === CallState.Ending) {
|
|
133
|
+
this._forceEnd("ended");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
_emitAudio = (pcm) => { this.emit("audio", pcm); };
|
|
138
|
+
|
|
139
|
+
_forceEnd = (reason) => {
|
|
140
|
+
if (this.#ended) return;
|
|
141
|
+
this.#ended = true;
|
|
142
|
+
if (this.#endTimer) { clearTimeout(this.#endTimer); this.#endTimer = null; }
|
|
143
|
+
this.emit("ended", reason);
|
|
144
|
+
this.#endResolver(reason);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class VoipClient extends EventEmitter {
|
|
149
|
+
#config;
|
|
150
|
+
#engine = null;
|
|
151
|
+
#relay = null;
|
|
152
|
+
#signaling = null;
|
|
153
|
+
#sock = null;
|
|
154
|
+
#activeCall = null;
|
|
155
|
+
#lastPeer = null; // JAP@Add: { peerJid, callId } for recovery
|
|
156
|
+
#lastGroupJoin = null; // JAP@Add: params for rejoinGroupCall()
|
|
157
|
+
#watchdogTimer = null; // JAP@Add: relay-health watchdog
|
|
158
|
+
#watchdogMisses = 0;
|
|
159
|
+
#capturePtr = 0;
|
|
160
|
+
#captureChunkBytes = 0;
|
|
161
|
+
#captureSampleRate = 16000;
|
|
162
|
+
#captureChannels = 1;
|
|
163
|
+
#captureFramesPerChunk = 320;
|
|
164
|
+
#feeder = null;
|
|
165
|
+
|
|
166
|
+
constructor(config = {}) {
|
|
167
|
+
super(); // JAP@Add --- VoipClient now emits 'incoming-call' / 'outgoing-call' / 'call-ended'
|
|
168
|
+
this.#config = config;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
connectWithSocket = async (existingSock) => {
|
|
172
|
+
this.#sock = existingSock;
|
|
173
|
+
await this.#initVoipStack();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
#initVoipStack = async () => {
|
|
177
|
+
this.#signaling = new SignalingBridge({
|
|
178
|
+
sock: this.#sock,
|
|
179
|
+
// JAP@Add --- surface inbound offers as 'incoming-call' events
|
|
180
|
+
onIncomingCall: (info) => this.#handleIncomingCall(info),
|
|
181
|
+
});
|
|
182
|
+
await this.#signaling.init();
|
|
183
|
+
|
|
184
|
+
this.#relay = this.#config.relay ?? new RelayRtcTransport({
|
|
185
|
+
onTransportMessage: (data, ip, port) => this.#engine?.handleOnTransportMessage(data, ip, port),
|
|
186
|
+
onIceRtt: (rttMs, ip, port) => this.#engine?.updateIceRtt(rttMs, ip, port),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// JAP@Add --- `config.engine`/`config.relay` injection (tests + custom stacks)
|
|
190
|
+
this.#engine = this.#config.engine ?? new WasmEngine({
|
|
191
|
+
resourcesPath: this.#config.resourcesPath,
|
|
192
|
+
callbacks: {
|
|
193
|
+
onSignalingXmpp: (peerJid, callId, xmlPayload) =>
|
|
194
|
+
this.#signaling.sendSignaling(peerJid, callId, xmlPayload),
|
|
195
|
+
onCallEvent: (eventType, eventData) => this.#handleCallEvent(eventType, eventData),
|
|
196
|
+
sendDataToRelay: (data, ip, port) => this.#relay.send(data, ip, port),
|
|
197
|
+
onAudioCaptureInit: (config) => this.#handleAudioCaptureInit(config),
|
|
198
|
+
onAudioCaptureStart: () => this.#handleAudioCaptureStart(),
|
|
199
|
+
onAudioCaptureStop: () => this.#handleAudioCaptureStop(),
|
|
200
|
+
onAudioPlaybackData: (audioData) => this.#activeCall?._emitAudio(audioData),
|
|
201
|
+
cryptoHkdf: computeHkdf,
|
|
202
|
+
hmacSha256: computeHmacSha256,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await this.#engine.initialize();
|
|
207
|
+
this.#signaling.attachEngine(this.#engine);
|
|
208
|
+
|
|
209
|
+
const selfPnJid = this.#sock.authState.creds.me?.id;
|
|
210
|
+
const selfLidJid = this.#sock.authState.creds.me?.lid;
|
|
211
|
+
this.#engine.initVoipStack(selfPnJid, toBareJid(selfPnJid), selfLidJid);
|
|
212
|
+
await this.#engine.waitForVoipStackReady();
|
|
213
|
+
try { this.#engine.updateNetworkMedium(2, 0); } catch {}
|
|
214
|
+
|
|
215
|
+
this.#sock.ws.on("CB:call", (node) => {
|
|
216
|
+
this.#signaling.processIncomingCall(node, this.#engine, this.#activeCall?.callId ?? "");
|
|
217
|
+
});
|
|
218
|
+
this.#sock.ws.on("CB:receipt", (node) => {
|
|
219
|
+
if (!isCallReceiptNode(node)) return;
|
|
220
|
+
this.#signaling.processIncomingReceipt(node, this.#engine, this.#activeCall?.callId ?? "");
|
|
221
|
+
});
|
|
222
|
+
// JAP@Add --- socket-drop recovery: end media cleanly, re-announce on reconnect
|
|
223
|
+
try {
|
|
224
|
+
this.#sock.ev.on("connection.update", ({ connection }) => {
|
|
225
|
+
if (connection === "close" && this.#activeCall) {
|
|
226
|
+
this.#activeCall._forceEnd("socket_closed");
|
|
227
|
+
} else if (connection === "open") {
|
|
228
|
+
this.emit("voip-ready", {});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
} catch {}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/** JAP@Add --- true while a call object exists (dialing, ringing or active). */
|
|
235
|
+
isBusy = () => !!this.#activeCall;
|
|
236
|
+
|
|
237
|
+
/** JAP@Add --- the current ActiveCall, or null when idle. */
|
|
238
|
+
getActiveCall = () => this.#activeCall;
|
|
239
|
+
|
|
240
|
+
#handleIncomingCall = (info) => {
|
|
241
|
+
this.emit("incoming-call", { ...info, busy: this.isBusy() });
|
|
242
|
+
if (info.busy || info.ignored) return;
|
|
243
|
+
// JAP@Add --- optional auto-reject
|
|
244
|
+
if (this.#config?.autoReject) {
|
|
245
|
+
void this.rejectCall(info.callId, info.from).catch(() => {});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// JAP@Add --- optional auto-answer (1:1) / auto-join (group)
|
|
249
|
+
if (this.#config?.autoAnswer && !info.isGroupCall) {
|
|
250
|
+
void this.answerCall(info.callId, { audioSource: this.#config.audioSource }).catch(() => {});
|
|
251
|
+
} else if (this.#config?.autoJoinGroup && info.isGroupCall) {
|
|
252
|
+
void this.joinGroupCall(info.callId, { audioSource: this.#config.audioSource }).catch(() => {});
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/** JAP@Add --- pending inbound offers (answers must come from here). */
|
|
257
|
+
getPendingCalls = () => this.#signaling?.listPendingOffers?.() ?? [];
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* JAP@Add --- decline an inbound call (delegates to the socket's `rejectCall`)
|
|
261
|
+
* and optionally send a text reply to the caller.
|
|
262
|
+
*/
|
|
263
|
+
rejectCall = async (callId, callFrom, { text } = {}) => {
|
|
264
|
+
if (!this.#sock) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
265
|
+
await this.#sock.rejectCall(callId, callFrom);
|
|
266
|
+
const replyText = text ?? this.#config?.autoRejectText;
|
|
267
|
+
if (replyText) {
|
|
268
|
+
try { await this.#sock.sendMessage(callFrom, { text: replyText }); } catch {}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* JAP@Add --- answer an inbound 1:1 call. The offer must still be pending
|
|
274
|
+
* (see `getPendingCalls()`); offers expire after `offerTtlMs` (default 45s).
|
|
275
|
+
*/
|
|
276
|
+
answerCall = async (callId, { audioSource = "silence", durationMs = 120_000, isMicEnabled = true } = {}) => {
|
|
277
|
+
if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
278
|
+
if (this.#activeCall) throw new Error("A call is already active.");
|
|
279
|
+
const offer = this.#signaling.takePendingOffer(callId);
|
|
280
|
+
if (!offer) throw new Error(`No pending inbound offer for ${callId} (expired, answered, or unknown)`);
|
|
281
|
+
const ttl = this.#config?.offerTtlMs ?? 45_000;
|
|
282
|
+
if (Date.now() - offer.receivedAt > ttl) throw new Error(`Inbound offer ${callId} expired`);
|
|
283
|
+
if (offer.isGroupCall) throw new Error(`Offer ${callId} is a group call — use joinGroupCall() instead`);
|
|
284
|
+
const call = new ActiveCall(callId, this.#engine, durationMs, { direction: "inbound", from: offer.from });
|
|
285
|
+
call._audioSource = audioSource;
|
|
286
|
+
this.#activeCall = call;
|
|
287
|
+
this.#lastPeer = { peerJid: offer.from, callId };
|
|
288
|
+
call.once("ended", (reason) => {
|
|
289
|
+
if (this.#activeCall === call) this.#activeCall = null;
|
|
290
|
+
this.#stopWatchdog();
|
|
291
|
+
this.emit("call-ended", { callId, reason });
|
|
292
|
+
});
|
|
293
|
+
this.#engine.acceptCall({ isMicEnabled });
|
|
294
|
+
call._updateState(CallState.AcceptSent);
|
|
295
|
+
this.emit("call-answered", { callId, from: offer.from });
|
|
296
|
+
this.#startWatchdog();
|
|
297
|
+
return call;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* JAP@Add --- join an inbound group call (offer must be pending).
|
|
302
|
+
*/
|
|
303
|
+
joinGroupCall = async (callId, { audioSource = "silence", durationMs = 120_000, hasVideo = false, joinAndAccept = true, chatName = "" } = {}) => {
|
|
304
|
+
if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
305
|
+
if (this.#activeCall) throw new Error("A call is already active.");
|
|
306
|
+
const offer = this.#signaling.takePendingOffer(callId);
|
|
307
|
+
if (!offer) throw new Error(`No pending group offer for ${callId} (expired, joined, or unknown)`);
|
|
308
|
+
const ttl = this.#config?.offerTtlMs ?? 45_000;
|
|
309
|
+
if (Date.now() - offer.receivedAt > ttl) throw new Error(`Group offer ${callId} expired`);
|
|
310
|
+
const call = new ActiveCall(callId, this.#engine, durationMs, { direction: "group-inbound", from: offer.from, groupJid: offer.groupJid });
|
|
311
|
+
call._audioSource = audioSource;
|
|
312
|
+
this.#activeCall = call;
|
|
313
|
+
this.#lastPeer = { peerJid: offer.from, callId };
|
|
314
|
+
this.#lastGroupJoin = {
|
|
315
|
+
callId, callCreatorJid: offer.callCreator, initialPeerJid: offer.from,
|
|
316
|
+
hasVideo, groupJid: offer.groupJid, joinAndAccept, chatName,
|
|
317
|
+
};
|
|
318
|
+
call.once("ended", (reason) => {
|
|
319
|
+
if (this.#activeCall === call) this.#activeCall = null;
|
|
320
|
+
this.#stopWatchdog();
|
|
321
|
+
this.emit("call-ended", { callId, reason });
|
|
322
|
+
});
|
|
323
|
+
this.#engine.joinOngoingCall({ ...this.#lastGroupJoin });
|
|
324
|
+
this.emit("group-call-joined", { callId, groupJid: offer.groupJid, from: offer.from });
|
|
325
|
+
this.#startWatchdog();
|
|
326
|
+
return call;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* JAP@Add --- start a new outbound group call.
|
|
331
|
+
* `participants`: phone numbers (digits) — LIDs are resolved automatically.
|
|
332
|
+
*/
|
|
333
|
+
startGroupCall = async (groupJid, participants = [], { audioSource = "silence", durationMs = 120_000, useVideo = false, chatName = "" } = {}) => {
|
|
334
|
+
if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
335
|
+
if (this.#activeCall) throw new Error("A call is already active.");
|
|
336
|
+
if (!groupJid || !participants.length) throw new Error("startGroupCall(groupJid, participants) needs a group + at least 1 participant");
|
|
337
|
+
const pnJids = participants.map((p) => `${String(p).replace(/\D/g, "")}@s.whatsapp.net`);
|
|
338
|
+
const lidJids = [];
|
|
339
|
+
for (const pn of pnJids) {
|
|
340
|
+
try { await this.#sock.presenceSubscribe(pn); } catch {}
|
|
341
|
+
try {
|
|
342
|
+
const lid = await this.#signaling.resolveLid(pn);
|
|
343
|
+
if (lid) lidJids.push(lid);
|
|
344
|
+
} catch {}
|
|
345
|
+
}
|
|
346
|
+
const deviceJids = [];
|
|
347
|
+
for (const lid of lidJids.length ? lidJids : pnJids) {
|
|
348
|
+
try {
|
|
349
|
+
const devs = await this.#signaling.discoverPeerDevices(lid);
|
|
350
|
+
deviceJids.push(...devs);
|
|
351
|
+
} catch {}
|
|
352
|
+
}
|
|
353
|
+
await this.#signaling.ensureSessionsForPeers(deviceJids.length ? deviceJids : pnJids);
|
|
354
|
+
const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
|
|
355
|
+
const call = new ActiveCall(callId, this.#engine, durationMs, { direction: "group-outbound", groupJid });
|
|
356
|
+
call._audioSource = audioSource;
|
|
357
|
+
this.#activeCall = call;
|
|
358
|
+
this.#lastPeer = { peerJid: groupJid, callId };
|
|
359
|
+
call.once("ended", (reason) => {
|
|
360
|
+
if (this.#activeCall === call) this.#activeCall = null;
|
|
361
|
+
this.#stopWatchdog();
|
|
362
|
+
this.emit("call-ended", { callId, reason });
|
|
363
|
+
});
|
|
364
|
+
this.emit("group-call-started", { callId, groupJid });
|
|
365
|
+
this.#engine.startGroupCall({ pnUserJids: pnJids, lidUserJids: lidJids, deviceJidsCsv: deviceJids.slice(0, 32), callId, useVideo, groupJid, chatName });
|
|
366
|
+
this.#startWatchdog();
|
|
367
|
+
return call;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/** JAP@Add --- rejoin the last group call (recovery after a drop). */
|
|
371
|
+
rejoinGroupCall = async (overrides = {}) => {
|
|
372
|
+
if (!this.#engine) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
373
|
+
if (this.#activeCall) throw new Error("A call is already active.");
|
|
374
|
+
if (!this.#lastGroupJoin) throw new Error("No previous group call to rejoin");
|
|
375
|
+
const params = { ...this.#lastGroupJoin, ...overrides };
|
|
376
|
+
const call = new ActiveCall(params.callId, this.#engine, overrides.durationMs ?? 120_000, { direction: "group-inbound", groupJid: params.groupJid });
|
|
377
|
+
call._audioSource = overrides.audioSource ?? "silence";
|
|
378
|
+
this.#activeCall = call;
|
|
379
|
+
call.once("ended", (reason) => {
|
|
380
|
+
if (this.#activeCall === call) this.#activeCall = null;
|
|
381
|
+
this.#stopWatchdog();
|
|
382
|
+
this.emit("call-ended", { callId: params.callId, reason });
|
|
383
|
+
});
|
|
384
|
+
this.#engine.joinOngoingCall(params);
|
|
385
|
+
this.emit("group-call-rejoined", { callId: params.callId, groupJid: params.groupJid });
|
|
386
|
+
this.#startWatchdog();
|
|
387
|
+
return call;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/** JAP@Add --- invite a participant into the active group call. */
|
|
391
|
+
inviteToGroupCall = async (phoneNumber, deviceJids = []) => {
|
|
392
|
+
if (!this.#activeCall) throw new Error("No active call");
|
|
393
|
+
const targetNumber = String(phoneNumber).replace(/\D/g, "");
|
|
394
|
+
const lid = await this.#signaling.resolveLid(`${targetNumber}@s.whatsapp.net`).catch(() => "");
|
|
395
|
+
return this.#activeCall.invite(`${targetNumber}@s.whatsapp.net`, lid || "", deviceJids);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/** JAP@Add --- kick a participant from the active group call. */
|
|
399
|
+
removeGroupParticipant = async (peerJid) => {
|
|
400
|
+
if (!this.#activeCall) throw new Error("No active call");
|
|
401
|
+
return this.#activeCall.removeParticipant(peerJid);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/** JAP@Add --- call-link preview/join (scheduled / lobby calls). */
|
|
405
|
+
previewCallLink = (token, opts = {}) => {
|
|
406
|
+
if (!this.#engine) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
407
|
+
return this.#engine.previewCallLink({ token, ...opts });
|
|
408
|
+
};
|
|
409
|
+
joinCallLink = () => {
|
|
410
|
+
if (!this.#engine) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
411
|
+
return this.#engine.joinCallLink();
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/** JAP@Add --- combined engine + relay stats snapshot. */
|
|
415
|
+
getStats = () => {
|
|
416
|
+
let call = null;
|
|
417
|
+
try { call = this.#engine?.getCallInfo?.() ?? null; } catch {}
|
|
418
|
+
let relay = null;
|
|
419
|
+
try { relay = this.#relay?.getStats?.() ?? null; } catch {}
|
|
420
|
+
return { busy: this.isBusy(), callId: this.#activeCall?.callId ?? null, call, relay };
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* JAP@Add --- media-path recovery: re-send the crypto rekey + the call offer.
|
|
425
|
+
* Called automatically by the watchdog; safe to call manually too.
|
|
426
|
+
*/
|
|
427
|
+
recoverCall = async ({ peerJid, callId, retryCount = 1 } = {}) => {
|
|
428
|
+
const target = peerJid || this.#lastPeer?.peerJid;
|
|
429
|
+
const id = callId || this.#lastPeer?.callId || this.#activeCall?.callId;
|
|
430
|
+
if (!target || !id) throw new Error("recoverCall needs a peer (no call on record)");
|
|
431
|
+
const out = { rekey: false, offer: false };
|
|
432
|
+
try { this.#engine.resendEncRekeyRetry(target, retryCount); out.rekey = true; } catch {}
|
|
433
|
+
try { this.#engine.resendOfferOnDecryptionFailure(target, id); out.offer = true; } catch {}
|
|
434
|
+
this.emit("call-recovery", { peerJid: target, callId: id, ...out });
|
|
435
|
+
return out;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
call = async (phoneNumber, opts = {}) => {
|
|
439
|
+
if (!this.#engine || !this.#signaling) throw new Error("Not connected. Call connectWithSocket() first.");
|
|
440
|
+
if (this.#activeCall) throw new Error("A call is already active.");
|
|
441
|
+
|
|
442
|
+
const targetNumber = phoneNumber.replace(/\D/g, "");
|
|
443
|
+
const targetPnJid = `${targetNumber}@s.whatsapp.net`;
|
|
444
|
+
const durationMs = opts.durationMs ?? 120_000;
|
|
445
|
+
const audioSource = opts.audioSource ?? "silence";
|
|
446
|
+
|
|
447
|
+
const peerLid = await this.#signaling.resolveLid(targetPnJid);
|
|
448
|
+
if (!peerLid) throw new Error(`Could not resolve LID for ${targetPnJid}`);
|
|
449
|
+
|
|
450
|
+
for (const jid of [targetPnJid, peerLid]) {
|
|
451
|
+
try { await this.#sock.presenceSubscribe(jid); } catch {}
|
|
452
|
+
}
|
|
453
|
+
await new Promise((r) => setTimeout(r, 750));
|
|
454
|
+
|
|
455
|
+
const peerDeviceJids = await this.#signaling.discoverPeerDevices(peerLid);
|
|
456
|
+
const deviceList = peerDeviceJids.length ? peerDeviceJids : [toBareJid(peerLid)];
|
|
457
|
+
|
|
458
|
+
await this.#signaling.ensureSessionsForPeers(deviceList);
|
|
459
|
+
|
|
460
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
461
|
+
await this.#signaling.issueTcToken(peerLid);
|
|
462
|
+
const tcToken = await this.#signaling.ensureTcToken(peerLid, targetPnJid);
|
|
463
|
+
|
|
464
|
+
const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
|
|
465
|
+
|
|
466
|
+
const call = new ActiveCall(callId, this.#engine, durationMs);
|
|
467
|
+
call._audioSource = audioSource;
|
|
468
|
+
this.#activeCall = call;
|
|
469
|
+
// JAP@Fix: release the slot when the call ends (was never cleared → isBusy stuck true)
|
|
470
|
+
call.once("ended", (reason) => {
|
|
471
|
+
if (this.#activeCall === call) this.#activeCall = null;
|
|
472
|
+
this.#stopWatchdog();
|
|
473
|
+
this.emit("call-ended", { callId, reason });
|
|
474
|
+
});
|
|
475
|
+
this.emit("outgoing-call", { callId, to: targetPnJid });
|
|
476
|
+
this.#lastPeer = { peerJid: peerLid, callId };
|
|
477
|
+
this.#startWatchdog();
|
|
478
|
+
|
|
479
|
+
this.#engine.startCall({
|
|
480
|
+
peerJid: peerLid,
|
|
481
|
+
peerPn: targetPnJid,
|
|
482
|
+
peerList: deviceList,
|
|
483
|
+
callId,
|
|
484
|
+
isVideo: false,
|
|
485
|
+
isLidCall: true,
|
|
486
|
+
isFromDialer: false,
|
|
487
|
+
extraData: tcToken,
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
return call;
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
#startWatchdog = () => {
|
|
494
|
+
this.#stopWatchdog();
|
|
495
|
+
const intervalMs = this.#config?.watchdogIntervalMs ?? 5000;
|
|
496
|
+
const maxSilent = this.#config?.watchdogMaxSilent ?? 3;
|
|
497
|
+
if (!intervalMs || !maxSilent) return;
|
|
498
|
+
this.#watchdogMisses = 0;
|
|
499
|
+
this.#watchdogTimer = setInterval(() => {
|
|
500
|
+
if (!this.#activeCall) { this.#stopWatchdog(); return; }
|
|
501
|
+
let open = -1;
|
|
502
|
+
try { open = this.#relay?.getStats?.()?.openConnections ?? -1; } catch {}
|
|
503
|
+
if (open === 0) {
|
|
504
|
+
this.#watchdogMisses += 1;
|
|
505
|
+
} else {
|
|
506
|
+
this.#watchdogMisses = 0;
|
|
507
|
+
}
|
|
508
|
+
if (this.#watchdogMisses >= maxSilent) {
|
|
509
|
+
this.#watchdogMisses = 0;
|
|
510
|
+
this.emit("call-degraded", { callId: this.#activeCall.callId });
|
|
511
|
+
void this.recoverCall({}).catch(() => {});
|
|
512
|
+
}
|
|
513
|
+
}, intervalMs);
|
|
514
|
+
this.#watchdogTimer.unref?.();
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
#stopWatchdog = () => {
|
|
518
|
+
if (this.#watchdogTimer) { clearInterval(this.#watchdogTimer); this.#watchdogTimer = null; }
|
|
519
|
+
this.#watchdogMisses = 0;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
disconnect = () => {
|
|
523
|
+
this.#stopWatchdog();
|
|
524
|
+
this.#activeCall?._forceEnd("disconnect");
|
|
525
|
+
this.#activeCall = null;
|
|
526
|
+
this.#relay?.closeAll();
|
|
527
|
+
this.#engine?.destroy();
|
|
528
|
+
this.#engine = null;
|
|
529
|
+
this.#relay = null;
|
|
530
|
+
this.#signaling = null;
|
|
531
|
+
this.#sock = null;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
#handleCallEvent = (eventType, eventData) => {
|
|
535
|
+
if (eventType === 16 && eventData) {
|
|
536
|
+
try {
|
|
537
|
+
const parsed = JSON.parse(eventData);
|
|
538
|
+
const info = parsed.call_info ?? parsed.callInfo ?? {};
|
|
539
|
+
const callState = Number(info.call_state ?? info.callState ?? 0);
|
|
540
|
+
this.#activeCall?._updateState(callState);
|
|
541
|
+
} catch {}
|
|
542
|
+
} else if (eventType === 156 && eventData) {
|
|
543
|
+
try {
|
|
544
|
+
const update = JSON.parse(eventData);
|
|
545
|
+
this.#relay?.updateRelayList(update);
|
|
546
|
+
} catch {}
|
|
547
|
+
} else if (eventType === 2) {
|
|
548
|
+
this.#activeCall?._forceEnd("remote_end");
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
#handleAudioCaptureInit = (config) => {
|
|
553
|
+
if (!this.#engine) return;
|
|
554
|
+
this.#captureSampleRate = config.sampleRate || 16000;
|
|
555
|
+
this.#captureChannels = config.channels || 1;
|
|
556
|
+
this.#captureFramesPerChunk = config.framesPerChunk || 320;
|
|
557
|
+
const chunkSamples = this.#captureFramesPerChunk * this.#captureChannels;
|
|
558
|
+
this.#captureChunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
|
|
559
|
+
this.#capturePtr = this.#engine.malloc(this.#captureChunkBytes);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
#handleAudioCaptureStart = () => {
|
|
563
|
+
if (!this.#engine || !this.#capturePtr) return;
|
|
564
|
+
const audioSource = this.#activeCall?._audioSource ?? "silence";
|
|
565
|
+
this.#feeder = new AudioFeeder(
|
|
566
|
+
this.#captureSampleRate,
|
|
567
|
+
this.#captureChannels,
|
|
568
|
+
this.#captureFramesPerChunk,
|
|
569
|
+
(chunk) => {
|
|
570
|
+
if (this.#engine && this.#capturePtr) this.#engine.sendAudioData(chunk, this.#capturePtr);
|
|
571
|
+
},
|
|
572
|
+
audioSource,
|
|
573
|
+
);
|
|
574
|
+
this.#feeder.start();
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
#handleAudioCaptureStop = () => {
|
|
578
|
+
this.#feeder?.stop();
|
|
579
|
+
this.#feeder = null;
|
|
580
|
+
if (this.#engine && this.#capturePtr) {
|
|
581
|
+
try { this.#engine.free(this.#capturePtr); } catch {}
|
|
582
|
+
this.#capturePtr = 0;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* JAP@Add --- one-liner VoIP setup: creates a VoipClient, binds it to an active
|
|
590
|
+
* Baileys socket, stores it as `sock.voip`, and returns it.
|
|
591
|
+
*
|
|
592
|
+
* ```js
|
|
593
|
+
* import { attachVoip } from '@japofc/baileys'
|
|
594
|
+
* const voip = await attachVoip(sock, {
|
|
595
|
+
* autoReject: true, // decline inbound calls automatically
|
|
596
|
+
* autoRejectText: 'Bot cannot take calls, please type your message 🙏',
|
|
597
|
+
* // autoAnswer: true, // ...or answer them instead
|
|
598
|
+
* // autoJoinGroup: true, // ...or auto-join group calls
|
|
599
|
+
* })
|
|
600
|
+
* voip.on('incoming-call', ({ from }) => console.log('ringing from', from))
|
|
601
|
+
* const call = await voip.call('62812xxxx', { audioSource: './greeting.mp3' })
|
|
602
|
+
* await call.waitForEnd()
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
605
|
+
export const attachVoip = async (sock, config = {}) => {
|
|
606
|
+
if (!sock?.ws) throw new Error("attachVoip(sock) requires an active Baileys socket.");
|
|
607
|
+
const client = new VoipClient(config);
|
|
608
|
+
await client.connectWithSocket(sock);
|
|
609
|
+
try { sock.voip = client; } catch {}
|
|
610
|
+
return client;
|
|
611
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
type RelayAddress = {
|
|
2
|
+
protocol: number;
|
|
3
|
+
ipv4?: string;
|
|
4
|
+
ipv6?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
port_v6?: number;
|
|
7
|
+
};
|
|
8
|
+
type RelayDescriptor = {
|
|
9
|
+
relay_id: number;
|
|
10
|
+
relay_name: string;
|
|
11
|
+
token_id: number;
|
|
12
|
+
auth_token_id?: number;
|
|
13
|
+
addresses: RelayAddress[];
|
|
14
|
+
};
|
|
15
|
+
export type RelayListUpdatePayload = {
|
|
16
|
+
relay_key: string;
|
|
17
|
+
relay_tokens: string[];
|
|
18
|
+
auth_tokens?: string[];
|
|
19
|
+
enable_edgeray_dtls_active_mode?: boolean;
|
|
20
|
+
relays: RelayDescriptor[];
|
|
21
|
+
};
|
|
22
|
+
export type RelayTransportStats = {
|
|
23
|
+
sentPackets: number;
|
|
24
|
+
receivedPackets: number;
|
|
25
|
+
sentBytes: number;
|
|
26
|
+
receivedBytes: number;
|
|
27
|
+
droppedPackets: number;
|
|
28
|
+
openConnections: number;
|
|
29
|
+
};
|
|
30
|
+
export type RelayTransportConfig = {
|
|
31
|
+
onTransportMessage: (data: Uint8Array, ip: string, port: number) => void;
|
|
32
|
+
onIceRtt?: (rttMs: number, ip: string, port: number) => void;
|
|
33
|
+
};
|
|
34
|
+
export declare class RelayRtcTransport {
|
|
35
|
+
#private;
|
|
36
|
+
private readonly config;
|
|
37
|
+
constructor(config: RelayTransportConfig);
|
|
38
|
+
updateRelayList: (update: RelayListUpdatePayload) => void;
|
|
39
|
+
send: (packet: Uint8Array | Buffer, ip: string, port: number) => number;
|
|
40
|
+
getStats: () => RelayTransportStats;
|
|
41
|
+
closeAll: () => Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export {};
|