@xbibzlibrary/whatsbibz 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/LICENSE +22 -0
  3. package/LICENSE.upstream +21 -0
  4. package/README.md +280 -0
  5. package/VERIFIKASI-IDENTITAS-2026-09-03.md +138 -0
  6. package/WAProto/GenerateStatics.sh +3 -0
  7. package/WAProto/WAProto.proto +6035 -0
  8. package/WAProto/fix-imports.js +81 -0
  9. package/WAProto/index.d.ts +15314 -0
  10. package/WAProto/index.js +132326 -0
  11. package/assets/logo/README.md +21 -0
  12. package/assets/logo/png/banner-1200x360.png +0 -0
  13. package/assets/logo/png/icon-1024.png +0 -0
  14. package/assets/logo/png/icon-128.png +0 -0
  15. package/assets/logo/png/icon-16.png +0 -0
  16. package/assets/logo/png/icon-24.png +0 -0
  17. package/assets/logo/png/icon-256.png +0 -0
  18. package/assets/logo/png/icon-32.png +0 -0
  19. package/assets/logo/png/icon-48.png +0 -0
  20. package/assets/logo/png/icon-512.png +0 -0
  21. package/assets/logo/png/icon-64.png +0 -0
  22. package/assets/logo/png/mark-512.png +0 -0
  23. package/assets/logo/preview.png +0 -0
  24. package/assets/logo/whatsbibz.ico +0 -0
  25. package/lib/BibzWhats/client.d.ts +95 -0
  26. package/lib/BibzWhats/client.js +501 -0
  27. package/lib/BibzWhats/device-identity.d.ts +60 -0
  28. package/lib/BibzWhats/device-identity.js +234 -0
  29. package/lib/BibzWhats/extract.d.ts +29 -0
  30. package/lib/BibzWhats/extract.js +115 -0
  31. package/lib/BibzWhats/index.d.ts +7 -0
  32. package/lib/BibzWhats/index.js +8 -0
  33. package/lib/BibzWhats/jid.d.ts +22 -0
  34. package/lib/BibzWhats/jid.js +98 -0
  35. package/lib/BibzWhats/pairing.d.ts +35 -0
  36. package/lib/BibzWhats/pairing.js +186 -0
  37. package/lib/BibzWhats/send.d.ts +9 -0
  38. package/lib/BibzWhats/send.js +107 -0
  39. package/lib/BibzWhats/version.d.ts +6 -0
  40. package/lib/BibzWhats/version.js +7 -0
  41. package/lib/Defaults/index.d.ts +73 -0
  42. package/lib/Defaults/index.js +141 -0
  43. package/lib/Modded/message_builder.d.ts +15 -0
  44. package/lib/Modded/message_builder.js +2330 -0
  45. package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
  46. package/lib/Signal/Group/ciphertext-message.js +11 -0
  47. package/lib/Signal/Group/group-session-builder.d.ts +14 -0
  48. package/lib/Signal/Group/group-session-builder.js +29 -0
  49. package/lib/Signal/Group/group_cipher.d.ts +16 -0
  50. package/lib/Signal/Group/group_cipher.js +81 -0
  51. package/lib/Signal/Group/index.d.ts +11 -0
  52. package/lib/Signal/Group/index.js +11 -0
  53. package/lib/Signal/Group/keyhelper.d.ts +10 -0
  54. package/lib/Signal/Group/keyhelper.js +17 -0
  55. package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
  56. package/lib/Signal/Group/sender-chain-key.js +25 -0
  57. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
  58. package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
  59. package/lib/Signal/Group/sender-key-message.d.ts +18 -0
  60. package/lib/Signal/Group/sender-key-message.js +65 -0
  61. package/lib/Signal/Group/sender-key-name.d.ts +17 -0
  62. package/lib/Signal/Group/sender-key-name.js +47 -0
  63. package/lib/Signal/Group/sender-key-record.d.ts +30 -0
  64. package/lib/Signal/Group/sender-key-record.js +40 -0
  65. package/lib/Signal/Group/sender-key-state.d.ts +38 -0
  66. package/lib/Signal/Group/sender-key-state.js +83 -0
  67. package/lib/Signal/Group/sender-message-key.d.ts +11 -0
  68. package/lib/Signal/Group/sender-message-key.js +25 -0
  69. package/lib/Signal/libsignal.d.ts +4 -0
  70. package/lib/Signal/libsignal.js +406 -0
  71. package/lib/Signal/lid-mapping.d.ts +18 -0
  72. package/lib/Signal/lid-mapping.js +276 -0
  73. package/lib/Socket/Client/index.d.ts +2 -0
  74. package/lib/Socket/Client/index.js +2 -0
  75. package/lib/Socket/Client/types.d.ts +15 -0
  76. package/lib/Socket/Client/types.js +10 -0
  77. package/lib/Socket/Client/websocket.d.ts +12 -0
  78. package/lib/Socket/Client/websocket.js +53 -0
  79. package/lib/Socket/business.d.ts +537 -0
  80. package/lib/Socket/business.js +421 -0
  81. package/lib/Socket/chats.d.ts +110 -0
  82. package/lib/Socket/chats.js +1006 -0
  83. package/lib/Socket/communities.d.ts +646 -0
  84. package/lib/Socket/communities.js +430 -0
  85. package/lib/Socket/engine.d.ts +129 -0
  86. package/lib/Socket/engine.js +767 -0
  87. package/lib/Socket/groups.d.ts +149 -0
  88. package/lib/Socket/groups.js +326 -0
  89. package/lib/Socket/index.d.ts +637 -0
  90. package/lib/Socket/index.js +14 -0
  91. package/lib/Socket/messages-recv.d.ts +239 -0
  92. package/lib/Socket/messages-recv.js +1307 -0
  93. package/lib/Socket/messages-send.d.ts +580 -0
  94. package/lib/Socket/messages-send.js +1639 -0
  95. package/lib/Socket/mex.d.ts +2 -0
  96. package/lib/Socket/mex.js +47 -0
  97. package/lib/Socket/newsletter.d.ts +176 -0
  98. package/lib/Socket/newsletter.js +456 -0
  99. package/lib/Socket/socket.d.ts +56 -0
  100. package/lib/Socket/socket.js +998 -0
  101. package/lib/Types/Auth.d.ts +115 -0
  102. package/lib/Types/Auth.js +1 -0
  103. package/lib/Types/Bussines.d.ts +36 -0
  104. package/lib/Types/Bussines.js +1 -0
  105. package/lib/Types/Call.d.ts +14 -0
  106. package/lib/Types/Call.js +1 -0
  107. package/lib/Types/Chat.d.ts +124 -0
  108. package/lib/Types/Chat.js +7 -0
  109. package/lib/Types/Contact.d.ts +23 -0
  110. package/lib/Types/Contact.js +1 -0
  111. package/lib/Types/Events.d.ts +241 -0
  112. package/lib/Types/Events.js +1 -0
  113. package/lib/Types/GroupMetadata.d.ts +67 -0
  114. package/lib/Types/GroupMetadata.js +1 -0
  115. package/lib/Types/Label.d.ts +46 -0
  116. package/lib/Types/Label.js +24 -0
  117. package/lib/Types/LabelAssociation.d.ts +29 -0
  118. package/lib/Types/LabelAssociation.js +6 -0
  119. package/lib/Types/Message.d.ts +427 -0
  120. package/lib/Types/Message.js +10 -0
  121. package/lib/Types/Newsletter.d.ts +134 -0
  122. package/lib/Types/Newsletter.js +30 -0
  123. package/lib/Types/Product.d.ts +78 -0
  124. package/lib/Types/Product.js +1 -0
  125. package/lib/Types/Signal.d.ts +77 -0
  126. package/lib/Types/Signal.js +1 -0
  127. package/lib/Types/Socket.d.ts +173 -0
  128. package/lib/Types/Socket.js +2 -0
  129. package/lib/Types/State.d.ts +38 -0
  130. package/lib/Types/State.js +12 -0
  131. package/lib/Types/USync.d.ts +25 -0
  132. package/lib/Types/USync.js +1 -0
  133. package/lib/Types/index.d.ts +75 -0
  134. package/lib/Types/index.js +32 -0
  135. package/lib/Utils/auth-utils.d.ts +18 -0
  136. package/lib/Utils/auth-utils.js +289 -0
  137. package/lib/Utils/browser-utils.d.ts +3 -0
  138. package/lib/Utils/browser-utils.js +47 -0
  139. package/lib/Utils/business.d.ts +22 -0
  140. package/lib/Utils/business.js +230 -0
  141. package/lib/Utils/chat-utils.d.ts +69 -0
  142. package/lib/Utils/chat-utils.js +811 -0
  143. package/lib/Utils/companion-reg-client-utils.d.ts +20 -0
  144. package/lib/Utils/companion-reg-client-utils.js +66 -0
  145. package/lib/Utils/crypto.d.ts +41 -0
  146. package/lib/Utils/crypto.js +117 -0
  147. package/lib/Utils/decode-wa-message.d.ts +47 -0
  148. package/lib/Utils/decode-wa-message.js +282 -0
  149. package/lib/Utils/event-buffer.d.ts +35 -0
  150. package/lib/Utils/event-buffer.js +590 -0
  151. package/lib/Utils/generics.d.ts +90 -0
  152. package/lib/Utils/generics.js +384 -0
  153. package/lib/Utils/history.d.ts +21 -0
  154. package/lib/Utils/history.js +130 -0
  155. package/lib/Utils/identity-change-handler.d.ts +36 -0
  156. package/lib/Utils/identity-change-handler.js +48 -0
  157. package/lib/Utils/index.d.ts +25 -0
  158. package/lib/Utils/index.js +24 -0
  159. package/lib/Utils/link-preview.d.ts +20 -0
  160. package/lib/Utils/link-preview.js +84 -0
  161. package/lib/Utils/logger.d.ts +12 -0
  162. package/lib/Utils/logger.js +2 -0
  163. package/lib/Utils/lt-hash.d.ts +10 -0
  164. package/lib/Utils/lt-hash.js +7 -0
  165. package/lib/Utils/make-mutex.d.ts +8 -0
  166. package/lib/Utils/make-mutex.js +32 -0
  167. package/lib/Utils/message-retry-manager.d.ts +109 -0
  168. package/lib/Utils/message-retry-manager.js +224 -0
  169. package/lib/Utils/messages-media.d.ts +129 -0
  170. package/lib/Utils/messages-media.js +784 -0
  171. package/lib/Utils/messages.d.ts +90 -0
  172. package/lib/Utils/messages.js +1240 -0
  173. package/lib/Utils/noise-handler.d.ts +19 -0
  174. package/lib/Utils/noise-handler.js +200 -0
  175. package/lib/Utils/offline-node-processor.d.ts +16 -0
  176. package/lib/Utils/offline-node-processor.js +39 -0
  177. package/lib/Utils/platform-identity.d.ts +14 -0
  178. package/lib/Utils/platform-identity.js +148 -0
  179. package/lib/Utils/pre-key-manager.d.ts +27 -0
  180. package/lib/Utils/pre-key-manager.js +105 -0
  181. package/lib/Utils/process-message.d.ts +59 -0
  182. package/lib/Utils/process-message.js +544 -0
  183. package/lib/Utils/reporting-utils.d.ts +10 -0
  184. package/lib/Utils/reporting-utils.js +257 -0
  185. package/lib/Utils/rich-messages.d.ts +276 -0
  186. package/lib/Utils/rich-messages.js +1143 -0
  187. package/lib/Utils/signal.d.ts +33 -0
  188. package/lib/Utils/signal.js +158 -0
  189. package/lib/Utils/stanza-ack.d.ts +10 -0
  190. package/lib/Utils/stanza-ack.js +37 -0
  191. package/lib/Utils/sticker-pack.d.ts +10 -0
  192. package/lib/Utils/sticker-pack.js +111 -0
  193. package/lib/Utils/sync-action-utils.d.ts +18 -0
  194. package/lib/Utils/sync-action-utils.js +47 -0
  195. package/lib/Utils/tc-token-utils.d.ts +8 -0
  196. package/lib/Utils/tc-token-utils.js +45 -0
  197. package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
  198. package/lib/Utils/use-multi-file-auth-state.js +120 -0
  199. package/lib/Utils/validate-connection.d.ts +10 -0
  200. package/lib/Utils/validate-connection.js +222 -0
  201. package/lib/VoIP/audio-feeder.d.ts +15 -0
  202. package/lib/VoIP/audio-feeder.js +136 -0
  203. package/lib/VoIP/index.d.ts +12 -0
  204. package/lib/VoIP/index.js +282 -0
  205. package/lib/VoIP/relay-transport.d.ts +43 -0
  206. package/lib/VoIP/relay-transport.js +559 -0
  207. package/lib/VoIP/signaling.js +603 -0
  208. package/lib/VoIP/types.d.ts +69 -0
  209. package/lib/VoIP/types.js +17 -0
  210. package/lib/VoIP/wasm-engine.d.ts +103 -0
  211. package/lib/VoIP/wasm-engine.js +1214 -0
  212. package/lib/VoIP/worker-bootstrap.js +1042 -0
  213. package/lib/WABinary/constants.d.ts +27 -0
  214. package/lib/WABinary/constants.js +1300 -0
  215. package/lib/WABinary/decode.d.ts +6 -0
  216. package/lib/WABinary/decode.js +261 -0
  217. package/lib/WABinary/encode.d.ts +2 -0
  218. package/lib/WABinary/encode.js +219 -0
  219. package/lib/WABinary/generic-utils.d.ts +16 -0
  220. package/lib/WABinary/generic-utils.js +190 -0
  221. package/lib/WABinary/index.d.ts +5 -0
  222. package/lib/WABinary/index.js +5 -0
  223. package/lib/WABinary/jid-utils.d.ts +47 -0
  224. package/lib/WABinary/jid-utils.js +95 -0
  225. package/lib/WABinary/types.d.ts +18 -0
  226. package/lib/WABinary/types.js +1 -0
  227. package/lib/WAM/BinaryInfo.d.ts +8 -0
  228. package/lib/WAM/BinaryInfo.js +9 -0
  229. package/lib/WAM/constants.d.ts +39 -0
  230. package/lib/WAM/constants.js +22852 -0
  231. package/lib/WAM/encode.d.ts +2 -0
  232. package/lib/WAM/encode.js +149 -0
  233. package/lib/WAM/index.d.ts +3 -0
  234. package/lib/WAM/index.js +3 -0
  235. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
  236. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -0
  237. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
  238. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -0
  239. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
  240. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
  241. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
  242. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
  243. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
  244. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  245. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +9 -0
  246. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
  247. package/lib/WAUSync/Protocols/index.d.ts +4 -0
  248. package/lib/WAUSync/Protocols/index.js +4 -0
  249. package/lib/WAUSync/USyncQuery.d.ts +28 -0
  250. package/lib/WAUSync/USyncQuery.js +93 -0
  251. package/lib/WAUSync/USyncUser.d.ts +12 -0
  252. package/lib/WAUSync/USyncUser.js +22 -0
  253. package/lib/WAUSync/index.d.ts +3 -0
  254. package/lib/WAUSync/index.js +3 -0
  255. package/lib/assets/wasm/loader.js +5 -0
  256. package/lib/assets/wasm/whatsapp.wasm +0 -0
  257. package/lib/assets/wasm/worker-modules.js +273 -0
  258. package/lib/index.d.ts +21 -0
  259. package/lib/index.js +22 -0
  260. package/package.json +111 -0
