ruby-baileys 0.0.0-stage → 1.5.17

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 (296) hide show
  1. package/EXPERIMENTAL.md +350 -0
  2. package/LICENSE +23 -0
  3. package/NOTICE.md +14 -0
  4. package/README.md +6265 -2
  5. package/WAProto/GenerateStatics.sh +3 -0
  6. package/WAProto/WAProto.proto +5479 -0
  7. package/WAProto/fix-imports.js +81 -0
  8. package/WAProto/index.d.ts +16877 -0
  9. package/WAProto/index.js +122159 -0
  10. package/assets/ruby_aspirant.jpg +0 -0
  11. package/assets/ruby_aspirant_4k.jpg +88 -0
  12. package/assets/ruby_aspirant_full.jpg +0 -0
  13. package/assets/ruby_aspirant_render.png +1 -0
  14. package/assets/ruby_aspirants_hd.jpg +0 -0
  15. package/assets/thumb.jpg +0 -0
  16. package/engine-requirements.js +14 -0
  17. package/index.cjs +29 -0
  18. package/index.d.ts +2 -0
  19. package/package.json +178 -4
  20. package/src/Defaults/index.d.ts +153 -0
  21. package/src/Defaults/index.js +165 -0
  22. package/src/MessageBuilder/bot-signature.d.ts +22 -0
  23. package/src/MessageBuilder/bot-signature.js +123 -0
  24. package/src/MessageBuilder/extras.d.ts +396 -0
  25. package/src/MessageBuilder/extras.js +1229 -0
  26. package/src/MessageBuilder/index.d.ts +210 -0
  27. package/src/MessageBuilder/index.js +3521 -0
  28. package/src/MessageBuilder/metaai.d.ts +438 -0
  29. package/src/MessageBuilder/metaai.js +891 -0
  30. package/src/Signal/Group/ciphertext-message.d.ts +10 -0
  31. package/src/Signal/Group/ciphertext-message.js +12 -0
  32. package/src/Signal/Group/group-session-builder.d.ts +8 -0
  33. package/src/Signal/Group/group-session-builder.js +28 -0
  34. package/src/Signal/Group/group_cipher.d.ts +11 -0
  35. package/src/Signal/Group/group_cipher.js +79 -0
  36. package/src/Signal/Group/index.d.ts +12 -0
  37. package/src/Signal/Group/index.js +12 -0
  38. package/src/Signal/Group/keyhelper.d.ts +7 -0
  39. package/src/Signal/Group/keyhelper.js +18 -0
  40. package/src/Signal/Group/sender-chain-key.d.ts +14 -0
  41. package/src/Signal/Group/sender-chain-key.js +26 -0
  42. package/src/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  43. package/src/Signal/Group/sender-key-distribution-message.js +63 -0
  44. package/src/Signal/Group/sender-key-message.d.ts +19 -0
  45. package/src/Signal/Group/sender-key-message.js +66 -0
  46. package/src/Signal/Group/sender-key-name.d.ts +12 -0
  47. package/src/Signal/Group/sender-key-name.js +48 -0
  48. package/src/Signal/Group/sender-key-record.d.ts +13 -0
  49. package/src/Signal/Group/sender-key-record.js +41 -0
  50. package/src/Signal/Group/sender-key-state.d.ts +17 -0
  51. package/src/Signal/Group/sender-key-state.js +84 -0
  52. package/src/Signal/Group/sender-message-key.d.ts +12 -0
  53. package/src/Signal/Group/sender-message-key.js +26 -0
  54. package/src/Signal/libsignal.d.ts +55 -0
  55. package/src/Signal/libsignal.js +429 -0
  56. package/src/Signal/lid-mapping.d.ts +21 -0
  57. package/src/Signal/lid-mapping.js +275 -0
  58. package/src/Socket/Client/index.d.ts +3 -0
  59. package/src/Socket/Client/index.js +3 -0
  60. package/src/Socket/Client/types.d.ts +6 -0
  61. package/src/Socket/Client/types.js +10 -0
  62. package/src/Socket/Client/websocket.d.ts +13 -0
  63. package/src/Socket/Client/websocket.js +54 -0
  64. package/src/Socket/business.d.ts +432 -0
  65. package/src/Socket/business.js +380 -0
  66. package/src/Socket/chats.d.ts +248 -0
  67. package/src/Socket/chats.js +1485 -0
  68. package/src/Socket/communities.d.ts +599 -0
  69. package/src/Socket/communities.js +422 -0
  70. package/src/Socket/groups.d.ts +299 -0
  71. package/src/Socket/groups.js +340 -0
  72. package/src/Socket/index.d.ts +582 -0
  73. package/src/Socket/index.js +48 -0
  74. package/src/Socket/messages-recv.d.ts +371 -0
  75. package/src/Socket/messages-recv.js +1803 -0
  76. package/src/Socket/messages-send.d.ts +391 -0
  77. package/src/Socket/messages-send.js +1575 -0
  78. package/src/Socket/mex.d.ts +2 -0
  79. package/src/Socket/mex.js +45 -0
  80. package/src/Socket/newsletter.d.ts +469 -0
  81. package/src/Socket/newsletter.js +643 -0
  82. package/src/Socket/socket.d.ts +78 -0
  83. package/src/Socket/socket.js +1088 -0
  84. package/src/Store/index.d.ts +4 -0
  85. package/src/Store/index.js +4 -0
  86. package/src/Store/make-in-memory-store.d.ts +63 -0
  87. package/src/Store/make-in-memory-store.js +398 -0
  88. package/src/Store/make-ordered-dictionary.d.ts +13 -0
  89. package/src/Store/make-ordered-dictionary.js +79 -0
  90. package/src/Store/object-repository.d.ts +11 -0
  91. package/src/Store/object-repository.js +24 -0
  92. package/src/Types/Auth.d.ts +2 -0
  93. package/src/Types/Auth.js +2 -0
  94. package/src/Types/Bussines.d.ts +2 -0
  95. package/src/Types/Bussines.js +2 -0
  96. package/src/Types/Call.d.ts +2 -0
  97. package/src/Types/Call.js +2 -0
  98. package/src/Types/Chat.d.ts +3 -0
  99. package/src/Types/Chat.js +25 -0
  100. package/src/Types/Contact.d.ts +2 -0
  101. package/src/Types/Contact.js +2 -0
  102. package/src/Types/Events.d.ts +2 -0
  103. package/src/Types/Events.js +1 -0
  104. package/src/Types/GroupMetadata.d.ts +6 -0
  105. package/src/Types/GroupMetadata.js +7 -0
  106. package/src/Types/Label.d.ts +3 -0
  107. package/src/Types/Label.js +25 -0
  108. package/src/Types/LabelAssociation.d.ts +3 -0
  109. package/src/Types/LabelAssociation.js +7 -0
  110. package/src/Types/Message.d.ts +14 -0
  111. package/src/Types/Message.js +24 -0
  112. package/src/Types/Mex.d.ts +3 -0
  113. package/src/Types/Mex.js +96 -0
  114. package/src/Types/Product.d.ts +2 -0
  115. package/src/Types/Product.js +2 -0
  116. package/src/Types/Signal.d.ts +2 -0
  117. package/src/Types/Signal.js +1 -0
  118. package/src/Types/Socket.d.ts +2 -0
  119. package/src/Types/Socket.js +1 -0
  120. package/src/Types/State.d.ts +6 -0
  121. package/src/Types/State.js +55 -0
  122. package/src/Types/USync.d.ts +2 -0
  123. package/src/Types/USync.js +1 -0
  124. package/src/Types/index.d.ts +14 -0
  125. package/src/Types/index.js +26 -0
  126. package/src/Utils/auth-store.d.ts +30 -0
  127. package/src/Utils/auth-store.js +77 -0
  128. package/src/Utils/auth-utils.d.ts +59 -0
  129. package/src/Utils/auth-utils.js +274 -0
  130. package/src/Utils/browser-utils.d.ts +10 -0
  131. package/src/Utils/browser-utils.js +29 -0
  132. package/src/Utils/business.d.ts +50 -0
  133. package/src/Utils/business.js +227 -0
  134. package/src/Utils/chat-utils.d.ts +409 -0
  135. package/src/Utils/chat-utils.js +847 -0
  136. package/src/Utils/companion-reg-client-utils.d.ts +5 -0
  137. package/src/Utils/companion-reg-client-utils.js +40 -0
  138. package/src/Utils/crypto.d.ts +40 -0
  139. package/src/Utils/crypto.js +111 -0
  140. package/src/Utils/decode-wa-message.d.ts +100 -0
  141. package/src/Utils/decode-wa-message.js +374 -0
  142. package/src/Utils/event-buffer.d.ts +23 -0
  143. package/src/Utils/event-buffer.js +625 -0
  144. package/src/Utils/generics.d.ts +64 -0
  145. package/src/Utils/generics.js +376 -0
  146. package/src/Utils/group-metadata-cache.d.ts +5 -0
  147. package/src/Utils/group-metadata-cache.js +26 -0
  148. package/src/Utils/history.d.ts +76 -0
  149. package/src/Utils/history.js +132 -0
  150. package/src/Utils/html-app.d.ts +19 -0
  151. package/src/Utils/html-app.js +97 -0
  152. package/src/Utils/html-multiplayer.d.ts +10 -0
  153. package/src/Utils/html-multiplayer.js +95 -0
  154. package/src/Utils/identity-change-handler.d.ts +14 -0
  155. package/src/Utils/identity-change-handler.js +49 -0
  156. package/src/Utils/index.d.ts +45 -0
  157. package/src/Utils/index.js +45 -0
  158. package/src/Utils/link-preview-metadata.d.ts +47 -0
  159. package/src/Utils/link-preview-metadata.js +89 -0
  160. package/src/Utils/link-preview.d.ts +13 -0
  161. package/src/Utils/link-preview.js +80 -0
  162. package/src/Utils/logger.d.ts +3 -0
  163. package/src/Utils/logger.js +3 -0
  164. package/src/Utils/lt-hash.d.ts +7 -0
  165. package/src/Utils/lt-hash.js +4 -0
  166. package/src/Utils/make-mutex.d.ts +7 -0
  167. package/src/Utils/make-mutex.js +33 -0
  168. package/src/Utils/message-retry-manager.d.ts +80 -0
  169. package/src/Utils/message-retry-manager.js +256 -0
  170. package/src/Utils/messages-media.d.ts +137 -0
  171. package/src/Utils/messages-media.js +815 -0
  172. package/src/Utils/messages.d.ts +63 -0
  173. package/src/Utils/messages.js +2149 -0
  174. package/src/Utils/modern-messages.d.ts +150 -0
  175. package/src/Utils/modern-messages.js +496 -0
  176. package/src/Utils/native-flow.d.ts +11 -0
  177. package/src/Utils/native-flow.js +116 -0
  178. package/src/Utils/newsletter-status.d.ts +158 -0
  179. package/src/Utils/newsletter-status.js +651 -0
  180. package/src/Utils/noise-handler.d.ts +21 -0
  181. package/src/Utils/noise-handler.js +201 -0
  182. package/src/Utils/offline-node-processor.d.ts +10 -0
  183. package/src/Utils/offline-node-processor.js +34 -0
  184. package/src/Utils/optional-media.d.ts +3 -0
  185. package/src/Utils/optional-media.js +46 -0
  186. package/src/Utils/payment-messages.d.ts +66 -0
  187. package/src/Utils/payment-messages.js +161 -0
  188. package/src/Utils/pre-key-manager.d.ts +26 -0
  189. package/src/Utils/pre-key-manager.js +96 -0
  190. package/src/Utils/process-message.d.ts +61 -0
  191. package/src/Utils/process-message.js +689 -0
  192. package/src/Utils/reporting-utils.d.ts +13 -0
  193. package/src/Utils/reporting-utils.js +257 -0
  194. package/src/Utils/scheduled-message.d.ts +51 -0
  195. package/src/Utils/scheduled-message.js +113 -0
  196. package/src/Utils/sender-key-memory.d.ts +20 -0
  197. package/src/Utils/sender-key-memory.js +52 -0
  198. package/src/Utils/signal.d.ts +83 -0
  199. package/src/Utils/signal.js +194 -0
  200. package/src/Utils/stanza-ack.d.ts +17 -0
  201. package/src/Utils/stanza-ack.js +31 -0
  202. package/src/Utils/status-stickers.d.ts +127 -0
  203. package/src/Utils/status-stickers.js +261 -0
  204. package/src/Utils/sync-action-utils.d.ts +3 -0
  205. package/src/Utils/sync-action-utils.js +45 -0
  206. package/src/Utils/tc-token-utils.d.ts +30 -0
  207. package/src/Utils/tc-token-utils.js +156 -0
  208. package/src/Utils/ttl-cache.d.ts +29 -0
  209. package/src/Utils/ttl-cache.js +121 -0
  210. package/src/Utils/use-mongo-auth-state.d.ts +22 -0
  211. package/src/Utils/use-mongo-auth-state.js +68 -0
  212. package/src/Utils/use-multi-file-auth-state.d.ts +12 -0
  213. package/src/Utils/use-multi-file-auth-state.js +107 -0
  214. package/src/Utils/use-mysql-auth-state.d.ts +20 -0
  215. package/src/Utils/use-mysql-auth-state.js +80 -0
  216. package/src/Utils/use-postgres-auth-state.d.ts +20 -0
  217. package/src/Utils/use-postgres-auth-state.js +80 -0
  218. package/src/Utils/use-redis-auth-state.d.ts +20 -0
  219. package/src/Utils/use-redis-auth-state.js +90 -0
  220. package/src/Utils/use-single-file-auth-state.d.ts +11 -0
  221. package/src/Utils/use-single-file-auth-state.js +109 -0
  222. package/src/Utils/use-sqlite-auth-state.d.ts +13 -0
  223. package/src/Utils/use-sqlite-auth-state.js +121 -0
  224. package/src/Utils/useNekoDBAuth.d.ts +19 -0
  225. package/src/Utils/useNekoDBAuth.js +111 -0
  226. package/src/Utils/username.d.ts +12 -0
  227. package/src/Utils/username.js +59 -0
  228. package/src/Utils/validate-connection.d.ts +44 -0
  229. package/src/Utils/validate-connection.js +208 -0
  230. package/src/Utils/voice-recognition.d.ts +53 -0
  231. package/src/Utils/voice-recognition.js +219 -0
  232. package/src/Voip/audio-feeder.d.ts +25 -0
  233. package/src/Voip/audio-feeder.js +269 -0
  234. package/src/Voip/index.d.ts +89 -0
  235. package/src/Voip/index.js +937 -0
  236. package/src/Voip/relay-transport.d.ts +6 -0
  237. package/src/Voip/relay-transport.js +825 -0
  238. package/src/Voip/signaling.d.ts +12 -0
  239. package/src/Voip/signaling.js +1100 -0
  240. package/src/Voip/types.d.ts +3 -0
  241. package/src/Voip/types.js +13 -0
  242. package/src/Voip/video-feeder.d.ts +25 -0
  243. package/src/Voip/video-feeder.js +269 -0
  244. package/src/Voip/wasm-engine.d.ts +27 -0
  245. package/src/Voip/wasm-engine.js +1308 -0
  246. package/src/Voip/worker-bootstrap.js +1024 -0
  247. package/src/WABinary/constants.d.ts +23 -0
  248. package/src/WABinary/constants.js +1301 -0
  249. package/src/WABinary/decode.d.ts +10 -0
  250. package/src/WABinary/decode.js +262 -0
  251. package/src/WABinary/encode.d.ts +3 -0
  252. package/src/WABinary/encode.js +220 -0
  253. package/src/WABinary/generic-utils.d.ts +77 -0
  254. package/src/WABinary/generic-utils.js +214 -0
  255. package/src/WABinary/index.d.ts +6 -0
  256. package/src/WABinary/index.js +6 -0
  257. package/src/WABinary/jid-utils.d.ts +31 -0
  258. package/src/WABinary/jid-utils.js +98 -0
  259. package/src/WABinary/types.d.ts +2 -0
  260. package/src/WABinary/types.js +1 -0
  261. package/src/WAM/BinaryInfo.d.ts +8 -0
  262. package/src/WAM/BinaryInfo.js +10 -0
  263. package/src/WAM/constants.d.ts +34927 -0
  264. package/src/WAM/constants.js +22853 -0
  265. package/src/WAM/encode.d.ts +2 -0
  266. package/src/WAM/encode.js +149 -0
  267. package/src/WAM/index.d.ts +4 -0
  268. package/src/WAM/index.js +4 -0
  269. package/src/WAUSync/Protocols/USyncContactProtocol.d.ts +37 -0
  270. package/src/WAUSync/Protocols/USyncContactProtocol.js +51 -0
  271. package/src/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  272. package/src/WAUSync/Protocols/USyncDeviceProtocol.js +52 -0
  273. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  274. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  275. package/src/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  276. package/src/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  277. package/src/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  278. package/src/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  279. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +39 -0
  280. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  281. package/src/WAUSync/Protocols/UsyncLIDProtocol.d.ts +15 -0
  282. package/src/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  283. package/src/WAUSync/Protocols/index.d.ts +6 -0
  284. package/src/WAUSync/Protocols/index.js +6 -0
  285. package/src/WAUSync/USyncQuery.d.ts +21 -0
  286. package/src/WAUSync/USyncQuery.js +94 -0
  287. package/src/WAUSync/USyncUser.d.ts +17 -0
  288. package/src/WAUSync/USyncUser.js +31 -0
  289. package/src/WAUSync/index.d.ts +4 -0
  290. package/src/WAUSync/index.js +4 -0
  291. package/src/assets/wasm/README.md +76 -0
  292. package/src/assets/wasm/loader.js +5 -0
  293. package/src/assets/wasm/whatsapp.wasm +0 -0
  294. package/src/assets/wasm/worker-modules.js +273 -0
  295. package/src/index.d.ts +49 -0
  296. package/src/index.js +54 -0
