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,825 @@
1
+ /* Elaina Baileys maintained distribution. Upstream notices and license are preserved in LICENSE and NOTICE.md. */
2
+ import { appendFileSync } from 'node:fs';
3
+
4
+ const RELAY_PROTO_UDP = 0;
5
+ const FAUX_WEB_CLIENT_RELAY_PORT = 3478;
6
+ const TRUE_WEB_CLIENT_RELAY_PORT = 3480;
7
+ const CONNECTION_TIMEOUT_MS = 20000;
8
+ const ICE_RESTART_IDLE_THRESHOLD_MS = 10000;
9
+ const ICE_RTT_POLL_MS = 1000;
10
+ const MAX_BUFFER_SIZE = 256 * 1024;
11
+
12
+ const RELAY_PACKET_LOG_PATH = process.env.CALL_DUMP_RELAY_PACKETS_PATH ?? "";
13
+ const DISABLE_IPV6 = process.env.CALL_DISABLE_IPV6 !== "0";
14
+ const RELAY_PORT_MODE = process.env.CALL_RELAY_PORT_MODE === "web" ? "web" : "original";
15
+ const USE_ORIGINAL_RELAY_PORTS = RELAY_PORT_MODE === "original";
16
+
17
+ 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";
18
+
19
+ const getConnectionIdentifier = (ip, port) => ip.includes(":") ? `[${ip}]:${port}` : `${ip}:${port}`;
20
+
21
+ const createEmptyStats = () => ({
22
+ sentPackets: 0,
23
+ receivedPackets: 0,
24
+ sentBytes: 0,
25
+ receivedBytes: 0,
26
+ droppedPackets: 0,
27
+ openConnections: 0
28
+ });
29
+
30
+ const getRelayLookupId = (ip, port) => getConnectionIdentifier(
31
+ ip,
32
+ port === TRUE_WEB_CLIENT_RELAY_PORT ? TRUE_WEB_CLIENT_RELAY_PORT : port
33
+ );
34
+
35
+ const getRtcConnectPort = info => info.ip === "157.240.24.133" ? FAUX_WEB_CLIENT_RELAY_PORT : info.port;
36
+
37
+ const getVoipStackPort = info => {
38
+ if (USE_ORIGINAL_RELAY_PORTS) {
39
+ return info.originalPort || info.port;
40
+ }
41
+
42
+ const sourcePort = info.originalPort || info.port;
43
+
44
+ return sourcePort === TRUE_WEB_CLIENT_RELAY_PORT
45
+ ? TRUE_WEB_CLIENT_RELAY_PORT
46
+ : FAUX_WEB_CLIENT_RELAY_PORT;
47
+ };
48
+
49
+ const bufferPacket = (connection, packet) => {
50
+ if (packet.byteLength > MAX_BUFFER_SIZE) {
51
+ connection.stats.droppedPackets += 1;
52
+ return false;
53
+ }
54
+
55
+ while (
56
+ connection.packetBuffer.length > 0 &&
57
+ connection.bufferedBytes + packet.byteLength > MAX_BUFFER_SIZE
58
+ ) {
59
+ const dropped = connection.packetBuffer.shift();
60
+
61
+ if (dropped) {
62
+ connection.bufferedBytes -= dropped.byteLength;
63
+ connection.stats.droppedPackets += 1;
64
+ }
65
+ }
66
+
67
+ connection.packetBuffer.push(packet);
68
+ connection.bufferedBytes += packet.byteLength;
69
+
70
+ return true;
71
+ };
72
+
73
+ const shiftPacket = connection => {
74
+ const packet = connection.packetBuffer.shift() ?? null;
75
+
76
+ if (packet) {
77
+ connection.bufferedBytes -= packet.byteLength;
78
+ }
79
+
80
+ return packet;
81
+ };
82
+
83
+ const replaceIceCredentials = (sdp, ufrag, pwd) => sdp
84
+ .replace(/a=ice-ufrag:[^\r\n]+/g, `a=ice-ufrag:${ufrag}`)
85
+ .replace(/a=ice-pwd:[^\r\n]+/g, `a=ice-pwd:${pwd}`);
86
+
87
+ const replaceDtlsFingerprint = (sdp, algorithm, fingerprint) => sdp
88
+ .replace(/a=fingerprint:[^\r\n]+/g, `a=fingerprint:${algorithm} ${fingerprint}`);
89
+
90
+ const removeIceCandidates = sdp => sdp
91
+ .replace(/a=candidate:[^\r\n]+\r?\n/g, "")
92
+ .replace(/a=end-of-candidates\r?\n?/g, "");
93
+
94
+ const appendRelayCandidate = (sdp, ip, port) => {
95
+ const candidate = `a=candidate:2 1 udp 2122262783 ${ip} ${port} typ host generation 0 network-cost 5`;
96
+
97
+ return `${removeIceCandidates(sdp)}${candidate}\r\na=end-of-candidates\r\n`;
98
+ };
99
+
100
+ const buildRemoteRelayAnswer = (offerSdp, info) => {
101
+ const setupLine = info.enableEdgerayDtlsActiveMode ? "a=setup:active" : "a=setup:passive";
102
+
103
+ let answerSdp = offerSdp.replace(/a=setup:actpass/g, setupLine);
104
+
105
+ answerSdp = replaceIceCredentials(answerSdp, info.authToken ?? info.token, info.key);
106
+ answerSdp = replaceDtlsFingerprint(answerSdp, "sha-256", RELAY_DTLS_FINGERPRINT);
107
+ answerSdp = answerSdp.replace(/a=ice-options:[^\r\n]+\r\n/g, "");
108
+ answerSdp = answerSdp.replace(/a=max-message-size:[^\r\n]+/g, "a=max-message-size:1200");
109
+
110
+ return appendRelayCandidate(answerSdp, info.ip, info.port);
111
+ };
112
+
113
+ const toArrayBuffer = data => {
114
+ const copy = new Uint8Array(data.byteLength);
115
+ copy.set(data);
116
+
117
+ return copy.buffer;
118
+ };
119
+
120
+ const toUint8Array = data => {
121
+ if (data instanceof Uint8Array) {
122
+ return new Uint8Array(data);
123
+ }
124
+
125
+ if (Buffer.isBuffer(data)) {
126
+ return new Uint8Array(data);
127
+ }
128
+
129
+ if (data instanceof ArrayBuffer) {
130
+ return new Uint8Array(data);
131
+ }
132
+
133
+ if (data && typeof data === "object" && "byteLength" in data && "buffer" in data) {
134
+ const view = data;
135
+
136
+ return new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
137
+ }
138
+
139
+ return null;
140
+ };
141
+
142
+ const classifyRelayPacket = packet => {
143
+ if (packet.byteLength < 2) {
144
+ return "non_stun";
145
+ }
146
+
147
+ const bytes = new Uint8Array(packet);
148
+ const first = bytes[0];
149
+ const second = bytes[1];
150
+
151
+ if ((first & 0xc0) !== 0) {
152
+ return "non_stun";
153
+ }
154
+
155
+ const stunType = ((first & 0x3f) << 8) | second;
156
+
157
+ if (stunType === 0x0001) {
158
+ return "stun_bind";
159
+ }
160
+
161
+ if (stunType === 0x0003) {
162
+ return "stun_alloc";
163
+ }
164
+
165
+ return "stun_unknown";
166
+ };
167
+
168
+ const appendRelayPacketLog = (direction, connection, packet) => {
169
+ if (!RELAY_PACKET_LOG_PATH) {
170
+ return;
171
+ }
172
+
173
+ const buffer = packet instanceof ArrayBuffer
174
+ ? Buffer.from(new Uint8Array(packet))
175
+ : Buffer.from(packet.buffer, packet.byteOffset, packet.byteLength);
176
+
177
+ appendFileSync(
178
+ RELAY_PACKET_LOG_PATH,
179
+ JSON.stringify({
180
+ ts: new Date().toISOString(),
181
+ direction,
182
+ id: connection.info.id,
183
+ relayId: connection.info.relayId,
184
+ relayName: connection.info.name,
185
+ ip: connection.info.ip,
186
+ rtcPort: connection.info.port,
187
+ voipPort: getVoipStackPort(connection.info),
188
+ size: buffer.byteLength,
189
+ hex: buffer.toString("hex")
190
+ }) + "\n"
191
+ );
192
+ };
193
+
194
+ export class RelayRtcTransport {
195
+ constructor(config) {
196
+ this.config = config;
197
+ this._relayInfoById = new Map();
198
+ this._connections = new Map();
199
+ this._totals = createEmptyStats();
200
+ this._wrtcPromise = null;
201
+
202
+ this.updateRelayList = update => {
203
+ const nextInfoById = new Map();
204
+
205
+ for (const relay of update.relays ?? []) {
206
+ if (
207
+ relay.token_id == null ||
208
+ relay.token_id < 0 ||
209
+ relay.token_id >= (update.relay_tokens ?? []).length
210
+ ) {
211
+ continue;
212
+ }
213
+
214
+ const authToken = update.auth_tokens && relay.auth_token_id != null && relay.auth_token_id >= 0
215
+ ? update.auth_tokens[relay.auth_token_id]
216
+ : undefined;
217
+
218
+ for (const address of relay.addresses ?? []) {
219
+ if (address.protocol !== RELAY_PROTO_UDP) {
220
+ continue;
221
+ }
222
+
223
+ if (address.ipv4 && address.port != null) {
224
+ const clientPort = USE_ORIGINAL_RELAY_PORTS ? address.port : TRUE_WEB_CLIENT_RELAY_PORT;
225
+ const id = getConnectionIdentifier(address.ipv4, clientPort);
226
+
227
+ nextInfoById.set(id, {
228
+ id,
229
+ relayId: relay.relay_id,
230
+ ip: address.ipv4,
231
+ port: clientPort,
232
+ originalPort: address.port,
233
+ isIPv6: false,
234
+ token: update.relay_tokens[relay.token_id],
235
+ authToken,
236
+ key: update.relay_key,
237
+ name: relay.relay_name,
238
+ enableEdgerayDtlsActiveMode: update.enable_edgeray_dtls_active_mode === true
239
+ });
240
+ }
241
+
242
+ if (!DISABLE_IPV6 && address.ipv6 && address.port_v6 != null) {
243
+ const clientPort = USE_ORIGINAL_RELAY_PORTS ? address.port_v6 : TRUE_WEB_CLIENT_RELAY_PORT;
244
+ const id = getConnectionIdentifier(address.ipv6, clientPort);
245
+
246
+ nextInfoById.set(id, {
247
+ id,
248
+ relayId: relay.relay_id,
249
+ ip: address.ipv6,
250
+ port: clientPort,
251
+ originalPort: address.port_v6,
252
+ isIPv6: true,
253
+ token: update.relay_tokens[relay.token_id],
254
+ authToken,
255
+ key: update.relay_key,
256
+ name: relay.relay_name,
257
+ enableEdgerayDtlsActiveMode: update.enable_edgeray_dtls_active_mode === true
258
+ });
259
+ }
260
+ }
261
+ }
262
+
263
+ for (const id of this._relayInfoById.keys()) {
264
+ if (!nextInfoById.has(id)) {
265
+ this._closeConnection(id);
266
+ }
267
+ }
268
+
269
+ this._relayInfoById.clear();
270
+
271
+ for (const [id, info] of nextInfoById) {
272
+ this._relayInfoById.set(id, info);
273
+ void this._ensureConnection(info);
274
+ }
275
+ };
276
+
277
+ this.send = (packet, ip, port) => {
278
+ const requestedId = getConnectionIdentifier(ip, port);
279
+ const preferredPort = USE_ORIGINAL_RELAY_PORTS ? port : TRUE_WEB_CLIENT_RELAY_PORT;
280
+
281
+ const candidateIds = [
282
+ requestedId,
283
+ getConnectionIdentifier(ip, preferredPort),
284
+ getRelayLookupId(ip, TRUE_WEB_CLIENT_RELAY_PORT),
285
+ getRelayLookupId(ip, FAUX_WEB_CLIENT_RELAY_PORT)
286
+ ];
287
+
288
+ let info;
289
+
290
+ for (const candidateId of candidateIds) {
291
+ info = this._relayInfoById.get(candidateId);
292
+
293
+ if (info) {
294
+ break;
295
+ }
296
+ }
297
+
298
+ if (!info) {
299
+ if (DISABLE_IPV6 && ip.includes(":")) {
300
+ return 0;
301
+ }
302
+
303
+ const earlyConnection = this._getOrCreateEarlyConnection(ip, port);
304
+ bufferPacket(earlyConnection, toArrayBuffer(packet));
305
+
306
+ return packet.byteLength;
307
+ }
308
+
309
+ const connection = this._getOrCreateConnection(info);
310
+ const arrayBuffer = toArrayBuffer(packet);
311
+
312
+ if (
313
+ classifyRelayPacket(arrayBuffer) === "stun_alloc" &&
314
+ connection.state === "open" &&
315
+ connection.sentMedia &&
316
+ Date.now() - connection.lastRxPacketTime > ICE_RESTART_IDLE_THRESHOLD_MS
317
+ ) {
318
+ connection.packetBuffer = [];
319
+ connection.bufferedBytes = 0;
320
+ bufferPacket(connection, arrayBuffer);
321
+ void this._restartIce(connection);
322
+
323
+ return packet.byteLength;
324
+ }
325
+
326
+ if (connection.state === "open" && connection.dataChannel?.readyState === "open") {
327
+ if (!this._sendBufferedPacket(connection, arrayBuffer)) {
328
+ connection.stats.droppedPackets += 1;
329
+ }
330
+
331
+ return packet.byteLength;
332
+ }
333
+
334
+ bufferPacket(connection, arrayBuffer);
335
+ void this._ensureConnection(info);
336
+
337
+ return packet.byteLength;
338
+ };
339
+
340
+ this.getStats = () => {
341
+ let openConnections = 0;
342
+
343
+ for (const connection of this._connections.values()) {
344
+ if (connection.state === "open") {
345
+ openConnections += 1;
346
+ }
347
+ }
348
+
349
+ return {
350
+ ...this._totals,
351
+ openConnections
352
+ };
353
+ };
354
+
355
+ this.closeAll = async () => {
356
+ for (const id of [...this._connections.keys()]) {
357
+ this._closeConnection(id);
358
+ }
359
+ };
360
+
361
+ this._getOrCreateConnection = info => {
362
+ const existing = this._connections.get(info.id);
363
+
364
+ if (existing) {
365
+ existing.info = info;
366
+ return existing;
367
+ }
368
+
369
+ const created = {
370
+ info,
371
+ state: "none",
372
+ peerConnection: null,
373
+ dataChannel: null,
374
+ iceCandidate: null,
375
+ packetBuffer: [],
376
+ bufferedBytes: 0,
377
+ connectPromise: null,
378
+ connectionTimeout: null,
379
+ iceStatsInterval: null,
380
+ lastIceRttMs: null,
381
+ hasReceivedFirstPacket: false,
382
+ hasNonStunPacketSent: false,
383
+ sentMedia: false,
384
+ lastRxPacketTime: 0,
385
+ isReconnecting: false,
386
+ stats: createEmptyStats()
387
+ };
388
+
389
+ this._connections.set(info.id, created);
390
+
391
+ return created;
392
+ };
393
+
394
+ this._getOrCreateEarlyConnection = (ip, port) => {
395
+ const id = getConnectionIdentifier(ip, port);
396
+ const existing = this._connections.get(id);
397
+
398
+ if (existing) {
399
+ return existing;
400
+ }
401
+
402
+ const placeholderInfo = {
403
+ id,
404
+ relayId: 0,
405
+ ip,
406
+ port,
407
+ originalPort: port,
408
+ isIPv6: ip.includes(":"),
409
+ token: "",
410
+ key: "",
411
+ name: "early-packet",
412
+ enableEdgerayDtlsActiveMode: false
413
+ };
414
+
415
+ const created = {
416
+ info: placeholderInfo,
417
+ state: "none",
418
+ peerConnection: null,
419
+ dataChannel: null,
420
+ iceCandidate: null,
421
+ packetBuffer: [],
422
+ bufferedBytes: 0,
423
+ connectPromise: null,
424
+ connectionTimeout: null,
425
+ iceStatsInterval: null,
426
+ lastIceRttMs: null,
427
+ hasReceivedFirstPacket: false,
428
+ hasNonStunPacketSent: false,
429
+ sentMedia: false,
430
+ lastRxPacketTime: 0,
431
+ isReconnecting: false,
432
+ stats: createEmptyStats()
433
+ };
434
+
435
+ this._connections.set(id, created);
436
+
437
+ return created;
438
+ };
439
+
440
+ this._ensureConnection = async info => {
441
+ const connection = this._getOrCreateConnection(info);
442
+
443
+ if (connection.state === "open" || connection.state === "connecting") {
444
+ return connection.connectPromise ?? Promise.resolve();
445
+ }
446
+
447
+ const promise = this._connect(connection);
448
+ connection.connectPromise = promise;
449
+
450
+ try {
451
+ await promise;
452
+ } finally {
453
+ connection.connectPromise = null;
454
+ }
455
+ };
456
+
457
+ this._connect = async connection => {
458
+ const wrtcModule = await this._loadWrtc();
459
+ const { RTCPeerConnection } = wrtcModule;
460
+
461
+ if (typeof RTCPeerConnection !== "function") {
462
+ throw new Error("RTCPeerConnection unavailable from @roamhq/wrtc");
463
+ }
464
+
465
+ this._closePeerObjects(connection);
466
+
467
+ connection.state = "connecting";
468
+
469
+ const pc = new RTCPeerConnection();
470
+ const dc = pc.createDataChannel("pre-negotiated", {
471
+ negotiated: true,
472
+ id: 0,
473
+ ordered: false,
474
+ maxRetransmits: 0,
475
+ priority: "high"
476
+ });
477
+
478
+ connection.peerConnection = pc;
479
+ connection.dataChannel = dc;
480
+
481
+ dc.binaryType = "arraybuffer";
482
+
483
+ dc.onopen = () => {
484
+ connection.state = "open";
485
+ connection.isReconnecting = false;
486
+
487
+ if (connection.connectionTimeout) {
488
+ clearTimeout(connection.connectionTimeout);
489
+ connection.connectionTimeout = null;
490
+ }
491
+
492
+ this._flushBufferedPackets(connection);
493
+ this._startIceRttPolling(connection);
494
+ };
495
+
496
+ dc.onclose = () => {
497
+ if (connection.state !== "failed") {
498
+ connection.state = "closed";
499
+ }
500
+ };
501
+
502
+ dc.onerror = () => {
503
+ connection.state = "failed";
504
+ };
505
+
506
+ dc.onmessage = event => {
507
+ this._handleIncomingPacket(connection, event.data);
508
+ };
509
+
510
+ pc.onicecandidate = event => {
511
+ if (event.candidate?.candidate && !connection.iceCandidate) {
512
+ connection.iceCandidate = event.candidate.candidate;
513
+ }
514
+ };
515
+
516
+ pc.oniceconnectionstatechange = () => {
517
+ if (pc.iceConnectionState === "failed" || pc.iceConnectionState === "closed") {
518
+ connection.state = "failed";
519
+ }
520
+ };
521
+
522
+ connection.connectionTimeout = setTimeout(() => {
523
+ if (connection.state === "connecting") {
524
+ connection.state = "failed";
525
+ this._closePeerObjects(connection);
526
+ }
527
+ }, CONNECTION_TIMEOUT_MS);
528
+
529
+ const offer = await pc.createOffer();
530
+
531
+ await pc.setLocalDescription(offer);
532
+
533
+ const remoteSdp = buildRemoteRelayAnswer(offer.sdp ?? "", {
534
+ ...connection.info,
535
+ port: getRtcConnectPort(connection.info)
536
+ });
537
+
538
+ await pc.setRemoteDescription({
539
+ type: "answer",
540
+ sdp: remoteSdp
541
+ });
542
+ };
543
+
544
+ this._restartIce = async connection => {
545
+ if (connection.isReconnecting || !connection.hasNonStunPacketSent) {
546
+ return;
547
+ }
548
+
549
+ const { RTCPeerConnection } = await this._loadWrtc();
550
+
551
+ if (typeof RTCPeerConnection !== "function") {
552
+ return;
553
+ }
554
+
555
+ connection.isReconnecting = true;
556
+
557
+ try {
558
+ this._closePeerObjects(connection);
559
+ connection.state = "connecting";
560
+
561
+ const pc = new RTCPeerConnection();
562
+ const dc = pc.createDataChannel("pre-negotiated", {
563
+ negotiated: true,
564
+ id: 0,
565
+ ordered: false,
566
+ maxRetransmits: 0,
567
+ priority: "high"
568
+ });
569
+
570
+ connection.peerConnection = pc;
571
+ connection.dataChannel = dc;
572
+
573
+ dc.binaryType = "arraybuffer";
574
+
575
+ dc.onopen = () => {
576
+ connection.state = "open";
577
+ connection.isReconnecting = false;
578
+ this._flushBufferedPackets(connection);
579
+ this._startIceRttPolling(connection);
580
+ };
581
+
582
+ dc.onclose = () => {
583
+ if (connection.state !== "failed") {
584
+ connection.state = "closed";
585
+ }
586
+ };
587
+
588
+ dc.onerror = () => {
589
+ connection.state = "failed";
590
+ };
591
+
592
+ dc.onmessage = event => {
593
+ this._handleIncomingPacket(connection, event.data);
594
+ };
595
+
596
+ pc.onicecandidate = event => {
597
+ if (event.candidate?.candidate && !connection.iceCandidate) {
598
+ connection.iceCandidate = event.candidate.candidate;
599
+ }
600
+ };
601
+
602
+ const offer = await pc.createOffer({
603
+ iceRestart: false
604
+ });
605
+
606
+ let localSdp = offer.sdp ?? "";
607
+
608
+ if (connection.iceCandidate) {
609
+ localSdp = `${removeIceCandidates(localSdp)}a=${connection.iceCandidate}\r\na=end-of-candidates\r\n`;
610
+ }
611
+
612
+ await pc.setLocalDescription({
613
+ type: "offer",
614
+ sdp: localSdp
615
+ });
616
+
617
+ const remoteSdp = buildRemoteRelayAnswer(localSdp, {
618
+ ...connection.info,
619
+ port: getRtcConnectPort(connection.info)
620
+ });
621
+
622
+ await pc.setRemoteDescription({
623
+ type: "answer",
624
+ sdp: remoteSdp
625
+ });
626
+ } catch {
627
+ connection.state = "failed";
628
+ } finally {
629
+ connection.isReconnecting = false;
630
+ }
631
+ };
632
+
633
+ this._handleIncomingPacket = (connection, raw) => {
634
+ const packet = toUint8Array(raw);
635
+
636
+ if (!packet) {
637
+ return;
638
+ }
639
+
640
+ connection.stats.receivedPackets += 1;
641
+ connection.stats.receivedBytes += packet.byteLength;
642
+ connection.hasReceivedFirstPacket = true;
643
+ connection.lastRxPacketTime = Date.now();
644
+
645
+ this._totals.receivedPackets += 1;
646
+ this._totals.receivedBytes += packet.byteLength;
647
+
648
+ appendRelayPacketLog("recv", connection, packet);
649
+
650
+ this.config.onTransportMessage(packet, connection.info.ip, getVoipStackPort(connection.info));
651
+ };
652
+
653
+ this._flushBufferedPackets = connection => {
654
+ while (
655
+ connection.state === "open" &&
656
+ connection.dataChannel?.readyState === "open" &&
657
+ connection.packetBuffer.length > 0
658
+ ) {
659
+ const packet = shiftPacket(connection);
660
+
661
+ if (!packet) {
662
+ break;
663
+ }
664
+
665
+ if (!this._sendBufferedPacket(connection, packet)) {
666
+ connection.stats.droppedPackets += 1;
667
+ break;
668
+ }
669
+ }
670
+ };
671
+
672
+ this._sendBufferedPacket = (connection, packet) => {
673
+ try {
674
+ connection.dataChannel?.send(packet);
675
+
676
+ if (classifyRelayPacket(packet) === "non_stun") {
677
+ connection.hasNonStunPacketSent = true;
678
+ connection.sentMedia = true;
679
+ }
680
+
681
+ connection.stats.sentPackets += 1;
682
+ connection.stats.sentBytes += packet.byteLength;
683
+
684
+ this._totals.sentPackets += 1;
685
+ this._totals.sentBytes += packet.byteLength;
686
+
687
+ appendRelayPacketLog("send", connection, packet);
688
+
689
+ return true;
690
+ } catch {
691
+ return false;
692
+ }
693
+ };
694
+
695
+ this._startIceRttPolling = connection => {
696
+ this._stopIceRttPolling(connection);
697
+
698
+ const pc = connection.peerConnection;
699
+
700
+ if (!pc || typeof pc.getStats !== "function" || !this.config.onIceRtt) {
701
+ return;
702
+ }
703
+
704
+ const poll = async () => {
705
+ const rttMs = await this._readCurrentRoundTripTimeMs(pc);
706
+
707
+ if (rttMs == null || connection.lastIceRttMs === rttMs) {
708
+ return;
709
+ }
710
+
711
+ connection.lastIceRttMs = rttMs;
712
+ this.config.onIceRtt?.(rttMs, connection.info.ip, connection.info.port);
713
+ };
714
+
715
+ void poll();
716
+
717
+ connection.iceStatsInterval = setInterval(() => {
718
+ void poll();
719
+ }, ICE_RTT_POLL_MS);
720
+
721
+ connection.iceStatsInterval.unref?.();
722
+ };
723
+
724
+ this._stopIceRttPolling = connection => {
725
+ if (connection.iceStatsInterval) {
726
+ clearInterval(connection.iceStatsInterval);
727
+ connection.iceStatsInterval = null;
728
+ }
729
+
730
+ connection.lastIceRttMs = null;
731
+ };
732
+
733
+ this._readCurrentRoundTripTimeMs = async pc => {
734
+ try {
735
+ const stats = await pc.getStats();
736
+
737
+ const reports = stats && typeof stats.values === "function"
738
+ ? Array.from(stats.values())
739
+ : Array.isArray(stats)
740
+ ? stats
741
+ : Object.values(stats ?? {});
742
+
743
+ if (!reports.length) {
744
+ return null;
745
+ }
746
+
747
+ let selectedCandidatePairId = "";
748
+
749
+ for (const report of reports) {
750
+ if (report?.type === "transport" && typeof report.selectedCandidatePairId === "string") {
751
+ selectedCandidatePairId = report.selectedCandidatePairId;
752
+ break;
753
+ }
754
+ }
755
+
756
+ const candidatePairs = reports.filter(report => report?.type === "candidate-pair");
757
+
758
+ const selectedPair =
759
+ selectedCandidatePairId && candidatePairs.find(report => report?.id === selectedCandidatePairId) ||
760
+ candidatePairs.find(report => report?.selected || report?.nominated) ||
761
+ candidatePairs[0];
762
+
763
+ const rttSeconds = typeof selectedPair?.currentRoundTripTime === "number"
764
+ ? selectedPair.currentRoundTripTime
765
+ : typeof selectedPair?.totalRoundTripTime === "number" &&
766
+ typeof selectedPair?.responsesReceived === "number" &&
767
+ selectedPair.responsesReceived > 0
768
+ ? selectedPair.totalRoundTripTime / selectedPair.responsesReceived
769
+ : null;
770
+
771
+ if (rttSeconds == null || !Number.isFinite(rttSeconds) || rttSeconds <= 0) {
772
+ return null;
773
+ }
774
+
775
+ return Math.max(1, Math.round(rttSeconds * 1000));
776
+ } catch {
777
+ return null;
778
+ }
779
+ };
780
+
781
+ this._closeConnection = id => {
782
+ const connection = this._connections.get(id);
783
+
784
+ if (!connection) {
785
+ return;
786
+ }
787
+
788
+ if (connection.connectionTimeout) {
789
+ clearTimeout(connection.connectionTimeout);
790
+ connection.connectionTimeout = null;
791
+ }
792
+
793
+ this._closePeerObjects(connection);
794
+
795
+ connection.state = "closed";
796
+ connection.packetBuffer = [];
797
+ connection.bufferedBytes = 0;
798
+
799
+ this._connections.delete(id);
800
+ };
801
+
802
+ this._closePeerObjects = connection => {
803
+ this._stopIceRttPolling(connection);
804
+
805
+ try {
806
+ connection.dataChannel?.close?.();
807
+ } catch { }
808
+
809
+ try {
810
+ connection.peerConnection?.close?.();
811
+ } catch { }
812
+
813
+ connection.dataChannel = null;
814
+ connection.peerConnection = null;
815
+ };
816
+
817
+ this._loadWrtc = () => {
818
+ this._wrtcPromise ??= import("@roamhq/wrtc").then(module => module.default ?? module);
819
+
820
+ return this._wrtcPromise;
821
+ };
822
+ }
823
+ }
824
+
825
+ export default RelayRtcTransport;