@fer2809fl/baileys 7.0.2 → 7.0.5

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 (267) hide show
  1. package/README.md +409 -32
  2. package/lib/Defaults/index.js +2 -1
  3. package/lib/Modded/message_builder.js +2356 -0
  4. package/lib/Signal/libsignal.js +19 -0
  5. package/lib/Socket/business.d.ts +213 -532
  6. package/lib/Socket/business.js +359 -401
  7. package/lib/Socket/chats.d.ts +30 -2
  8. package/lib/Socket/chats.js +295 -40
  9. package/lib/Socket/groups.d.ts +16 -0
  10. package/lib/Socket/groups.js +101 -18
  11. package/lib/Socket/index.d.ts +17 -0
  12. package/lib/Socket/messages-recv.js +18 -1
  13. package/lib/Socket/messages-send.d.ts +18 -0
  14. package/lib/Socket/messages-send.js +50 -2
  15. package/lib/Socket/newsletter.js +3 -3
  16. package/lib/Socket/socket.js +50 -6
  17. package/lib/Types/Newsletter.d.ts +13 -4
  18. package/lib/Types/Newsletter.js +12 -2
  19. package/lib/Utils/anti-ban.d.ts +41 -0
  20. package/lib/Utils/anti-ban.js +182 -0
  21. package/lib/Utils/banner.d.ts +8 -0
  22. package/lib/Utils/banner.js +76 -0
  23. package/lib/Utils/bot-utils.d.ts +57 -0
  24. package/lib/Utils/bot-utils.js +241 -0
  25. package/lib/Utils/chat-utils.d.ts +34 -4
  26. package/lib/Utils/chat-utils.js +80 -20
  27. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  28. package/lib/Utils/companion-reg-client-utils.js +35 -0
  29. package/lib/Utils/enhanced-cache.d.ts +40 -0
  30. package/lib/Utils/enhanced-cache.js +242 -0
  31. package/lib/Utils/enhanced-logger.d.ts +41 -0
  32. package/lib/Utils/enhanced-logger.js +185 -0
  33. package/lib/Utils/event-buffer.js +49 -1
  34. package/lib/Utils/index.d.ts +14 -0
  35. package/lib/Utils/index.js +14 -0
  36. package/lib/Utils/lid-utils.d.ts +139 -0
  37. package/lib/Utils/lid-utils.js +503 -0
  38. package/lib/Utils/message-queue.d.ts +47 -0
  39. package/lib/Utils/message-queue.js +226 -0
  40. package/lib/Utils/message-retry-manager.js +40 -0
  41. package/lib/Utils/rich-message-utils.d.ts +21 -0
  42. package/lib/Utils/rich-message-utils.js +229 -0
  43. package/lib/Utils/rich-messages.d.ts +52 -0
  44. package/lib/Utils/rich-messages.js +185 -0
  45. package/lib/Utils/scheduled-messages.d.ts +122 -0
  46. package/lib/Utils/scheduled-messages.js +289 -0
  47. package/lib/Utils/smart-reconnect.d.ts +48 -0
  48. package/lib/Utils/smart-reconnect.js +207 -0
  49. package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
  50. package/lib/Utils/use-sqlite-auth-state.js +95 -0
  51. package/lib/VoIP/audio-feeder.d.ts +15 -0
  52. package/lib/VoIP/audio-feeder.js +132 -0
  53. package/lib/VoIP/index.js +277 -0
  54. package/lib/VoIP/relay-transport.d.ts +43 -0
  55. package/lib/VoIP/relay-transport.js +559 -0
  56. package/lib/VoIP/signaling.js +594 -0
  57. package/lib/VoIP/types.d.ts +69 -0
  58. package/lib/VoIP/types.js +17 -0
  59. package/lib/VoIP/wasm-engine.d.ts +103 -0
  60. package/lib/VoIP/wasm-engine.js +1214 -0
  61. package/lib/VoIP/worker-bootstrap.js +1042 -0
  62. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  63. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  64. package/lib/WAUSync/Protocols/index.js +1 -0
  65. package/lib/WAUSync/USyncQuery.js +5 -1
  66. package/lib/assets/wasm/loader.js +5 -0
  67. package/lib/assets/wasm/whatsapp.wasm +0 -0
  68. package/lib/assets/wasm/worker-modules.js +273 -0
  69. package/lib/index.d.ts +41 -0
  70. package/lib/index.js +3 -0
  71. package/package.json +15 -7
  72. package/lib/Defaults/index.d.ts.map +0 -1
  73. package/lib/Defaults/index.js.map +0 -1
  74. package/lib/Signal/Group/ciphertext-message.d.ts.map +0 -1
  75. package/lib/Signal/Group/ciphertext-message.js.map +0 -1
  76. package/lib/Signal/Group/group-session-builder.d.ts.map +0 -1
  77. package/lib/Signal/Group/group-session-builder.js.map +0 -1
  78. package/lib/Signal/Group/group_cipher.d.ts.map +0 -1
  79. package/lib/Signal/Group/group_cipher.js.map +0 -1
  80. package/lib/Signal/Group/index.d.ts.map +0 -1
  81. package/lib/Signal/Group/index.js.map +0 -1
  82. package/lib/Signal/Group/keyhelper.d.ts.map +0 -1
  83. package/lib/Signal/Group/keyhelper.js.map +0 -1
  84. package/lib/Signal/Group/sender-chain-key.d.ts.map +0 -1
  85. package/lib/Signal/Group/sender-chain-key.js.map +0 -1
  86. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +0 -1
  87. package/lib/Signal/Group/sender-key-distribution-message.js.map +0 -1
  88. package/lib/Signal/Group/sender-key-message.d.ts.map +0 -1
  89. package/lib/Signal/Group/sender-key-message.js.map +0 -1
  90. package/lib/Signal/Group/sender-key-name.d.ts.map +0 -1
  91. package/lib/Signal/Group/sender-key-name.js.map +0 -1
  92. package/lib/Signal/Group/sender-key-record.d.ts.map +0 -1
  93. package/lib/Signal/Group/sender-key-record.js.map +0 -1
  94. package/lib/Signal/Group/sender-key-state.d.ts.map +0 -1
  95. package/lib/Signal/Group/sender-key-state.js.map +0 -1
  96. package/lib/Signal/Group/sender-message-key.d.ts.map +0 -1
  97. package/lib/Signal/Group/sender-message-key.js.map +0 -1
  98. package/lib/Signal/libsignal.d.ts.map +0 -1
  99. package/lib/Signal/libsignal.js.map +0 -1
  100. package/lib/Signal/lid-mapping.d.ts.map +0 -1
  101. package/lib/Signal/lid-mapping.js.map +0 -1
  102. package/lib/Socket/Client/index.d.ts.map +0 -1
  103. package/lib/Socket/Client/index.js.map +0 -1
  104. package/lib/Socket/Client/types.d.ts.map +0 -1
  105. package/lib/Socket/Client/types.js.map +0 -1
  106. package/lib/Socket/Client/websocket.d.ts.map +0 -1
  107. package/lib/Socket/Client/websocket.js.map +0 -1
  108. package/lib/Socket/business.d.ts.map +0 -1
  109. package/lib/Socket/business.js.map +0 -1
  110. package/lib/Socket/chats.d.ts.map +0 -1
  111. package/lib/Socket/chats.js.map +0 -1
  112. package/lib/Socket/communities.d.ts.map +0 -1
  113. package/lib/Socket/communities.js.map +0 -1
  114. package/lib/Socket/dugong.d.ts.map +0 -1
  115. package/lib/Socket/dugong.js.map +0 -1
  116. package/lib/Socket/groups.d.ts.map +0 -1
  117. package/lib/Socket/groups.js.map +0 -1
  118. package/lib/Socket/index.d.ts.map +0 -1
  119. package/lib/Socket/index.js.map +0 -1
  120. package/lib/Socket/messages-recv.d.ts.map +0 -1
  121. package/lib/Socket/messages-recv.js.map +0 -1
  122. package/lib/Socket/messages-send.d.ts.map +0 -1
  123. package/lib/Socket/messages-send.js.map +0 -1
  124. package/lib/Socket/mex.d.ts.map +0 -1
  125. package/lib/Socket/mex.js.map +0 -1
  126. package/lib/Socket/newsletter.d.ts.map +0 -1
  127. package/lib/Socket/newsletter.js.map +0 -1
  128. package/lib/Socket/socket.d.ts.map +0 -1
  129. package/lib/Socket/socket.js.map +0 -1
  130. package/lib/Types/Auth.d.ts.map +0 -1
  131. package/lib/Types/Auth.js.map +0 -1
  132. package/lib/Types/Bussines.d.ts.map +0 -1
  133. package/lib/Types/Bussines.js.map +0 -1
  134. package/lib/Types/Call.d.ts.map +0 -1
  135. package/lib/Types/Call.js.map +0 -1
  136. package/lib/Types/Chat.d.ts.map +0 -1
  137. package/lib/Types/Chat.js.map +0 -1
  138. package/lib/Types/Contact.d.ts.map +0 -1
  139. package/lib/Types/Contact.js.map +0 -1
  140. package/lib/Types/Events.d.ts.map +0 -1
  141. package/lib/Types/Events.js.map +0 -1
  142. package/lib/Types/GroupMetadata.d.ts.map +0 -1
  143. package/lib/Types/GroupMetadata.js.map +0 -1
  144. package/lib/Types/Label.d.ts.map +0 -1
  145. package/lib/Types/Label.js.map +0 -1
  146. package/lib/Types/LabelAssociation.d.ts.map +0 -1
  147. package/lib/Types/LabelAssociation.js.map +0 -1
  148. package/lib/Types/Message.d.ts.map +0 -1
  149. package/lib/Types/Message.js.map +0 -1
  150. package/lib/Types/Newsletter.d.ts.map +0 -1
  151. package/lib/Types/Newsletter.js.map +0 -1
  152. package/lib/Types/Product.d.ts.map +0 -1
  153. package/lib/Types/Product.js.map +0 -1
  154. package/lib/Types/Signal.d.ts.map +0 -1
  155. package/lib/Types/Signal.js.map +0 -1
  156. package/lib/Types/Socket.d.ts.map +0 -1
  157. package/lib/Types/Socket.js.map +0 -1
  158. package/lib/Types/State.d.ts.map +0 -1
  159. package/lib/Types/State.js.map +0 -1
  160. package/lib/Types/USync.d.ts.map +0 -1
  161. package/lib/Types/USync.js.map +0 -1
  162. package/lib/Types/index.d.ts.map +0 -1
  163. package/lib/Types/index.js.map +0 -1
  164. package/lib/Utils/auth-utils.d.ts.map +0 -1
  165. package/lib/Utils/auth-utils.js.map +0 -1
  166. package/lib/Utils/browser-utils.d.ts.map +0 -1
  167. package/lib/Utils/browser-utils.js.map +0 -1
  168. package/lib/Utils/business.d.ts.map +0 -1
  169. package/lib/Utils/business.js.map +0 -1
  170. package/lib/Utils/chat-utils.d.ts.map +0 -1
  171. package/lib/Utils/chat-utils.js.map +0 -1
  172. package/lib/Utils/crypto.d.ts.map +0 -1
  173. package/lib/Utils/crypto.js.map +0 -1
  174. package/lib/Utils/decode-wa-message.d.ts.map +0 -1
  175. package/lib/Utils/decode-wa-message.js.map +0 -1
  176. package/lib/Utils/event-buffer.d.ts.map +0 -1
  177. package/lib/Utils/event-buffer.js.map +0 -1
  178. package/lib/Utils/generics.d.ts.map +0 -1
  179. package/lib/Utils/generics.js.map +0 -1
  180. package/lib/Utils/history.d.ts.map +0 -1
  181. package/lib/Utils/history.js.map +0 -1
  182. package/lib/Utils/identity-change-handler.d.ts.map +0 -1
  183. package/lib/Utils/identity-change-handler.js.map +0 -1
  184. package/lib/Utils/index.d.ts.map +0 -1
  185. package/lib/Utils/index.js.map +0 -1
  186. package/lib/Utils/link-preview.d.ts.map +0 -1
  187. package/lib/Utils/link-preview.js.map +0 -1
  188. package/lib/Utils/logger.d.ts.map +0 -1
  189. package/lib/Utils/logger.js.map +0 -1
  190. package/lib/Utils/lt-hash.d.ts.map +0 -1
  191. package/lib/Utils/lt-hash.js.map +0 -1
  192. package/lib/Utils/make-mutex.d.ts.map +0 -1
  193. package/lib/Utils/make-mutex.js.map +0 -1
  194. package/lib/Utils/message-retry-manager.d.ts.map +0 -1
  195. package/lib/Utils/message-retry-manager.js.map +0 -1
  196. package/lib/Utils/messages-media.d.ts.map +0 -1
  197. package/lib/Utils/messages-media.js.map +0 -1
  198. package/lib/Utils/messages.d.ts.map +0 -1
  199. package/lib/Utils/messages.js.map +0 -1
  200. package/lib/Utils/noise-handler.d.ts.map +0 -1
  201. package/lib/Utils/noise-handler.js.map +0 -1
  202. package/lib/Utils/offline-node-processor.d.ts.map +0 -1
  203. package/lib/Utils/offline-node-processor.js.map +0 -1
  204. package/lib/Utils/pre-key-manager.d.ts.map +0 -1
  205. package/lib/Utils/pre-key-manager.js.map +0 -1
  206. package/lib/Utils/process-message.d.ts.map +0 -1
  207. package/lib/Utils/process-message.js.map +0 -1
  208. package/lib/Utils/reporting-utils.d.ts.map +0 -1
  209. package/lib/Utils/reporting-utils.js.map +0 -1
  210. package/lib/Utils/rich-messages.d.ts.map +0 -1
  211. package/lib/Utils/rich-messages.js.map +0 -1
  212. package/lib/Utils/signal.d.ts.map +0 -1
  213. package/lib/Utils/signal.js.map +0 -1
  214. package/lib/Utils/stanza-ack.d.ts.map +0 -1
  215. package/lib/Utils/stanza-ack.js.map +0 -1
  216. package/lib/Utils/sync-action-utils.d.ts.map +0 -1
  217. package/lib/Utils/sync-action-utils.js.map +0 -1
  218. package/lib/Utils/tc-token-utils.d.ts.map +0 -1
  219. package/lib/Utils/tc-token-utils.js.map +0 -1
  220. package/lib/Utils/use-multi-file-auth-state.d.ts.map +0 -1
  221. package/lib/Utils/use-multi-file-auth-state.js.map +0 -1
  222. package/lib/Utils/validate-connection.d.ts.map +0 -1
  223. package/lib/Utils/validate-connection.js.map +0 -1
  224. package/lib/WABinary/constants.d.ts.map +0 -1
  225. package/lib/WABinary/constants.js.map +0 -1
  226. package/lib/WABinary/decode.d.ts.map +0 -1
  227. package/lib/WABinary/decode.js.map +0 -1
  228. package/lib/WABinary/encode.d.ts.map +0 -1
  229. package/lib/WABinary/encode.js.map +0 -1
  230. package/lib/WABinary/generic-utils.d.ts.map +0 -1
  231. package/lib/WABinary/generic-utils.js.map +0 -1
  232. package/lib/WABinary/index.d.ts.map +0 -1
  233. package/lib/WABinary/index.js.map +0 -1
  234. package/lib/WABinary/jid-utils.d.ts.map +0 -1
  235. package/lib/WABinary/jid-utils.js.map +0 -1
  236. package/lib/WABinary/types.d.ts.map +0 -1
  237. package/lib/WABinary/types.js.map +0 -1
  238. package/lib/WAM/BinaryInfo.d.ts.map +0 -1
  239. package/lib/WAM/BinaryInfo.js.map +0 -1
  240. package/lib/WAM/constants.d.ts.map +0 -1
  241. package/lib/WAM/constants.js.map +0 -1
  242. package/lib/WAM/encode.d.ts.map +0 -1
  243. package/lib/WAM/encode.js.map +0 -1
  244. package/lib/WAM/index.d.ts.map +0 -1
  245. package/lib/WAM/index.js.map +0 -1
  246. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +0 -1
  247. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +0 -1
  248. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +0 -1
  249. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +0 -1
  250. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +0 -1
  251. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +0 -1
  252. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +0 -1
  253. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +0 -1
  254. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +0 -1
  255. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +0 -1
  256. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +0 -1
  257. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +0 -1
  258. package/lib/WAUSync/Protocols/index.d.ts.map +0 -1
  259. package/lib/WAUSync/Protocols/index.js.map +0 -1
  260. package/lib/WAUSync/USyncQuery.d.ts.map +0 -1
  261. package/lib/WAUSync/USyncQuery.js.map +0 -1
  262. package/lib/WAUSync/USyncUser.d.ts.map +0 -1
  263. package/lib/WAUSync/USyncUser.js.map +0 -1
  264. package/lib/WAUSync/index.d.ts.map +0 -1
  265. package/lib/WAUSync/index.js.map +0 -1
  266. package/lib/index.d.ts.map +0 -1
  267. package/lib/index.js.map +0 -1