@@ -0,0 +1,937 @@
1
+ /* Elaina Baileys maintained distribution. Upstream notices and license are preserved in LICENSE and NOTICE.md. */
2
+ import { EventEmitter } from 'node:events';
3
+ import { createHmac, randomBytes } from 'node:crypto';
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 { VideoFeeder, VIDEO_FORMAT_I420 } from './video-feeder.js';
9
+ import { CallState } from './types.js';
10
+ export { CallState };
11
+
12
+ const SHA256_LEN = 32;
13
+
14
+
15
+ const toBareJid = jid => {
16
+ if (!jid) return jid;
17
+ const value = String(jid);
18
+ const at = value.indexOf("@");
19
+ if (at < 0) return value;
20
+ const user = value.slice(0, at).split(":")[0];
21
+ return `${user}@${value.slice(at + 1)}`;
22
+ };
23
+
24
+
25
+ const computeHkdf = (key, salt, info, length) => {
26
+ const effectiveSalt = salt && salt.length > 0 ? Buffer.from(salt) : Buffer.alloc(SHA256_LEN, 0);
27
+ const prk = createHmac("sha256", effectiveSalt).update(key).digest();
28
+ const blocks = Math.ceil(length / SHA256_LEN);
29
+ const okm = Buffer.alloc(blocks * SHA256_LEN);
30
+ let prev = Buffer.alloc(0);
31
+
32
+ for (let i = 1; i <= blocks; i += 1) {
33
+ prev = createHmac("sha256", prk)
34
+ .update(prev)
35
+ .update(info)
36
+ .update(Buffer.from([i]))
37
+ .digest();
38
+ prev.copy(okm, (i - 1) * SHA256_LEN);
39
+ }
40
+
41
+ return new Uint8Array(okm.buffer, okm.byteOffset, length);
42
+ };
43
+
44
+ const computeHmacSha256 = (data, key) => {
45
+ const result = createHmac("sha256", Buffer.from(key)).update(data).digest();
46
+ return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
47
+ };
48
+
49
+ const isCallReceiptNode = node => {
50
+ if (node?.tag !== "receipt") return false;
51
+ const child = Array.isArray(node.content) ? node.content[0] : null;
52
+ return !!(child?.attrs?.["call-id"] || child?.attrs?.call_id);
53
+ };
54
+
55
+ export class ActiveCall extends EventEmitter {
56
+ constructor(callId, engine, durationMs) {
57
+ super();
58
+ this.callId = callId;
59
+ this.engine = engine;
60
+ this._state = CallState.Idle;
61
+ this._endResolver = undefined;
62
+ this._endPromise = undefined;
63
+ this._endTimer = null;
64
+ this._ended = false;
65
+ this._audioSource = "silence";
66
+
67
+ this.end = () => {
68
+ if (this._ended) return;
69
+ try { this.engine.endCall(0, true); } catch {}
70
+ this._forceEnd("local_end");
71
+ };
72
+
73
+ this.mute = muted => {
74
+ try { this.engine.setMute(muted); } catch {}
75
+ };
76
+
77
+ this.waitForEnd = () => this._endPromise;
78
+
79
+ this._playlist = [];
80
+ this._feeder = null;
81
+ this._idleTimer = null;
82
+ this._endWhenQueueEmpty = true;
83
+ this._idleGraceMs = 0;
84
+
85
+ this._attachFeeder = feeder => {
86
+ this._feeder = feeder;
87
+ };
88
+
89
+ this.play = source => {
90
+ this._playlist = [];
91
+ if (this._feeder) {
92
+ this._feeder.clearPlaylist();
93
+ this._feeder.enqueue(source);
94
+ this._feeder.skip();
95
+ return true;
96
+ }
97
+ this._playlist.push(source);
98
+ return false;
99
+ };
100
+
101
+ this.enqueue = source => {
102
+ this._clearIdleTimer();
103
+ if (this._feeder) {
104
+ return this._feeder.enqueue(source);
105
+ }
106
+ const tracks = Array.isArray(source) ? source : [source];
107
+ for (const track of tracks) {
108
+ if (track) {
109
+ this._playlist.push(track);
110
+ }
111
+ }
112
+ return this._playlist.length;
113
+ };
114
+
115
+ this.skip = () => this._feeder?.skip() ?? false;
116
+
117
+ this.queued = () => this._feeder?.playlistLength ?? this._playlist.length;
118
+
119
+ this.nowPlaying = () => this._feeder?.currentTrack ?? null;
120
+
121
+ this._video = false;
122
+ this._videoSource = "black";
123
+ this._videoPlaylist = [];
124
+ this._videoFeeder = null;
125
+
126
+ this._attachVideoFeeder = feeder => {
127
+ this._videoFeeder = feeder;
128
+ };
129
+
130
+ this.playVideo = source => {
131
+ this._videoPlaylist = [];
132
+ if (this._videoFeeder) {
133
+ this._videoFeeder.clearPlaylist();
134
+ this._videoFeeder.enqueue(source);
135
+ this._videoFeeder.skip();
136
+ return true;
137
+ }
138
+ this._videoPlaylist.push(source);
139
+ return false;
140
+ };
141
+
142
+ this.enqueueVideo = source => {
143
+ if (this._videoFeeder) {
144
+ return this._videoFeeder.enqueue(source);
145
+ }
146
+ const tracks = Array.isArray(source) ? source : [source];
147
+ for (const track of tracks) {
148
+ if (track) {
149
+ this._videoPlaylist.push(track);
150
+ }
151
+ }
152
+ return this._videoPlaylist.length;
153
+ };
154
+
155
+ this.skipVideo = () => this._videoFeeder?.skip() ?? false;
156
+
157
+ this.queuedVideo = () => this._videoFeeder?.playlistLength ?? this._videoPlaylist.length;
158
+
159
+ this.nowPlayingVideo = () => this._videoFeeder?.currentTrack ?? null;
160
+
161
+ this.isVideo = () => this._video;
162
+
163
+ this._screenShare = false;
164
+
165
+ this.startScreenShare = () => {
166
+ if (!this._video) {
167
+ throw new Error("screen share needs a video call: pass video: true, or screenShare: true which implies it");
168
+ }
169
+ this._screenShare = true;
170
+ try {
171
+ return this.engine.startScreenShare();
172
+ }
173
+ catch {
174
+ return undefined;
175
+ }
176
+ };
177
+
178
+ this.stopScreenShare = () => {
179
+ this._screenShare = false;
180
+ try {
181
+ return this.engine.stopScreenShare();
182
+ }
183
+ catch {
184
+ return undefined;
185
+ }
186
+ };
187
+
188
+ this.isScreenShare = () => this._screenShare;
189
+
190
+ this._onVideoTrackStart = track => {
191
+ this.emit("videotrack", track);
192
+ };
193
+
194
+ this._onVideoTrackEnd = track => {
195
+ this.emit("videotrackend", track);
196
+ };
197
+
198
+ this._clearIdleTimer = () => {
199
+ if (this._idleTimer) {
200
+ clearTimeout(this._idleTimer);
201
+ this._idleTimer = null;
202
+ }
203
+ };
204
+
205
+ this._onTrackStart = track => {
206
+ this._clearIdleTimer();
207
+ this.emit("track", track);
208
+ };
209
+
210
+ this._onTrackEnd = track => {
211
+ this.emit("trackend", track);
212
+ };
213
+
214
+ this._onPlaylistIdle = () => {
215
+ this.emit("idle");
216
+ if (!this._endWhenQueueEmpty) {
217
+ return;
218
+ }
219
+ this._clearIdleTimer();
220
+ this._idleTimer = setTimeout(() => {
221
+ this._idleTimer = null;
222
+ if (this._feeder?.playlistLength || this._feeder?.currentTrack) {
223
+ return;
224
+ }
225
+ this.end();
226
+ }, this._idleGraceMs);
227
+ };
228
+
229
+ this._updateState = state => {
230
+ this._state = state;
231
+ if (state === CallState.PreacceptReceived) {
232
+ this.emit("ringing");
233
+ } else if (state === CallState.Active) {
234
+ this.emit("connected");
235
+ if (this._screenShare) {
236
+ try { this.engine.startScreenShare(); } catch {}
237
+ }
238
+ } else if (state === CallState.Idle || state === CallState.Ending) {
239
+ this._forceEnd("ended");
240
+ }
241
+ };
242
+
243
+ this._emitAudio = pcm => {
244
+ this.emit("audio", pcm);
245
+ };
246
+
247
+ this._forceEnd = reason => {
248
+ if (this._ended) return;
249
+ this._ended = true;
250
+ this._clearIdleTimer();
251
+ if (this._endTimer) {
252
+ clearTimeout(this._endTimer);
253
+ this._endTimer = null;
254
+ }
255
+ this.emit("ended", reason);
256
+ this._endResolver(reason);
257
+ };
258
+
259
+ this._endPromise = new Promise(resolve => {
260
+ this._endResolver = resolve;
261
+ });
262
+
263
+ if (durationMs > 0) {
264
+ this._endTimer = setTimeout(() => this.end(), durationMs);
265
+ }
266
+ }
267
+
268
+ get state() {
269
+ return this._state;
270
+ }
271
+ }
272
+
273
+
274
+ export class VoipClient {
275
+ constructor(config = {}) {
276
+ this._config = config;
277
+ this._log = typeof config.logger === 'function'
278
+ ? config.logger
279
+ : config.debug ? (...args) => console.log(...args) : () => {};
280
+ this._engine = null;
281
+ this._relay = null;
282
+ this._signaling = null;
283
+ this._sock = config.socket || config.sock || config.conn || null;
284
+ this._activeCall = null;
285
+ this._externalSocket = !!this._sock;
286
+ this._capturePtr = 0;
287
+ this._captureChunkBytes = 0;
288
+ this._captureSampleRate = 16000;
289
+ this._captureChannels = 1;
290
+ this._captureFramesPerChunk = 320;
291
+ this._feeder = null;
292
+ this._videoFeeder = null;
293
+ this._wsCallHandler = null;
294
+ this._wsReceiptHandler = null;
295
+ this._captureStartRequested = false;
296
+ this._audioStartTimer = null;
297
+
298
+ this.connect = async () => {
299
+ if (!this._sock) {
300
+ throw new Error('VoipClient needs the socket you are already logged in with: new VoipClient({ socket: sock }). It never opens a session of its own, so there is no second pairing or QR scan.');
301
+ }
302
+ if (!this._sock.ws) {
303
+ throw new Error('the socket has no websocket yet; call connect() once connection.update reports "open"');
304
+ }
305
+ await this._initVoipStack();
306
+ };
307
+
308
+ this.attach = async socket => {
309
+ if (!socket) {
310
+ throw new Error('attach needs the reconnected socket');
311
+ }
312
+ this._detachSocketHandlers();
313
+ this._sock = socket;
314
+ this._externalSocket = true;
315
+ this._signaling?.setSocket?.(socket);
316
+ this._bindSocketHandlers();
317
+ };
318
+
319
+ this.call = async (phoneNumber, opts = {}) => {
320
+ if (!this._engine || !this._signaling) {
321
+ throw new Error("Not connected. Call connect() first.");
322
+ }
323
+
324
+ if (this._activeCall) {
325
+ try { this._activeCall._forceEnd("replaced"); } catch {}
326
+ try { this._engine?.endCall?.(0, true); } catch {}
327
+ try { this._feeder?.stop(); } catch {}
328
+ this._feeder = null;
329
+ try { this._videoFeeder?.stop(); } catch {}
330
+ this._videoFeeder = null;
331
+ this._activeCall = null;
332
+ }
333
+
334
+ const targetNumber = phoneNumber.replace(/\D/g, "");
335
+ if (!targetNumber) {
336
+ throw new Error("Nomor target tidak valid.");
337
+ }
338
+
339
+ const targetPnJid = `${targetNumber}@s.whatsapp.net`;
340
+ const durationMs = opts.durationMs ?? 120000;
341
+ const audioSource = opts.audioSource ?? "silence";
342
+
343
+ const peerLid = await this._signaling.resolveLid(targetPnJid);
344
+ if (!peerLid) {
345
+ throw new Error(`Tidak bisa resolve LID untuk ${targetPnJid}. Pastikan target valid dan sudah tersimpan mapping LID/PN atau USync LID bisa diakses.`);
346
+ }
347
+
348
+ const peerJid = peerLid;
349
+ const peerPn = targetPnJid;
350
+ const isLidCall = true;
351
+
352
+ for (const jid of [targetPnJid, peerLid]) {
353
+ try { await this._sock.presenceSubscribe(jid); } catch {}
354
+ }
355
+ await new Promise(resolve => setTimeout(resolve, 150));
356
+
357
+ let peerDeviceJids = [];
358
+ try {
359
+ peerDeviceJids = await this._signaling.discoverPeerDevices(peerLid);
360
+ } catch (err) {
361
+ this._log("[RTC DISCOVER DEVICES ERROR]", err?.message || err);
362
+ }
363
+
364
+ const deviceList = peerDeviceJids.length ? peerDeviceJids : [toBareJid(peerLid)];
365
+
366
+ try {
367
+ await this._signaling.ensureSessionsForPeers(deviceList);
368
+ } catch (err) {
369
+ this._log("[RTC ENSURE SESSIONS WARNING]", err?.message || err);
370
+ }
371
+
372
+ await new Promise(resolve => setTimeout(resolve, 100));
373
+
374
+ let tcToken = null;
375
+ try {
376
+ this._signaling.issueTcToken(peerLid).catch(err => {
377
+ this._log("[RTC ISSUE TC TOKEN ASYNC ERROR]", err?.message || err);
378
+ });
379
+
380
+ tcToken = await Promise.race([
381
+ this._signaling.ensureTcToken(peerLid, targetPnJid).catch(err => {
382
+ this._log("[RTC ENSURE TC TOKEN ERROR]", err?.message || err);
383
+ return null;
384
+ }),
385
+ new Promise(resolve => setTimeout(() => resolve(null), 1500))
386
+ ]);
387
+ } catch (err) {
388
+ this._log("[RTC TC TOKEN FAST PATH ERROR]", err?.message || err);
389
+ }
390
+
391
+ if (!tcToken || !tcToken.length) {
392
+ this._log("[RTC TC TOKEN WARNING] TC token kosong, lanjut startCall dengan token kosong.");
393
+ tcToken = undefined;
394
+ }
395
+
396
+ const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
397
+ const call = new ActiveCall(callId, this._engine, durationMs);
398
+
399
+ call._audioSource = audioSource;
400
+ call._audioStartDelayMs = Number(opts.audioStartDelayMs ?? opts.audioDelayMs ?? 1800) || 0;
401
+ call._playlist = Array.isArray(opts.playlist) ? opts.playlist.filter(Boolean) : [];
402
+ call._endWhenQueueEmpty = opts.endWhenQueueEmpty ?? true;
403
+ call._idleGraceMs = Number(opts.idleGraceMs ?? 0) || 0;
404
+ call._screenShare = !!opts.screenShare;
405
+ call._video = !!(opts.video ?? opts.isVideo) || call._screenShare;
406
+ call._videoSource = opts.videoSource ?? "black";
407
+ call._videoPlaylist = Array.isArray(opts.videoPlaylist) ? opts.videoPlaylist.filter(Boolean) : [];
408
+ this._activeCall = call;
409
+
410
+ call.once("ended", () => {
411
+ if (this._activeCall === call) this._activeCall = null;
412
+ });
413
+
414
+ call.once("error", () => {
415
+ if (this._activeCall === call) this._activeCall = null;
416
+ });
417
+
418
+ this._log("[RTC START CALL PARAMS]", {
419
+ peerJid,
420
+ peerPn,
421
+ peerList: deviceList,
422
+ callId,
423
+ isLidCall,
424
+ targetPnJid,
425
+ peerLid,
426
+ hasTcToken: !!tcToken,
427
+ tcTokenLength: tcToken?.length || 0
428
+ });
429
+
430
+ try {
431
+ const startResult = this._engine.startCall({
432
+ peerJid,
433
+ peerPn,
434
+ peerList: deviceList,
435
+ callId,
436
+ isVideo: call._video,
437
+ isLidCall,
438
+ isFromDialer: false,
439
+ extraData: tcToken
440
+ });
441
+ this._log("[RTC START CALL RESULT]", startResult);
442
+ } catch (err) {
443
+ if (this._activeCall === call) this._activeCall = null;
444
+ try { call._forceEnd("start_call_failed"); } catch {}
445
+ throw err;
446
+ }
447
+
448
+ return call;
449
+ };
450
+
451
+ this.callGroup = async (groupJid, opts = {}) => {
452
+ if (!this._engine || !this._signaling) {
453
+ throw new Error("Not connected. Call connect() first.");
454
+ }
455
+ if (typeof groupJid !== "string" || !groupJid.endsWith("@g.us")) {
456
+ throw new Error(`${JSON.stringify(groupJid)} is not a group jid`);
457
+ }
458
+
459
+ const metadata = opts.metadata ?? await this._sock.groupMetadata(groupJid);
460
+ const selfPn = toBareJid(this._sock.authState?.creds?.me?.id ?? this._sock.user?.id ?? "");
461
+ const selfLid = toBareJid(this._sock.authState?.creds?.me?.lid ?? "");
462
+ const members = (metadata?.participants ?? [])
463
+ .map(participant => toBareJid(participant.id ?? participant.jid ?? ""))
464
+ .filter(jid => jid && jid !== selfPn && jid !== selfLid);
465
+ const wanted = Array.isArray(opts.participants) && opts.participants.length
466
+ ? opts.participants.map(toBareJid)
467
+ : members;
468
+
469
+ if (!wanted.length) {
470
+ throw new Error(`no one to call in ${groupJid}`);
471
+ }
472
+
473
+ const pnUserJids = [];
474
+ const lidUserJids = [];
475
+ const deviceJidsCsv = [];
476
+
477
+ for (const member of wanted) {
478
+ const isLid = member.endsWith("@lid");
479
+ const lid = isLid ? member : await this._signaling.resolveLid(member).catch(() => null);
480
+ const pn = isLid ? "" : member;
481
+ if (pn) {
482
+ pnUserJids.push(pn);
483
+ }
484
+ if (lid) {
485
+ lidUserJids.push(lid);
486
+ let devices = [];
487
+ try {
488
+ devices = await this._signaling.discoverPeerDevices(lid);
489
+ } catch (err) {
490
+ this._log("[RTC GROUP DISCOVER DEVICES ERROR]", member, err?.message || err);
491
+ }
492
+ deviceJidsCsv.push((devices.length ? devices : [lid]).join(","));
493
+ }
494
+ }
495
+
496
+ if (!lidUserJids.length) {
497
+ throw new Error(`could not resolve a LID for anyone in ${groupJid}`);
498
+ }
499
+
500
+ const flatDevices = deviceJidsCsv.join(",").split(",").filter(Boolean);
501
+ try {
502
+ await this._signaling.ensureSessionsForPeers(flatDevices);
503
+ } catch (err) {
504
+ this._log("[RTC GROUP ENSURE SESSIONS WARNING]", err?.message || err);
505
+ }
506
+
507
+ const callId = ("00" + randomBytes(16).toString("hex").slice(2)).toUpperCase();
508
+ const call = new ActiveCall(callId, this._engine, opts.durationMs ?? 120000);
509
+
510
+ call._audioSource = opts.audioSource ?? "silence";
511
+ call._audioStartDelayMs = Number(opts.audioStartDelayMs ?? opts.audioDelayMs ?? 1800) || 0;
512
+ call._playlist = Array.isArray(opts.playlist) ? opts.playlist.filter(Boolean) : [];
513
+ call._endWhenQueueEmpty = opts.endWhenQueueEmpty ?? true;
514
+ call._idleGraceMs = Number(opts.idleGraceMs ?? 0) || 0;
515
+ call._screenShare = !!opts.screenShare;
516
+ call._video = !!(opts.video ?? opts.isVideo) || call._screenShare;
517
+ call._videoSource = opts.videoSource ?? "black";
518
+ call._videoPlaylist = Array.isArray(opts.videoPlaylist) ? opts.videoPlaylist.filter(Boolean) : [];
519
+ call.groupJid = groupJid;
520
+ this._activeCall = call;
521
+
522
+ call.once("ended", () => {
523
+ if (this._activeCall === call) this._activeCall = null;
524
+ });
525
+
526
+ this._log("[RTC START GROUP CALL PARAMS]", { groupJid, callId, pnUserJids, lidUserJids, deviceJidsCsv });
527
+
528
+ try {
529
+ const startResult = this._engine.startGroupCall({
530
+ pnUserJids,
531
+ lidUserJids,
532
+ deviceJidsCsv,
533
+ callId,
534
+ isVideo: call._video,
535
+ groupJid,
536
+ chatName: metadata?.subject ?? ""
537
+ });
538
+ this._log("[RTC START GROUP CALL RESULT]", startResult);
539
+ } catch (err) {
540
+ if (this._activeCall === call) this._activeCall = null;
541
+ try { call._forceEnd("start_group_call_failed"); } catch {}
542
+ throw err;
543
+ }
544
+
545
+ return call;
546
+ };
547
+
548
+ this.joinGroupCall = async (invite, opts = {}) => {
549
+ if (!this._engine || !this._signaling) {
550
+ throw new Error("Not connected. Call connect() first.");
551
+ }
552
+ const callId = invite?.callId ?? invite?.id;
553
+ const groupJid = invite?.groupJid ?? invite?.from;
554
+ const callCreatorJid = invite?.callCreatorJid ?? invite?.creator ?? invite?.from;
555
+ if (!callId || !callCreatorJid) {
556
+ throw new Error("joinGroupCall needs at least { callId, callCreatorJid } from the incoming offer");
557
+ }
558
+
559
+ const call = new ActiveCall(callId, this._engine, opts.durationMs ?? 0);
560
+ call._audioSource = opts.audioSource ?? "silence";
561
+ call._audioStartDelayMs = Number(opts.audioStartDelayMs ?? opts.audioDelayMs ?? 1800) || 0;
562
+ call._playlist = Array.isArray(opts.playlist) ? opts.playlist.filter(Boolean) : [];
563
+ call._endWhenQueueEmpty = opts.endWhenQueueEmpty ?? true;
564
+ call._idleGraceMs = Number(opts.idleGraceMs ?? 0) || 0;
565
+ call._screenShare = !!opts.screenShare;
566
+ call._video = !!(opts.video ?? opts.isVideo) || call._screenShare;
567
+ call._videoSource = opts.videoSource ?? "black";
568
+ call._videoPlaylist = Array.isArray(opts.videoPlaylist) ? opts.videoPlaylist.filter(Boolean) : [];
569
+ call.groupJid = groupJid;
570
+ this._activeCall = call;
571
+
572
+ call.once("ended", () => {
573
+ if (this._activeCall === call) this._activeCall = null;
574
+ });
575
+
576
+ try {
577
+ const result = this._engine.joinOngoingCall({
578
+ callId,
579
+ callCreatorJid,
580
+ initialPeerJid: invite?.initialPeerJid ?? callCreatorJid,
581
+ pnUserJids: invite?.pnUserJids ?? [],
582
+ lidUserJids: invite?.lidUserJids ?? [],
583
+ deviceJidsCsv: invite?.deviceJidsCsv ?? [],
584
+ groupJid: groupJid ?? "",
585
+ isVideo: call._video,
586
+ joinAndAccept: opts.joinAndAccept ?? true,
587
+ chatName: invite?.chatName ?? ""
588
+ });
589
+ this._log("[RTC JOIN CALL RESULT]", result);
590
+ } catch (err) {
591
+ if (this._activeCall === call) this._activeCall = null;
592
+ try { call._forceEnd("join_call_failed"); } catch {}
593
+ throw err;
594
+ }
595
+
596
+ return call;
597
+ };
598
+
599
+ this.acceptCall = (isMicEnabled = true) => this._engine?.acceptCall(isMicEnabled, false);
600
+
601
+ this.disconnect = () => {
602
+ this._activeCall?._forceEnd("disconnect");
603
+ this._activeCall = null;
604
+ this._captureStartRequested = false;
605
+ try { this._feeder?.stop(); } catch {}
606
+ this._feeder = null;
607
+ try { this._videoFeeder?.stop(); } catch {}
608
+ this._videoFeeder = null;
609
+ if (this._audioStartTimer) {
610
+ clearTimeout(this._audioStartTimer);
611
+ this._audioStartTimer = null;
612
+ }
613
+ try { this._signaling?.attachEngine?.(null); } catch {}
614
+ this._detachSocketHandlers();
615
+ this._relay?.closeAll();
616
+ this._engine?.destroy();
617
+ if (!this._externalSocket) {
618
+ this._sock?.end?.();
619
+ }
620
+ this._engine = null;
621
+ this._relay = null;
622
+ this._signaling = null;
623
+ if (!this._externalSocket) {
624
+ this._sock = null;
625
+ }
626
+ };
627
+
628
+ this.resetCallState = () => {
629
+ try { this._activeCall?._forceEnd("reset"); } catch {}
630
+ try { this._engine?.endCall?.(0, true); } catch {}
631
+ try { this._feeder?.stop(); } catch {}
632
+ this._feeder = null;
633
+ try { this._videoFeeder?.stop(); } catch {}
634
+ this._videoFeeder = null;
635
+ if (this._audioStartTimer) {
636
+ clearTimeout(this._audioStartTimer);
637
+ this._audioStartTimer = null;
638
+ }
639
+ this._captureStartRequested = false;
640
+ this._activeCall = null;
641
+ };
642
+
643
+ this._initVoipStack = async () => {
644
+ if (!this._sock) {
645
+ throw new Error("Socket Baileys belum tersedia.");
646
+ }
647
+
648
+ this._signaling = new SignalingBridge({
649
+ sock: this._sock,
650
+ logger: this._config.logger,
651
+ debug: this._config.debug
652
+ });
653
+ await this._signaling.init();
654
+
655
+ this._relay = new RelayRtcTransport({
656
+ onTransportMessage: (data, ip, port) => this._engine?.handleOnTransportMessage(data, ip, port),
657
+ onIceRtt: (rttMs, ip, port) => this._engine?.updateIceRtt(rttMs, ip, port)
658
+ });
659
+
660
+ this._engine = new WasmEngine({
661
+ callbacks: {
662
+ onSignalingXmpp: (peerJid, callId, xmlPayload) => {
663
+ this._log("[RTC WASM SIGNALING XMPP]", {
664
+ peerJid,
665
+ callId,
666
+ payloadLength: xmlPayload?.length || 0
667
+ });
668
+ return this._signaling.sendSignaling(peerJid, callId, xmlPayload);
669
+ },
670
+ onCallEvent: (eventType, eventData) => this._handleCallEvent(eventType, eventData),
671
+ sendDataToRelay: (data, ip, port) => this._relay.send(data, ip, port),
672
+ onAudioCaptureInit: config => this._handleAudioCaptureInit(config),
673
+ onAudioCaptureStart: () => this._handleAudioCaptureStart(),
674
+ onAudioCaptureStop: () => this._handleAudioCaptureStop(),
675
+ onAudioPlaybackData: audioData => this._activeCall?._emitAudio(audioData),
676
+ onVideoCaptureStart: config => this._handleVideoCaptureStart(config),
677
+ onVideoCaptureStop: () => this._handleVideoCaptureStop(),
678
+ onDesktopCaptureStart: config => this._handleDesktopCaptureStart(config),
679
+ onDesktopCaptureStop: () => this._handleDesktopCaptureStop(),
680
+ cryptoHkdf: computeHkdf,
681
+ hmacSha256: computeHmacSha256
682
+ }
683
+ });
684
+
685
+ await this._engine.initialize();
686
+ this._signaling.attachEngine(this._engine);
687
+
688
+ const selfPnJidRaw =
689
+ this._sock.authState?.creds?.me?.id ||
690
+ this._sock.authState?.creds?.me?.jid ||
691
+ this._sock.user?.id ||
692
+ this._sock.user?.jid;
693
+
694
+ const selfPnJid = toBareJid(selfPnJidRaw);
695
+ const selfLidJid =
696
+ this._sock.authState?.creds?.me?.lid ||
697
+ this._sock.authState?.creds?.me?.lidJid ||
698
+ "";
699
+
700
+ if (!selfPnJid) {
701
+ throw new Error("Tidak bisa membaca JID bot dari socket utama.");
702
+ }
703
+
704
+ this._log("[RTC SELF JID]", { selfPnJid, selfLidJid });
705
+
706
+ this._engine.initVoipStack(selfPnJid, selfPnJid, selfLidJid);
707
+ await this._engine.waitForVoipStackReady();
708
+
709
+ try { this._engine.updateNetworkMedium(2, 0); } catch {}
710
+
711
+ this._bindSocketHandlers();
712
+ };
713
+
714
+ this._detachSocketHandlers = () => {
715
+ const ws = this._sock?.ws;
716
+ if (ws?.off) {
717
+ try { if (this._wsCallHandler) ws.off("CB:call", this._wsCallHandler); } catch {}
718
+ try { if (this._wsReceiptHandler) ws.off("CB:receipt", this._wsReceiptHandler); } catch {}
719
+ }
720
+ this._wsCallHandler = null;
721
+ this._wsReceiptHandler = null;
722
+ };
723
+
724
+ this._bindSocketHandlers = () => {
725
+ if (!this._sock?.ws?.on) {
726
+ return;
727
+ }
728
+ this._detachSocketHandlers();
729
+
730
+ this._wsCallHandler = node => {
731
+ const signaling = this._signaling;
732
+ const engine = this._engine;
733
+ if (!signaling || !engine) return;
734
+ if (typeof engine.isInitialized === "function" && !engine.isInitialized()) return;
735
+ signaling.processIncomingCall(node, engine, this._activeCall?.callId ?? "");
736
+ };
737
+
738
+ this._wsReceiptHandler = node => {
739
+ if (!isCallReceiptNode(node)) return;
740
+ const signaling = this._signaling;
741
+ const engine = this._engine;
742
+ if (!signaling || !engine) return;
743
+ if (typeof engine.isInitialized === "function" && !engine.isInitialized()) return;
744
+ signaling.processIncomingReceipt(node, engine, this._activeCall?.callId ?? "");
745
+ };
746
+
747
+ this._sock.ws.on("CB:call", this._wsCallHandler);
748
+ this._sock.ws.on("CB:receipt", this._wsReceiptHandler);
749
+ };
750
+
751
+ this._handleCallEvent = (eventType, eventData) => {
752
+ this._log("[RTC CALL EVENT]", { eventType, hasData: !!eventData, data: eventData });
753
+
754
+ if (eventType === 16 && eventData) {
755
+ try {
756
+ const parsed = JSON.parse(eventData);
757
+ const info = parsed.call_info ?? parsed.callInfo ?? {};
758
+ const callState = Number(info.call_state ?? info.callState ?? 0);
759
+ this._log("[RTC CALL STATE]", callState);
760
+ this._activeCall?._updateState(callState);
761
+ if (callState === CallState.Active) {
762
+ this._captureStartRequested = true;
763
+ setTimeout(() => {
764
+ try { this._startAudioFeeder?.(); } catch (err) {
765
+ this._log("[RTC AUDIO START ON ACTIVE ERROR]", err?.message || err);
766
+ }
767
+ }, 300);
768
+ }
769
+ } catch {}
770
+ } else if (eventType === 156 && eventData) {
771
+ try {
772
+ const update = JSON.parse(eventData);
773
+ this._relay?.updateRelayList(update);
774
+ } catch {}
775
+ } else if (eventType === 2) {
776
+ this._activeCall?._forceEnd("remote_end");
777
+ }
778
+ };
779
+
780
+ this._handleAudioCaptureInit = config => {
781
+ if (!this._engine) return;
782
+ this._captureSampleRate = config.sampleRate || 16000;
783
+ this._captureChannels = config.channels || 1;
784
+ this._captureFramesPerChunk = config.framesPerChunk || 320;
785
+ const chunkSamples = this._captureFramesPerChunk * this._captureChannels;
786
+ this._captureChunkBytes = chunkSamples * Float32Array.BYTES_PER_ELEMENT;
787
+ this._capturePtr = this._engine.malloc(this._captureChunkBytes);
788
+ if (this._captureStartRequested && this._activeCall?.state === CallState.Active) {
789
+ setTimeout(() => {
790
+ try { this._startAudioFeeder?.(); } catch {}
791
+ }, 100);
792
+ }
793
+ };
794
+
795
+ this._startAudioFeeder = () => {
796
+ this._captureStartRequested = true;
797
+
798
+ if (this._activeCall?.state !== CallState.Active) {
799
+ this._log("[RTC AUDIO] capture siap, tunggu call active sebelum audio start");
800
+ return;
801
+ }
802
+
803
+ if (!this._engine || !this._capturePtr) {
804
+ this._log("[RTC AUDIO] capturePtr belum siap, audio feeder batal start");
805
+ return;
806
+ }
807
+
808
+ if (this._feeder) return;
809
+
810
+ const audioDelayMs = Number(this._activeCall?._audioStartDelayMs ?? 0) || 0;
811
+ if (audioDelayMs > 0 && !this._audioStartTimer) {
812
+ this._log("[RTC AUDIO] call active, audio ditunda:", audioDelayMs);
813
+ this._audioStartTimer = setTimeout(() => {
814
+ this._audioStartTimer = null;
815
+ if (this._activeCall?.state === CallState.Active) {
816
+ this._activeCall._audioStartDelayMs = 0;
817
+ this._startAudioFeeder();
818
+ }
819
+ }, audioDelayMs);
820
+ return;
821
+ }
822
+
823
+ if (this._audioStartTimer) return;
824
+
825
+ const call = this._activeCall;
826
+ const playlist = call?._playlist?.length ? [...call._playlist] : [call?._audioSource ?? "silence"];
827
+
828
+ this._log("[RTC AUDIO] start feeder:", {
829
+ playlist,
830
+ sampleRate: this._captureSampleRate,
831
+ channels: this._captureChannels,
832
+ framesPerChunk: this._captureFramesPerChunk
833
+ });
834
+
835
+ this._feeder = new AudioFeeder(
836
+ this._captureSampleRate,
837
+ this._captureChannels,
838
+ this._captureFramesPerChunk,
839
+ chunk => {
840
+ if (this._engine && this._capturePtr && (!this._engine.isInitialized || this._engine.isInitialized())) {
841
+ this._engine.sendAudioData(chunk, this._capturePtr);
842
+ }
843
+ },
844
+ undefined,
845
+ {
846
+ onTrackStart: track => call?._onTrackStart(track),
847
+ onTrackEnd: track => call?._onTrackEnd(track),
848
+ onIdle: () => call?._onPlaylistIdle(),
849
+ ffmpegPath: this._config.ffmpegPath
850
+ }
851
+ );
852
+
853
+ call?._attachFeeder(this._feeder);
854
+ this._feeder.start(playlist);
855
+ };
856
+
857
+ this._startVideoFeeder = (config, useDesktopCapture) => {
858
+ const call = this._activeCall;
859
+ const label = useDesktopCapture ? "SCREEN" : "VIDEO";
860
+ if (!call?._video) {
861
+ this._log(`[RTC ${label}] capture diminta tapi call ini bukan video, diabaikan`);
862
+ return;
863
+ }
864
+ if (this._videoFeeder) {
865
+ return;
866
+ }
867
+ const width = config?.width ?? 640;
868
+ const height = config?.height ?? 480;
869
+ const fps = config?.fps ?? 24;
870
+ const playlist = call._videoPlaylist?.length ? [...call._videoPlaylist] : [call._videoSource ?? "black"];
871
+
872
+ this._log(`[RTC ${label}] start feeder:`, { width, height, fps, playlist });
873
+
874
+ this._videoFeeder = new VideoFeeder(
875
+ width,
876
+ height,
877
+ fps,
878
+ (frame, w, h, rate) => {
879
+ if (this._engine && (!this._engine.isInitialized || this._engine.isInitialized())) {
880
+ this._engine.sendVideoFrame(frame, w, h, rate, VIDEO_FORMAT_I420, 0, useDesktopCapture);
881
+ }
882
+ },
883
+ undefined,
884
+ {
885
+ onTrackStart: track => call._onVideoTrackStart(track),
886
+ onTrackEnd: track => call._onVideoTrackEnd(track),
887
+ ffmpegPath: this._config.ffmpegPath
888
+ }
889
+ );
890
+
891
+ call._attachVideoFeeder(this._videoFeeder);
892
+ this._videoFeeder.start(playlist);
893
+ };
894
+
895
+ this._stopVideoFeeder = label => {
896
+ this._log(`[RTC ${label}] stop feeder`);
897
+ try { this._videoFeeder?.stop(); } catch {}
898
+ this._videoFeeder = null;
899
+ try { this._engine?.releaseVideoFrameBuffer?.(); } catch {}
900
+ };
901
+
902
+ this._handleVideoCaptureStart = config => this._startVideoFeeder(config, false);
903
+ this._handleVideoCaptureStop = () => this._stopVideoFeeder("VIDEO");
904
+ this._handleDesktopCaptureStart = config => this._startVideoFeeder(config, true);
905
+ this._handleDesktopCaptureStop = () => this._stopVideoFeeder("SCREEN");
906
+
907
+ this._handleAudioCaptureStart = () => {
908
+ this._captureStartRequested = true;
909
+ if (this._activeCall?.state === CallState.Active) {
910
+ this._startAudioFeeder();
911
+ } else {
912
+ this._log("[RTC AUDIO] startCaptureJS diterima sebelum active, audio ditahan dulu");
913
+ }
914
+ };
915
+
916
+ this._handleAudioCaptureStop = () => {
917
+ this._captureStartRequested = false;
918
+ this._feeder?.stop();
919
+ this._feeder = null;
920
+ try { this._videoFeeder?.stop(); } catch {}
921
+ this._videoFeeder = null;
922
+ if (this._engine && this._capturePtr) {
923
+ try { this._engine.free(this._capturePtr); } catch {}
924
+ this._capturePtr = 0;
925
+ }
926
+ };
927
+ }
928
+ }
929
+
930
+ export { VideoFeeder, VIDEO_FORMAT_I420 } from './video-feeder.js';
931
+ export { AudioFeeder } from './audio-feeder.js';
932
+ export const makeVoipClient = async (socket, config = {}) => {
933
+ const client = new VoipClient({ ...config, socket });
934
+ await client.connect();
935
+ return client;
936
+ };
937
+ export default VoipClient;