@@ -0,0 +1,43 @@
1
+ type RelayAddress = {
2
+ protocol: number;
3
+ ipv4?: string;
4
+ ipv6?: string;
5
+ port?: number;
6
+ port_v6?: number;
7
+ };
8
+ type RelayDescriptor = {
9
+ relay_id: number;
10
+ relay_name: string;
11
+ token_id: number;
12
+ auth_token_id?: number;
13
+ addresses: RelayAddress[];
14
+ };
15
+ export type RelayListUpdatePayload = {
16
+ relay_key: string;
17
+ relay_tokens: string[];
18
+ auth_tokens?: string[];
19
+ enable_edgeray_dtls_active_mode?: boolean;
20
+ relays: RelayDescriptor[];
21
+ };
22
+ export type RelayTransportStats = {
23
+ sentPackets: number;
24
+ receivedPackets: number;
25
+ sentBytes: number;
26
+ receivedBytes: number;
27
+ droppedPackets: number;
28
+ openConnections: number;
29
+ };
30
+ export type RelayTransportConfig = {
31
+ onTransportMessage: (data: Uint8Array, ip: string, port: number) => void;
32
+ onIceRtt?: (rttMs: number, ip: string, port: number) => void;
33
+ };
34
+ export declare class RelayRtcTransport {
35
+ #private;
36
+ private readonly config;
37
+ constructor(config: RelayTransportConfig);
38
+ updateRelayList: (update: RelayListUpdatePayload) => void;
39
+ send: (packet: Uint8Array | Buffer, ip: string, port: number) => number;
40
+ getStats: () => RelayTransportStats;
41
+ closeAll: () => Promise<void>;
42
+ }
43
+ export {};
@@ -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 === "original" ? "original" : "web";
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
+ }