@areumtecnologia/wasockets 2.0.0 → 2.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 (75) hide show
  1. package/README.md +1987 -1649
  2. package/lib/Client/Builders/Buttons.js +127 -0
  3. package/lib/Client/Builders/EventBuilder.js +122 -0
  4. package/lib/Client/Builders/InteractiveMessage.js +150 -0
  5. package/lib/Client/Builders/Lists.js +78 -0
  6. package/lib/Client/Builders/PollBuilder.js +84 -0
  7. package/lib/Client/Builders/index.js +21 -0
  8. package/lib/Client/Constants.js +54 -0
  9. package/lib/Client/Context/CallContext.js +96 -0
  10. package/lib/Client/Context/MediaObject.js +155 -0
  11. package/lib/Client/Context/MessageContext.js +146 -0
  12. package/lib/Client/Modules/BusinessManager.js +155 -0
  13. package/lib/Client/Modules/ChannelManager.js +144 -0
  14. package/lib/Client/Modules/ChatManager.js +113 -0
  15. package/lib/Client/Modules/CommunityManager.js +136 -0
  16. package/lib/Client/Modules/GroupManager.js +194 -0
  17. package/lib/Client/Modules/LabelManager.js +123 -0
  18. package/lib/Client/Modules/MessageManager.js +198 -0
  19. package/lib/Client/Modules/PresenceManager.js +68 -0
  20. package/lib/Client/Modules/PrivacyManager.js +98 -0
  21. package/lib/Client/Modules/ProfileManager.js +92 -0
  22. package/lib/Client/Modules/UserManager.js +92 -0
  23. package/lib/Client/Modules/VoipManager.js +117 -0
  24. package/lib/Client/Modules/index.js +29 -0
  25. package/lib/Client/Normalizers/EventNormalizer.js +82 -0
  26. package/lib/Client/Normalizers/JidNormalizer.js +93 -0
  27. package/lib/Client/Normalizers/MessageNormalizer.js +134 -0
  28. package/lib/Client/WaClient.js +438 -0
  29. package/lib/Client/index.d.ts +464 -0
  30. package/lib/Client/index.js +76 -0
  31. package/lib/Defaults/baileys-version.json +1 -1
  32. package/lib/Defaults/connection.js +14 -1
  33. package/lib/Socket/index.js +51 -48
  34. package/lib/Socket/messages-recv.js +74 -13
  35. package/lib/Socket/messages-send.js +50 -25
  36. package/lib/Socket/socket.js +13 -0
  37. package/lib/Socket/voip.js +123 -0
  38. package/lib/Store/make-cache-manager-store.js +14 -5
  39. package/lib/Types/index.d.ts +4 -0
  40. package/lib/Utils/auth-utils.js +16 -1
  41. package/lib/Utils/browser-utils.js +6 -2
  42. package/lib/Utils/generics.js +47 -8
  43. package/lib/Utils/index.d.ts +1 -0
  44. package/lib/Utils/messages-media.js +44 -21
  45. package/lib/Utils/pre-key-manager.js +16 -1
  46. package/lib/Utils/validate-connection.js +10 -11
  47. package/lib/VoIP/VoIPCrypto.js +150 -0
  48. package/lib/VoIP/VoIPManager.js +469 -0
  49. package/lib/VoIP/VoIPMediaEngine.js +224 -0
  50. package/lib/VoIP/VoIPProtocol.js +354 -0
  51. package/lib/VoIP/VoIPRelayTransport.js +173 -0
  52. package/lib/VoIP/VoIPSession.js +314 -0
  53. package/lib/VoIP/assets/wasm/loader.js +5 -0
  54. package/lib/VoIP/assets/wasm/whatsapp.wasm +0 -0
  55. package/lib/VoIP/assets/wasm/worker-modules.js +305 -0
  56. package/lib/VoIP/engine/audio-feeder.d.mts +15 -0
  57. package/lib/VoIP/engine/audio-feeder.mjs +243 -0
  58. package/lib/VoIP/engine/index.d.mts +91 -0
  59. package/lib/VoIP/engine/index.mjs +1683 -0
  60. package/lib/VoIP/engine/relay-transport.d.mts +43 -0
  61. package/lib/VoIP/engine/relay-transport.mjs +607 -0
  62. package/lib/VoIP/engine/signaling.d.mts +44 -0
  63. package/lib/VoIP/engine/signaling.mjs +893 -0
  64. package/lib/VoIP/engine/types.d.mts +183 -0
  65. package/lib/VoIP/engine/types.mjs +17 -0
  66. package/lib/VoIP/engine/wasm-engine.d.mts +103 -0
  67. package/lib/VoIP/engine/wasm-engine.mjs +1397 -0
  68. package/lib/VoIP/engine/worker-bootstrap.mjs +1049 -0
  69. package/lib/VoIP/index.js +51 -0
  70. package/lib/VoIP/utils/audio.d.ts +28 -0
  71. package/lib/VoIP/utils/audio.js +148 -0
  72. package/lib/WABinary/generic-utils.js +22 -6
  73. package/lib/index.d.ts +382 -274
  74. package/lib/index.js +5 -0
  75. package/package.json +51 -49
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+
5
+ const crypto = require("crypto");
6
+ const {
7
+ getBinaryNodeChild,
8
+ getBinaryNodeChildren,
9
+ getBinaryNodeChildString,
10
+ getBinaryNodeChildBuffer,
11
+ S_WHATSAPP_NET,
12
+ jidEncode,
13
+ jidDecode
14
+ } = require("../WABinary");
15
+ const { generateMessageID } = require("../Utils");
16
+
17
+ /**
18
+ * WhatsApp VoIP Protocol Stanza Builder & Parser
19
+ */
20
+
21
+ const VOIP_CAPABILITY = Buffer.from("0105f509e0fa53", "hex");
22
+
23
+ /**
24
+ * Builds an outgoing <call> offer stanza matching official WhatsApp Android wire spec:
25
+ * audio (16000) -> audio (8000) -> (video) -> capability -> enc / destination -> encopt -> net
26
+ */
27
+ function buildOfferStanza({
28
+ toJid,
29
+ myJid,
30
+ callId,
31
+ destinations,
32
+ deviceIdentity,
33
+ isVideo = false,
34
+ audioRate = "16000",
35
+ keygen = "2",
36
+ callerPn = null,
37
+ callerCountryCode = "BR",
38
+ deviceClass = "2015",
39
+ joinable = "1",
40
+ tcTokenNode = null,
41
+ callerNotify = null,
42
+ callerPlatform = null,
43
+ callerVersion = null
44
+ }) {
45
+ const offerContent = [];
46
+
47
+ // 1. Audio codecs: rate="8000" then rate="16000"
48
+ offerContent.push({ tag: "audio", attrs: { rate: "8000", enc: "opus" }, content: undefined });
49
+ offerContent.push({ tag: "audio", attrs: { rate: "16000", enc: "opus" }, content: undefined });
50
+
51
+ // Video codec (if video call)
52
+ if (isVideo) {
53
+ offerContent.push({
54
+ tag: "video",
55
+ attrs: {
56
+ enc: "vp8",
57
+ dec: "vp8",
58
+ orientation: "0",
59
+ screen_width: "1920",
60
+ screen_height: "1080",
61
+ device_orientation: "0"
62
+ },
63
+ content: undefined
64
+ });
65
+ }
66
+
67
+ // 2. Capability node
68
+ offerContent.push({ tag: "capability", attrs: { ver: "1" }, content: VOIP_CAPABILITY });
69
+
70
+ // 3. Signal encrypted destination keys
71
+ if (destinations && destinations.length > 0) {
72
+ if (destinations[0].tag === "to") {
73
+ offerContent.push({ tag: "destination", attrs: {}, content: destinations });
74
+ } else if (destinations[0].tag === "enc") {
75
+ offerContent.push(...destinations);
76
+ } else {
77
+ offerContent.push({ tag: "destination", attrs: {}, content: destinations });
78
+ }
79
+ }
80
+
81
+ // 4. Encopt (keygen="2")
82
+ offerContent.push({ tag: "encopt", attrs: { keygen }, content: undefined });
83
+
84
+ // 5. Network medium (medium="3")
85
+ offerContent.push({ tag: "net", attrs: { medium: "3" }, content: undefined });
86
+
87
+ // 6. Signed device identity
88
+ if (deviceIdentity) {
89
+ offerContent.push({
90
+ tag: "device-identity",
91
+ attrs: {},
92
+ content: deviceIdentity
93
+ });
94
+ }
95
+
96
+ const { jidNormalizedUser } = require("../WABinary");
97
+ const callCreator = jidNormalizedUser(myJid);
98
+
99
+ const offerAttrs = {
100
+ "call-creator": callCreator,
101
+ "call-id": callId,
102
+ "device_class": deviceClass || "2015",
103
+ "joinable": joinable || "1"
104
+ };
105
+
106
+ if (callerPn) {
107
+ offerAttrs["caller_pn"] = callerPn;
108
+ }
109
+ if (callerCountryCode) {
110
+ offerAttrs["caller_country_code"] = callerCountryCode;
111
+ }
112
+
113
+ return {
114
+ tag: "call",
115
+ attrs: {
116
+ id: generateMessageID(),
117
+ to: toJid
118
+ },
119
+ content: [{
120
+ tag: "offer",
121
+ attrs: offerAttrs,
122
+ content: offerContent
123
+ }]
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Builds an <accept> stanza to answer an incoming WhatsApp call
129
+ */
130
+ function buildAcceptStanza({
131
+ callId,
132
+ callCreator,
133
+ myJid,
134
+ isVideo = false,
135
+ audioRate = "16000",
136
+ destinations = null,
137
+ deviceIdentity = null
138
+ }) {
139
+ const acceptContent = [];
140
+
141
+ acceptContent.push({ tag: "audio", attrs: { enc: "opus", rate: audioRate }, content: undefined });
142
+ if (isVideo) {
143
+ acceptContent.push({
144
+ tag: "video",
145
+ attrs: {
146
+ enc: "vp8",
147
+ dec: "vp8",
148
+ orientation: "0",
149
+ screen_width: "1920",
150
+ screen_height: "1080",
151
+ device_orientation: "0"
152
+ },
153
+ content: undefined
154
+ });
155
+ }
156
+
157
+ acceptContent.push({ tag: "net", attrs: { medium: "3" }, content: undefined });
158
+ acceptContent.push({ tag: "capability", attrs: { ver: "1" }, content: VOIP_CAPABILITY });
159
+ acceptContent.push({ tag: "encopt", attrs: { keygen: "2" }, content: undefined });
160
+
161
+ if (destinations && destinations.length > 0) {
162
+ acceptContent.push({ tag: "destination", attrs: {}, content: destinations });
163
+ }
164
+
165
+ if (deviceIdentity) {
166
+ acceptContent.push({
167
+ tag: "device-identity",
168
+ attrs: {},
169
+ content: deviceIdentity
170
+ });
171
+ }
172
+
173
+ return {
174
+ tag: "call",
175
+ attrs: {
176
+ id: generateMessageID(),
177
+ from: myJid,
178
+ to: callCreator
179
+ },
180
+ content: [{
181
+ tag: "accept",
182
+ attrs: {
183
+ "call-id": callId,
184
+ "call-creator": callCreator
185
+ },
186
+ content: acceptContent
187
+ }]
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Builds a <reject> stanza
193
+ */
194
+ function buildRejectStanza({ callId, callCreator, myJid, reason = "0" }) {
195
+ return {
196
+ tag: "call",
197
+ attrs: {
198
+ id: generateMessageID(),
199
+ from: myJid,
200
+ to: callCreator
201
+ },
202
+ content: [{
203
+ tag: "reject",
204
+ attrs: {
205
+ "call-id": callId,
206
+ "call-creator": callCreator,
207
+ count: reason
208
+ },
209
+ content: undefined
210
+ }]
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Builds a <terminate> stanza (hangup / timeout / error)
216
+ */
217
+ function buildTerminateStanza({ callId, callCreator, myJid, reason = "hangup" }) {
218
+ return {
219
+ tag: "call",
220
+ attrs: {
221
+ id: generateMessageID(),
222
+ from: myJid,
223
+ to: callCreator
224
+ },
225
+ content: [{
226
+ tag: "terminate",
227
+ attrs: {
228
+ "call-id": callId,
229
+ "call-creator": callCreator,
230
+ reason
231
+ },
232
+ content: undefined
233
+ }]
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Builds a relay query IQ stanza to retrieve Meta relay servers
239
+ */
240
+ function buildRelayQueryIq() {
241
+ return {
242
+ tag: "iq",
243
+ attrs: {
244
+ id: generateMessageID(),
245
+ to: S_WHATSAPP_NET,
246
+ type: "get",
247
+ xmlns: "w:p"
248
+ },
249
+ content: [{
250
+ tag: "relay",
251
+ attrs: {
252
+ latency: "15"
253
+ },
254
+ content: undefined
255
+ }]
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Parses relay tokens and endpoints from a relay IQ result
261
+ */
262
+ function parseRelayEndpoints(node) {
263
+ if (!node) return [];
264
+
265
+ const endpoints = [];
266
+ const findTeNodes = (curr) => {
267
+ if (!curr || typeof curr !== "object") return;
268
+ if (curr.tag === "te") {
269
+ const ip = curr.attrs?.ip;
270
+ const port = parseInt(curr.attrs?.port, 10);
271
+ const token = curr.content instanceof Uint8Array
272
+ ? Buffer.from(curr.content)
273
+ : (curr.attrs?.token ? Buffer.from(curr.attrs.token, "base64") : null);
274
+
275
+ if (ip && port) {
276
+ endpoints.push({
277
+ ip,
278
+ port,
279
+ token,
280
+ type: curr.attrs?.type || "udp"
281
+ });
282
+ }
283
+ }
284
+ if (Array.isArray(curr.content)) {
285
+ for (const child of curr.content) {
286
+ findTeNodes(child);
287
+ }
288
+ }
289
+ };
290
+
291
+ findTeNodes(node);
292
+ return endpoints;
293
+ }
294
+
295
+ /**
296
+ * Parses an incoming <call> node and extracts metadata, offer/accept parameters,
297
+ * destination encrypted payloads, and video attributes.
298
+ */
299
+ function parseCallNode(node) {
300
+ const { attrs } = node;
301
+ const children = getBinaryNodeChildren(node);
302
+ if (!children.length) return null;
303
+
304
+ const actionNode = children[0];
305
+ const action = actionNode.tag; // offer, accept, reject, terminate, relaylatency
306
+
307
+ const callId = actionNode.attrs["call-id"];
308
+ const callCreator = actionNode.attrs["call-creator"] || attrs.from;
309
+ const from = attrs.from || callCreator;
310
+
311
+ const audioChild = getBinaryNodeChild(actionNode, "audio");
312
+ const videoChild = getBinaryNodeChild(actionNode, "video");
313
+ const netChild = getBinaryNodeChild(actionNode, "net");
314
+ const destinationChild = getBinaryNodeChild(actionNode, "destination");
315
+
316
+ // Extract destinations (encrypted callKey for each participant device)
317
+ let destinationNodes = destinationChild ? getBinaryNodeChildren(destinationChild, "to") : [];
318
+ if (!destinationNodes.length) {
319
+ destinationNodes = getBinaryNodeChildren(actionNode, "to");
320
+ }
321
+ if (!destinationNodes.length) {
322
+ const encChild = getBinaryNodeChild(actionNode, "enc");
323
+ if (encChild) {
324
+ destinationNodes = [{ tag: "to", attrs: { jid: attrs.to }, content: [encChild] }];
325
+ }
326
+ }
327
+
328
+ return {
329
+ action,
330
+ callId,
331
+ callCreator,
332
+ from,
333
+ chatId: attrs.from,
334
+ timestamp: attrs.t ? new Date(+attrs.t * 1000) : new Date(),
335
+ offline: !!attrs.offline,
336
+ isVideo: !!videoChild,
337
+ audioRate: audioChild?.attrs?.rate || "16000",
338
+ audioCodec: audioChild?.attrs?.enc || "opus",
339
+ netMedium: netChild?.attrs?.medium,
340
+ destinations: destinationNodes,
341
+ rawNode: node
342
+ };
343
+ }
344
+
345
+ module.exports = {
346
+ VOIP_CAPABILITY,
347
+ buildOfferStanza,
348
+ buildAcceptStanza,
349
+ buildRejectStanza,
350
+ buildTerminateStanza,
351
+ buildRelayQueryIq,
352
+ parseRelayEndpoints,
353
+ parseCallNode,
354
+ };
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+
5
+ const dgram = require("dgram");
6
+ const { EventEmitter } = require("events");
7
+
8
+ /**
9
+ * Meta VoIP Relay UDP Transport Client
10
+ * Manages UDP socket communication, relay authentication tokens,
11
+ * NAT keep-alives, and packet dispatching.
12
+ */
13
+ class VoIPRelayTransport extends EventEmitter {
14
+ constructor({ ip, port, token, logger = console }) {
15
+ super();
16
+ this.ip = ip;
17
+ this.port = port;
18
+ this.token = Buffer.isBuffer(token) ? token : (token ? Buffer.from(token) : Buffer.alloc(0));
19
+ this.logger = logger;
20
+ this.socket = null;
21
+ this.keepAliveTimer = null;
22
+ this.isConnected = false;
23
+ this.bytesSent = 0;
24
+ this.bytesReceived = 0;
25
+ this.packetsSent = 0;
26
+ this.packetsReceived = 0;
27
+ }
28
+
29
+ /**
30
+ * Connects to the Meta Relay Server via UDP
31
+ */
32
+ async connect() {
33
+ return new Promise((resolve, reject) => {
34
+ try {
35
+ this.socket = dgram.createSocket("udp4");
36
+
37
+ this.socket.on("error", (err) => {
38
+ this.logger.error?.({ err, relay: `${this.ip}:${this.port}` }, "VoIP Relay socket error");
39
+ this.emit("error", err);
40
+ });
41
+
42
+ this.socket.on("message", (msg, rinfo) => {
43
+ this.bytesReceived += msg.length;
44
+ this.packetsReceived++;
45
+ this.handleIncomingPacket(msg, rinfo);
46
+ });
47
+
48
+ this.socket.on("close", () => {
49
+ this.isConnected = false;
50
+ this.stopKeepAlive();
51
+ this.emit("close");
52
+ });
53
+
54
+ // Bind to random available local UDP port
55
+ this.socket.bind(0, () => {
56
+ this.isConnected = true;
57
+ // Send initial relay allocation / binding handshake
58
+ this.sendRelayBinding();
59
+ this.startKeepAlive();
60
+ resolve();
61
+ });
62
+ } catch (err) {
63
+ reject(err);
64
+ }
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Sends the Meta VoIP Relay Binding Packet
70
+ * Format: [0x01 (Bind Command)] [Token Length (2 bytes)] [Token Bytes]
71
+ */
72
+ sendRelayBinding() {
73
+ if (!this.isConnected || !this.socket) return;
74
+
75
+ const packet = Buffer.alloc(3 + this.token.length);
76
+ packet.writeUInt8(0x01, 0); // Binding request
77
+ packet.writeUInt16BE(this.token.length, 1);
78
+ if (this.token.length > 0) {
79
+ this.token.copy(packet, 3);
80
+ }
81
+
82
+ this.sendRaw(packet);
83
+ }
84
+
85
+ /**
86
+ * Sends an RTP / SRTP packet through the relay connection
87
+ * @param {Buffer} rtpPacket The encrypted SRTP packet
88
+ */
89
+ sendRtp(rtpPacket) {
90
+ if (!this.isConnected || !this.socket) return;
91
+ // In Meta relay transport, standard RTP packets can be prefixed with data tag (0x02) or sent directly
92
+ this.sendRaw(rtpPacket);
93
+ }
94
+
95
+ /**
96
+ * Sends a raw UDP packet to the relay endpoint
97
+ */
98
+ sendRaw(buffer) {
99
+ if (!this.isConnected || !this.socket) return;
100
+
101
+ this.socket.send(buffer, 0, buffer.length, this.port, this.ip, (err) => {
102
+ if (err) {
103
+ this.logger.warn?.({ err }, "Error sending VoIP relay packet");
104
+ } else {
105
+ this.bytesSent += buffer.length;
106
+ this.packetsSent++;
107
+ }
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Parses incoming UDP packet
113
+ */
114
+ handleIncomingPacket(msg, rinfo) {
115
+ if (msg.length < 2) return;
116
+
117
+ const header = msg[0];
118
+
119
+ // 0x01 = Relay Ack / Pong
120
+ if (header === 0x01) {
121
+ this.emit("relay.bound", { rinfo });
122
+ return;
123
+ }
124
+
125
+ // 0x80+ = Standard RTP/SRTP packet (Version 2 RFC 3550)
126
+ if ((header & 0xc0) === 0x80) {
127
+ this.emit("rtp", msg);
128
+ return;
129
+ }
130
+
131
+ // Emit general packet
132
+ this.emit("packet", msg);
133
+ }
134
+
135
+ /**
136
+ * Sends periodic keep-alives to preserve NAT binding and session affinity
137
+ */
138
+ startKeepAlive(intervalMs = 2000) {
139
+ this.stopKeepAlive();
140
+ this.keepAliveTimer = setInterval(() => {
141
+ if (this.isConnected) {
142
+ // Relay ping: 0x03 [Ping]
143
+ const ping = Buffer.from([0x03, 0x00]);
144
+ this.sendRaw(ping);
145
+ }
146
+ }, intervalMs);
147
+ }
148
+
149
+ stopKeepAlive() {
150
+ if (this.keepAliveTimer) {
151
+ clearInterval(this.keepAliveTimer);
152
+ this.keepAliveTimer = null;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Gracefully closes the relay socket
158
+ */
159
+ close() {
160
+ this.stopKeepAlive();
161
+ this.isConnected = false;
162
+ if (this.socket) {
163
+ try {
164
+ this.socket.close();
165
+ } catch (_) {}
166
+ this.socket = null;
167
+ }
168
+ }
169
+ }
170
+
171
+ module.exports = {
172
+ VoIPRelayTransport,
173
+ };