@@ -0,0 +1,559 @@
1
+ /**
2
+ * Relay transport.
3
+ *
4
+ * Tunnels UDP traffic to WhatsApp's edge relay servers via WebRTC data
5
+ * channels (using `@roamhq/wrtc`). Mirrors the browser client's behavior:
6
+ * pre-negotiated SCTP, custom DTLS fingerprint, ICE restart on idle.
7
+ *
8
+ * @author ShellTear
9
+ */
10
+ import { appendFileSync } from "node:fs";
11
+ const RELAY_PROTO_UDP = 0;
12
+ const FAUX_WEB_CLIENT_RELAY_PORT = 3478;
13
+ const TRUE_WEB_CLIENT_RELAY_PORT = 3480;
14
+ const CONNECTION_TIMEOUT_MS = 20_000;
15
+ const ICE_RESTART_IDLE_THRESHOLD_MS = 10_000;
16
+ const ICE_RTT_POLL_MS = 1_000;
17
+ const MAX_BUFFER_SIZE = 256 * 1024;
18
+ const RELAY_PACKET_LOG_PATH = process.env.CALL_DUMP_RELAY_PACKETS_PATH ?? "";
19
+ const DISABLE_IPV6 = process.env.CALL_DISABLE_IPV6 !== "0";
20
+ const RELAY_PORT_MODE = process.env.CALL_RELAY_PORT_MODE === "web" ? "web" : "original";
21
+ const USE_ORIGINAL_RELAY_PORTS = RELAY_PORT_MODE === "original";
22
+ const RELAY_DTLS_FINGERPRINT = "F9:CA:0C:98:A3:CC:71:D6:42:CE:5A:E2:53:D2:15:20:D3:1B:BA:D8:57:A4:F0:AF:BE:0B:FB:F3:6B:0C:A0:68";
23
+ const getConnectionIdentifier = (ip, port) => ip.includes(":") ? `[${ip}]:${port}` : `${ip}:${port}`;
24
+ const createEmptyStats = () => ({
25
+ sentPackets: 0,
26
+ receivedPackets: 0,
27
+ sentBytes: 0,
28
+ receivedBytes: 0,
29
+ droppedPackets: 0,
30
+ openConnections: 0,
31
+ });
32
+ const getRelayLookupId = (ip, port) => getConnectionIdentifier(ip, port === TRUE_WEB_CLIENT_RELAY_PORT ? TRUE_WEB_CLIENT_RELAY_PORT : port);
33
+ const getRtcConnectPort = (info) => info.ip === "157.240.24.133" ? FAUX_WEB_CLIENT_RELAY_PORT : info.port;
34
+ const getVoipStackPort = (info) => {
35
+ if (USE_ORIGINAL_RELAY_PORTS)
36
+ return info.originalPort || info.port;
37
+ const sourcePort = info.originalPort || info.port;
38
+ return sourcePort === TRUE_WEB_CLIENT_RELAY_PORT
39
+ ? TRUE_WEB_CLIENT_RELAY_PORT
40
+ : FAUX_WEB_CLIENT_RELAY_PORT;
41
+ };
42
+ const bufferPacket = (connection, packet) => {
43
+ if (packet.byteLength > MAX_BUFFER_SIZE) {
44
+ connection.stats.droppedPackets += 1;
45
+ return false;
46
+ }
47
+ while (connection.packetBuffer.length > 0 &&
48
+ connection.bufferedBytes + packet.byteLength > MAX_BUFFER_SIZE) {
49
+ const dropped = connection.packetBuffer.shift();
50
+ if (dropped) {
51
+ connection.bufferedBytes -= dropped.byteLength;
52
+ connection.stats.droppedPackets += 1;
53
+ }
54
+ }
55
+ connection.packetBuffer.push(packet);
56
+ connection.bufferedBytes += packet.byteLength;
57
+ return true;
58
+ };
59
+ const shiftPacket = (connection) => {
60
+ const packet = connection.packetBuffer.shift() ?? null;
61
+ if (packet)
62
+ connection.bufferedBytes -= packet.byteLength;
63
+ return packet;
64
+ };
65
+ const replaceIceCredentials = (sdp, ufrag, pwd) => sdp
66
+ .replace(/a=ice-ufrag:[^\r\n]+/g, `a=ice-ufrag:${ufrag}`)
67
+ .replace(/a=ice-pwd:[^\r\n]+/g, `a=ice-pwd:${pwd}`);
68
+ const replaceDtlsFingerprint = (sdp, algorithm, fingerprint) => sdp.replace(/a=fingerprint:[^\r\n]+/g, `a=fingerprint:${algorithm} ${fingerprint}`);
69
+ const removeIceCandidates = (sdp) => sdp
70
+ .replace(/a=candidate:[^\r\n]+\r?\n/g, "")
71
+ .replace(/a=end-of-candidates\r?\n?/g, "");
72
+ const appendRelayCandidate = (sdp, ip, port) => {
73
+ const candidate = `a=candidate:2 1 udp 2122262783 ${ip} ${port} typ host generation 0 network-cost 5`;
74
+ return `${removeIceCandidates(sdp)}${candidate}\r\na=end-of-candidates\r\n`;
75
+ };
76
+ const buildRemoteRelayAnswer = (offerSdp, info) => {
77
+ const setupLine = info.enableEdgerayDtlsActiveMode ? "a=setup:active" : "a=setup:passive";
78
+ let answerSdp = offerSdp.replace(/a=setup:actpass/g, setupLine);
79
+ answerSdp = replaceIceCredentials(answerSdp, info.authToken ?? info.token, info.key);
80
+ answerSdp = replaceDtlsFingerprint(answerSdp, "sha-256", RELAY_DTLS_FINGERPRINT);
81
+ answerSdp = answerSdp.replace(/a=ice-options:[^\r\n]+\r\n/g, "");
82
+ answerSdp = answerSdp.replace(/a=max-message-size:[^\r\n]+/g, "a=max-message-size:1200");
83
+ return appendRelayCandidate(answerSdp, info.ip, info.port);
84
+ };
85
+ const toArrayBuffer = (data) => {
86
+ const copy = new Uint8Array(data.byteLength);
87
+ copy.set(data);
88
+ return copy.buffer;
89
+ };
90
+ const toUint8Array = (data) => {
91
+ if (data instanceof Uint8Array)
92
+ return new Uint8Array(data);
93
+ if (Buffer.isBuffer(data))
94
+ return new Uint8Array(data);
95
+ if (data instanceof ArrayBuffer)
96
+ return new Uint8Array(data);
97
+ if (data && typeof data === "object" && "byteLength" in data && "buffer" in data) {
98
+ const view = data;
99
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
100
+ }
101
+ return null;
102
+ };
103
+ const classifyRelayPacket = (packet) => {
104
+ if (packet.byteLength < 2)
105
+ return "non_stun";
106
+ const bytes = new Uint8Array(packet);
107
+ const first = bytes[0];
108
+ const second = bytes[1];
109
+ if ((first & 0xc0) !== 0)
110
+ return "non_stun";
111
+ const stunType = ((first & 0x3f) << 8) | second;
112
+ if (stunType === 0x0001)
113
+ return "stun_bind";
114
+ if (stunType === 0x0003)
115
+ return "stun_alloc";
116
+ return "stun_unknown";
117
+ };
118
+ const appendRelayPacketLog = (direction, connection, packet) => {
119
+ if (!RELAY_PACKET_LOG_PATH)
120
+ return;
121
+ const buffer = packet instanceof ArrayBuffer
122
+ ? Buffer.from(new Uint8Array(packet))
123
+ : Buffer.from(packet.buffer, packet.byteOffset, packet.byteLength);
124
+ appendFileSync(RELAY_PACKET_LOG_PATH, JSON.stringify({
125
+ ts: new Date().toISOString(),
126
+ direction,
127
+ id: connection.info.id,
128
+ relayId: connection.info.relayId,
129
+ relayName: connection.info.name,
130
+ ip: connection.info.ip,
131
+ rtcPort: connection.info.port,
132
+ voipPort: getVoipStackPort(connection.info),
133
+ size: buffer.byteLength,
134
+ hex: buffer.toString("hex"),
135
+ }) + "\n");
136
+ };
137
+ export class RelayRtcTransport {
138
+ config;
139
+ #relayInfoById = new Map();
140
+ #connections = new Map();
141
+ #totals = createEmptyStats();
142
+ #wrtcPromise = null;
143
+ constructor(config) {
144
+ this.config = config;
145
+ }
146
+ updateRelayList = (update) => {
147
+ const nextInfoById = new Map();
148
+ for (const relay of update.relays ?? []) {
149
+ if (relay.token_id == null || relay.token_id < 0 ||
150
+ relay.token_id >= (update.relay_tokens ?? []).length) {
151
+ continue;
152
+ }
153
+ const authToken = update.auth_tokens && relay.auth_token_id != null && relay.auth_token_id >= 0
154
+ ? update.auth_tokens[relay.auth_token_id]
155
+ : undefined;
156
+ for (const address of relay.addresses ?? []) {
157
+ if (address.protocol !== RELAY_PROTO_UDP)
158
+ continue;
159
+ if (address.ipv4 && address.port != null) {
160
+ const clientPort = USE_ORIGINAL_RELAY_PORTS ? address.port : TRUE_WEB_CLIENT_RELAY_PORT;
161
+ const id = getConnectionIdentifier(address.ipv4, clientPort);
162
+ nextInfoById.set(id, {
163
+ id,
164
+ relayId: relay.relay_id,
165
+ ip: address.ipv4,
166
+ port: clientPort,
167
+ originalPort: address.port,
168
+ isIPv6: false,
169
+ token: update.relay_tokens[relay.token_id],
170
+ authToken,
171
+ key: update.relay_key,
172
+ name: relay.relay_name,
173
+ enableEdgerayDtlsActiveMode: update.enable_edgeray_dtls_active_mode === true,
174
+ });
175
+ }
176
+ if (!DISABLE_IPV6 && address.ipv6 && address.port_v6 != null) {
177
+ const clientPort = USE_ORIGINAL_RELAY_PORTS ? address.port_v6 : TRUE_WEB_CLIENT_RELAY_PORT;
178
+ const id = getConnectionIdentifier(address.ipv6, clientPort);
179
+ nextInfoById.set(id, {
180
+ id,
181
+ relayId: relay.relay_id,
182
+ ip: address.ipv6,
183
+ port: clientPort,
184
+ originalPort: address.port_v6,
185
+ isIPv6: true,
186
+ token: update.relay_tokens[relay.token_id],
187
+ authToken,
188
+ key: update.relay_key,
189
+ name: relay.relay_name,
190
+ enableEdgerayDtlsActiveMode: update.enable_edgeray_dtls_active_mode === true,
191
+ });
192
+ }
193
+ }
194
+ }
195
+ for (const id of this.#relayInfoById.keys()) {
196
+ if (!nextInfoById.has(id))
197
+ this.#closeConnection(id);
198
+ }
199
+ this.#relayInfoById.clear();
200
+ for (const [id, info] of nextInfoById) {
201
+ this.#relayInfoById.set(id, info);
202
+ void this.#ensureConnection(info);
203
+ }
204
+ };
205
+ send = (packet, ip, port) => {
206
+ const requestedId = getConnectionIdentifier(ip, port);
207
+ const preferredPort = USE_ORIGINAL_RELAY_PORTS ? port : TRUE_WEB_CLIENT_RELAY_PORT;
208
+ const candidateIds = [
209
+ requestedId,
210
+ getConnectionIdentifier(ip, preferredPort),
211
+ getRelayLookupId(ip, TRUE_WEB_CLIENT_RELAY_PORT),
212
+ getRelayLookupId(ip, FAUX_WEB_CLIENT_RELAY_PORT),
213
+ ];
214
+ let info;
215
+ for (const candidateId of candidateIds) {
216
+ info = this.#relayInfoById.get(candidateId);
217
+ if (info)
218
+ break;
219
+ }
220
+ if (!info) {
221
+ if (DISABLE_IPV6 && ip.includes(":"))
222
+ return 0;
223
+ const earlyConnection = this.#getOrCreateEarlyConnection(ip, port);
224
+ bufferPacket(earlyConnection, toArrayBuffer(packet));
225
+ return packet.byteLength;
226
+ }
227
+ const connection = this.#getOrCreateConnection(info);
228
+ const arrayBuffer = toArrayBuffer(packet);
229
+ if (classifyRelayPacket(arrayBuffer) === "stun_alloc" &&
230
+ connection.state === "open" &&
231
+ connection.sentMedia &&
232
+ Date.now() - connection.lastRxPacketTime > ICE_RESTART_IDLE_THRESHOLD_MS) {
233
+ connection.packetBuffer = [];
234
+ connection.bufferedBytes = 0;
235
+ bufferPacket(connection, arrayBuffer);
236
+ void this.#restartIce(connection);
237
+ return packet.byteLength;
238
+ }
239
+ if (connection.state === "open" && connection.dataChannel?.readyState === "open") {
240
+ if (!this.#sendBufferedPacket(connection, arrayBuffer)) {
241
+ connection.stats.droppedPackets += 1;
242
+ }
243
+ return packet.byteLength;
244
+ }
245
+ bufferPacket(connection, arrayBuffer);
246
+ void this.#ensureConnection(info);
247
+ return packet.byteLength;
248
+ };
249
+ getStats = () => {
250
+ let openConnections = 0;
251
+ for (const connection of this.#connections.values()) {
252
+ if (connection.state === "open")
253
+ openConnections += 1;
254
+ }
255
+ return { ...this.#totals, openConnections };
256
+ };
257
+ closeAll = async () => {
258
+ for (const id of [...this.#connections.keys()])
259
+ this.#closeConnection(id);
260
+ };
261
+ // ─── private ──────────────────────────────────────────────────────────────
262
+ #getOrCreateConnection = (info) => {
263
+ const existing = this.#connections.get(info.id);
264
+ if (existing) {
265
+ existing.info = info;
266
+ return existing;
267
+ }
268
+ const created = {
269
+ info, state: "none", peerConnection: null, dataChannel: null,
270
+ iceCandidate: null, packetBuffer: [], bufferedBytes: 0,
271
+ connectPromise: null, connectionTimeout: null, iceStatsInterval: null,
272
+ lastIceRttMs: null, hasReceivedFirstPacket: false,
273
+ hasNonStunPacketSent: false, sentMedia: false, lastRxPacketTime: 0,
274
+ isReconnecting: false, stats: createEmptyStats(),
275
+ };
276
+ this.#connections.set(info.id, created);
277
+ return created;
278
+ };
279
+ #getOrCreateEarlyConnection = (ip, port) => {
280
+ const id = getConnectionIdentifier(ip, port);
281
+ const existing = this.#connections.get(id);
282
+ if (existing)
283
+ return existing;
284
+ const placeholderInfo = {
285
+ id, relayId: 0, ip, port, originalPort: port,
286
+ isIPv6: ip.includes(":"), token: "", key: "",
287
+ name: "early-packet", enableEdgerayDtlsActiveMode: false,
288
+ };
289
+ const created = {
290
+ info: placeholderInfo, state: "none", peerConnection: null,
291
+ dataChannel: null, iceCandidate: null, packetBuffer: [],
292
+ bufferedBytes: 0, connectPromise: null, connectionTimeout: null,
293
+ iceStatsInterval: null, lastIceRttMs: null,
294
+ hasReceivedFirstPacket: false, hasNonStunPacketSent: false,
295
+ sentMedia: false, lastRxPacketTime: 0, isReconnecting: false,
296
+ stats: createEmptyStats(),
297
+ };
298
+ this.#connections.set(id, created);
299
+ return created;
300
+ };
301
+ #ensureConnection = async (info) => {
302
+ const connection = this.#getOrCreateConnection(info);
303
+ if (connection.state === "open" || connection.state === "connecting") {
304
+ return connection.connectPromise ?? Promise.resolve();
305
+ }
306
+ const promise = this.#connect(connection);
307
+ connection.connectPromise = promise;
308
+ try {
309
+ await promise;
310
+ }
311
+ finally {
312
+ connection.connectPromise = null;
313
+ }
314
+ };
315
+ #connect = async (connection) => {
316
+ const wrtcModule = await this.#loadWrtc();
317
+ const { RTCPeerConnection } = wrtcModule;
318
+ if (typeof RTCPeerConnection !== "function") {
319
+ throw new Error("RTCPeerConnection unavailable from @roamhq/wrtc");
320
+ }
321
+ this.#closePeerObjects(connection);
322
+ connection.state = "connecting";
323
+ const pc = new RTCPeerConnection();
324
+ const dc = pc.createDataChannel("pre-negotiated", {
325
+ negotiated: true, id: 0, ordered: false, maxRetransmits: 0, priority: "high",
326
+ });
327
+ connection.peerConnection = pc;
328
+ connection.dataChannel = dc;
329
+ dc.binaryType = "arraybuffer";
330
+ dc.onopen = () => {
331
+ connection.state = "open";
332
+ connection.isReconnecting = false;
333
+ if (connection.connectionTimeout) {
334
+ clearTimeout(connection.connectionTimeout);
335
+ connection.connectionTimeout = null;
336
+ }
337
+ this.#flushBufferedPackets(connection);
338
+ this.#startIceRttPolling(connection);
339
+ };
340
+ dc.onclose = () => {
341
+ if (connection.state !== "failed")
342
+ connection.state = "closed";
343
+ };
344
+ dc.onerror = () => {
345
+ connection.state = "failed";
346
+ };
347
+ dc.onmessage = (event) => {
348
+ this.#handleIncomingPacket(connection, event.data);
349
+ };
350
+ pc.onicecandidate = (event) => {
351
+ if (event.candidate?.candidate && !connection.iceCandidate) {
352
+ connection.iceCandidate = event.candidate.candidate;
353
+ }
354
+ };
355
+ pc.oniceconnectionstatechange = () => {
356
+ if (pc.iceConnectionState === "failed" || pc.iceConnectionState === "closed") {
357
+ connection.state = "failed";
358
+ }
359
+ };
360
+ connection.connectionTimeout = setTimeout(() => {
361
+ if (connection.state === "connecting") {
362
+ connection.state = "failed";
363
+ this.#closePeerObjects(connection);
364
+ }
365
+ }, CONNECTION_TIMEOUT_MS);
366
+ const offer = await pc.createOffer();
367
+ await pc.setLocalDescription(offer);
368
+ const remoteSdp = buildRemoteRelayAnswer(offer.sdp ?? "", {
369
+ ...connection.info,
370
+ port: getRtcConnectPort(connection.info),
371
+ });
372
+ await pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
373
+ };
374
+ #restartIce = async (connection) => {
375
+ if (connection.isReconnecting || !connection.hasNonStunPacketSent)
376
+ return;
377
+ const { RTCPeerConnection } = await this.#loadWrtc();
378
+ if (typeof RTCPeerConnection !== "function")
379
+ return;
380
+ connection.isReconnecting = true;
381
+ try {
382
+ this.#closePeerObjects(connection);
383
+ connection.state = "connecting";
384
+ const pc = new RTCPeerConnection();
385
+ const dc = pc.createDataChannel("pre-negotiated", {
386
+ negotiated: true, id: 0, ordered: false, maxRetransmits: 0, priority: "high",
387
+ });
388
+ connection.peerConnection = pc;
389
+ connection.dataChannel = dc;
390
+ dc.binaryType = "arraybuffer";
391
+ dc.onopen = () => {
392
+ connection.state = "open";
393
+ connection.isReconnecting = false;
394
+ this.#flushBufferedPackets(connection);
395
+ this.#startIceRttPolling(connection);
396
+ };
397
+ dc.onclose = () => { if (connection.state !== "failed")
398
+ connection.state = "closed"; };
399
+ dc.onerror = () => { connection.state = "failed"; };
400
+ dc.onmessage = (event) => {
401
+ this.#handleIncomingPacket(connection, event.data);
402
+ };
403
+ pc.onicecandidate = (event) => {
404
+ if (event.candidate?.candidate && !connection.iceCandidate) {
405
+ connection.iceCandidate = event.candidate.candidate;
406
+ }
407
+ };
408
+ const offer = await pc.createOffer({ iceRestart: false });
409
+ let localSdp = offer.sdp ?? "";
410
+ if (connection.iceCandidate) {
411
+ localSdp = `${removeIceCandidates(localSdp)}a=${connection.iceCandidate}\r\na=end-of-candidates\r\n`;
412
+ }
413
+ await pc.setLocalDescription({ type: "offer", sdp: localSdp });
414
+ const remoteSdp = buildRemoteRelayAnswer(localSdp, {
415
+ ...connection.info,
416
+ port: getRtcConnectPort(connection.info),
417
+ });
418
+ await pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
419
+ }
420
+ catch {
421
+ connection.state = "failed";
422
+ }
423
+ finally {
424
+ connection.isReconnecting = false;
425
+ }
426
+ };
427
+ #handleIncomingPacket = (connection, raw) => {
428
+ const packet = toUint8Array(raw);
429
+ if (!packet)
430
+ return;
431
+ connection.stats.receivedPackets += 1;
432
+ connection.stats.receivedBytes += packet.byteLength;
433
+ connection.hasReceivedFirstPacket = true;
434
+ connection.lastRxPacketTime = Date.now();
435
+ this.#totals.receivedPackets += 1;
436
+ this.#totals.receivedBytes += packet.byteLength;
437
+ appendRelayPacketLog("recv", connection, packet);
438
+ this.config.onTransportMessage(packet, connection.info.ip, getVoipStackPort(connection.info));
439
+ };
440
+ #flushBufferedPackets = (connection) => {
441
+ while (connection.state === "open" &&
442
+ connection.dataChannel?.readyState === "open" &&
443
+ connection.packetBuffer.length > 0) {
444
+ const packet = shiftPacket(connection);
445
+ if (!packet)
446
+ break;
447
+ if (!this.#sendBufferedPacket(connection, packet)) {
448
+ connection.stats.droppedPackets += 1;
449
+ break;
450
+ }
451
+ }
452
+ };
453
+ #sendBufferedPacket = (connection, packet) => {
454
+ try {
455
+ connection.dataChannel?.send(packet);
456
+ if (classifyRelayPacket(packet) === "non_stun") {
457
+ connection.hasNonStunPacketSent = true;
458
+ connection.sentMedia = true;
459
+ }
460
+ connection.stats.sentPackets += 1;
461
+ connection.stats.sentBytes += packet.byteLength;
462
+ this.#totals.sentPackets += 1;
463
+ this.#totals.sentBytes += packet.byteLength;
464
+ appendRelayPacketLog("send", connection, packet);
465
+ return true;
466
+ }
467
+ catch {
468
+ return false;
469
+ }
470
+ };
471
+ #startIceRttPolling = (connection) => {
472
+ this.#stopIceRttPolling(connection);
473
+ const pc = connection.peerConnection;
474
+ if (!pc || typeof pc.getStats !== "function" || !this.config.onIceRtt)
475
+ return;
476
+ const poll = async () => {
477
+ const rttMs = await this.#readCurrentRoundTripTimeMs(pc);
478
+ if (rttMs == null || connection.lastIceRttMs === rttMs)
479
+ return;
480
+ connection.lastIceRttMs = rttMs;
481
+ this.config.onIceRtt?.(rttMs, connection.info.ip, connection.info.port);
482
+ };
483
+ void poll();
484
+ connection.iceStatsInterval = setInterval(() => { void poll(); }, ICE_RTT_POLL_MS);
485
+ connection.iceStatsInterval.unref?.();
486
+ };
487
+ #stopIceRttPolling = (connection) => {
488
+ if (connection.iceStatsInterval) {
489
+ clearInterval(connection.iceStatsInterval);
490
+ connection.iceStatsInterval = null;
491
+ }
492
+ connection.lastIceRttMs = null;
493
+ };
494
+ #readCurrentRoundTripTimeMs = async (pc) => {
495
+ try {
496
+ const stats = await pc.getStats();
497
+ const reports = stats && typeof stats.values === "function"
498
+ ? Array.from(stats.values())
499
+ : Array.isArray(stats) ? stats : Object.values(stats ?? {});
500
+ if (!reports.length)
501
+ return null;
502
+ let selectedCandidatePairId = "";
503
+ for (const report of reports) {
504
+ if (report?.type === "transport" && typeof report.selectedCandidatePairId === "string") {
505
+ selectedCandidatePairId = report.selectedCandidatePairId;
506
+ break;
507
+ }
508
+ }
509
+ const candidatePairs = reports.filter((r) => r?.type === "candidate-pair");
510
+ const selectedPair = (selectedCandidatePairId && candidatePairs.find((r) => r?.id === selectedCandidatePairId)) ||
511
+ candidatePairs.find((r) => r?.selected || r?.nominated) ||
512
+ candidatePairs[0];
513
+ const rttSeconds = typeof selectedPair?.currentRoundTripTime === "number"
514
+ ? selectedPair.currentRoundTripTime
515
+ : typeof selectedPair?.totalRoundTripTime === "number" &&
516
+ typeof selectedPair?.responsesReceived === "number" &&
517
+ selectedPair.responsesReceived > 0
518
+ ? selectedPair.totalRoundTripTime / selectedPair.responsesReceived
519
+ : null;
520
+ if (rttSeconds == null || !Number.isFinite(rttSeconds) || rttSeconds <= 0)
521
+ return null;
522
+ return Math.max(1, Math.round(rttSeconds * 1000));
523
+ }
524
+ catch {
525
+ return null;
526
+ }
527
+ };
528
+ #closeConnection = (id) => {
529
+ const connection = this.#connections.get(id);
530
+ if (!connection)
531
+ return;
532
+ if (connection.connectionTimeout) {
533
+ clearTimeout(connection.connectionTimeout);
534
+ connection.connectionTimeout = null;
535
+ }
536
+ this.#closePeerObjects(connection);
537
+ connection.state = "closed";
538
+ connection.packetBuffer = [];
539
+ connection.bufferedBytes = 0;
540
+ this.#connections.delete(id);
541
+ };
542
+ #closePeerObjects = (connection) => {
543
+ this.#stopIceRttPolling(connection);
544
+ try {
545
+ connection.dataChannel?.close?.();
546
+ }
547
+ catch { }
548
+ try {
549
+ connection.peerConnection?.close?.();
550
+ }
551
+ catch { }
552
+ connection.dataChannel = null;
553
+ connection.peerConnection = null;
554
+ };
555
+ #loadWrtc = () => {
556
+ this.#wrtcPromise ??= import("@roamhq/wrtc").then((module) => (module.default ?? module));
557
+ return this.#wrtcPromise;
558
+ };
559
+ }