@areumtecnologia/wasockets 2.0.0-alpha.1 → 2.2.3

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 (46) hide show
  1. package/README.md +1929 -1649
  2. package/lib/Defaults/baileys-version.json +1 -1
  3. package/lib/Defaults/connection.js +14 -1
  4. package/lib/Socket/index.js +51 -48
  5. package/lib/Socket/messages-recv.js +74 -13
  6. package/lib/Socket/messages-send.js +50 -25
  7. package/lib/Socket/socket.js +13 -0
  8. package/lib/Socket/voip.js +123 -0
  9. package/lib/Store/make-cache-manager-store.js +14 -5
  10. package/lib/Types/index.d.ts +4 -0
  11. package/lib/Utils/auth-utils.js +16 -1
  12. package/lib/Utils/browser-utils.js +6 -2
  13. package/lib/Utils/generics.js +47 -8
  14. package/lib/Utils/index.d.ts +1 -0
  15. package/lib/Utils/messages-media.js +44 -21
  16. package/lib/Utils/pre-key-manager.js +16 -1
  17. package/lib/Utils/validate-connection.js +10 -11
  18. package/lib/VoIP/VoIPCrypto.js +150 -0
  19. package/lib/VoIP/VoIPManager.js +469 -0
  20. package/lib/VoIP/VoIPMediaEngine.js +224 -0
  21. package/lib/VoIP/VoIPProtocol.js +354 -0
  22. package/lib/VoIP/VoIPRelayTransport.js +173 -0
  23. package/lib/VoIP/VoIPSession.js +314 -0
  24. package/lib/VoIP/assets/wasm/loader.js +5 -0
  25. package/lib/VoIP/assets/wasm/whatsapp.wasm +0 -0
  26. package/lib/VoIP/assets/wasm/worker-modules.js +305 -0
  27. package/lib/VoIP/engine/audio-feeder.d.mts +15 -0
  28. package/lib/VoIP/engine/audio-feeder.mjs +243 -0
  29. package/lib/VoIP/engine/index.d.mts +91 -0
  30. package/lib/VoIP/engine/index.mjs +1683 -0
  31. package/lib/VoIP/engine/relay-transport.d.mts +43 -0
  32. package/lib/VoIP/engine/relay-transport.mjs +607 -0
  33. package/lib/VoIP/engine/signaling.d.mts +44 -0
  34. package/lib/VoIP/engine/signaling.mjs +893 -0
  35. package/lib/VoIP/engine/types.d.mts +183 -0
  36. package/lib/VoIP/engine/types.mjs +17 -0
  37. package/lib/VoIP/engine/wasm-engine.d.mts +103 -0
  38. package/lib/VoIP/engine/wasm-engine.mjs +1397 -0
  39. package/lib/VoIP/engine/worker-bootstrap.mjs +1049 -0
  40. package/lib/VoIP/index.js +51 -0
  41. package/lib/VoIP/utils/audio.d.ts +28 -0
  42. package/lib/VoIP/utils/audio.js +148 -0
  43. package/lib/WABinary/generic-utils.js +22 -6
  44. package/lib/index.d.ts +382 -274
  45. package/lib/index.js +5 -0
  46. package/package.json +28 -32
@@ -232,12 +232,34 @@ const printQRIfNecessaryListener = (ev, logger) => {
232
232
  })
233
233
  }
234
234
 
235
+ const parseWaVersion = (ver) => {
236
+ if (Array.isArray(ver) && ver.length === 3) {
237
+ return ver.map(Number)
238
+ }
239
+ if (typeof ver === 'string') {
240
+ const parts = ver.split('.').map(Number)
241
+ if (parts.length === 3 && parts.every(n => !isNaN(n))) {
242
+ return parts
243
+ }
244
+ }
245
+ return undefined
246
+ }
247
+
248
+ const getLocalPackageWaVersion = () => {
249
+ try {
250
+ const pkg = require('../../package.json')
251
+ return parseWaVersion(pkg.whatsappVersion) || version
252
+ } catch {
253
+ return version
254
+ }
255
+ }
256
+
235
257
  /**
236
258
  * utility that fetches latest baileys version from the master branch.
237
259
  * Use to ensure your WA connection is always on the latest version
238
260
  */
239
261
  const fetchLatestBaileysVersion = async (options = {}) => {
240
- const URL = 'https://raw.githubusercontent.com/Itsukichann/Baileys/refs/heads/master/lib/Defaults/baileys-version.json'
262
+ const URL = 'https://raw.githubusercontent.com/areumtecnologia/WaSockets/master/package.json'
241
263
  try {
242
264
  const signal = options.signal || AbortSignal.timeout(15000)
243
265
  const result = await fetch(URL, {
@@ -245,15 +267,22 @@ const fetchLatestBaileysVersion = async (options = {}) => {
245
267
  signal,
246
268
  method: 'GET'
247
269
  })
270
+ if (!result.ok) {
271
+ throw new Boom(`Failed to fetch package.json: ${result.statusText}`, { statusCode: result.status })
272
+ }
248
273
  const json = await result.json()
249
- return {
250
- version: json.version,
251
- isLatest: true
274
+ const remoteVersion = parseWaVersion(json.whatsappVersion)
275
+ if (remoteVersion) {
276
+ return {
277
+ version: remoteVersion,
278
+ isLatest: true
279
+ }
252
280
  }
281
+ throw new Boom('whatsappVersion not defined or invalid in remote package.json')
253
282
  }
254
283
  catch (error) {
255
284
  return {
256
- version: version,
285
+ version: getLocalPackageWaVersion(),
257
286
  isLatest: false,
258
287
  error
259
288
  }
@@ -291,7 +320,7 @@ const fetchLatestWaWebVersion = async (options = {}) => {
291
320
 
292
321
  if (!match?.[1]) {
293
322
  return {
294
- version: version,
323
+ version: getLocalPackageWaVersion(),
295
324
  isLatest: false,
296
325
  error: {
297
326
  message: 'Could not find client revision in the fetched content'
@@ -307,7 +336,7 @@ const fetchLatestWaWebVersion = async (options = {}) => {
307
336
  }
308
337
  catch (error) {
309
338
  return {
310
- version: version,
339
+ version: getLocalPackageWaVersion(),
311
340
  isLatest: false,
312
341
  error
313
342
  }
@@ -382,9 +411,19 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
382
411
  case 'accept':
383
412
  status = 'accept'
384
413
  break
385
- default:
414
+ case 'relay':
415
+ status = 'relay'
416
+ break
417
+ case 'relaylatency':
418
+ status = 'relaylatency'
419
+ break
420
+ case 'ring':
421
+ case 'ringing':
386
422
  status = 'ringing'
387
423
  break
424
+ default:
425
+ status = tag || 'ringing'
426
+ break
388
427
  }
389
428
  return status
390
429
  }
@@ -15,6 +15,7 @@ export declare const Browsers: {
15
15
  macOS(browser: string): [string, string, string]
16
16
  baileys(browser: string): [string, string, string]
17
17
  windows(browser: string): [string, string, string]
18
+ android(browser: string): [string, string, string]
18
19
  appropriate(browser: string): [string, string, string]
19
20
  }
20
21
 
@@ -17,11 +17,17 @@ const {
17
17
  createReadStream,
18
18
  createWriteStream
19
19
  } = require("fs")
20
- const {
21
- parseBuffer,
22
- parseFile,
23
- parseStream
24
- } = require('music-metadata')
20
+ let _musicMetadata;
21
+ const getMusicMetadata = async () => {
22
+ if (!_musicMetadata) {
23
+ try {
24
+ _musicMetadata = require('music-metadata');
25
+ } catch {
26
+ _musicMetadata = await import('music-metadata');
27
+ }
28
+ }
29
+ return _musicMetadata;
30
+ };
25
31
  const { tmpdir } = require("os")
26
32
  const { join } = require("path")
27
33
  const {
@@ -175,21 +181,28 @@ const extractImageThumb = async (bufferOrFilePath, width = 32, quality = 50) =>
175
181
  }
176
182
  }
177
183
  }
178
- else if ('jimp' in lib && typeof lib.jimp.read === 'function') {
179
- const { read, MIME_JPEG, RESIZE_BEZIER, AUTO } = lib.jimp
180
- const jimp = await read(bufferOrFilePath)
184
+ else if ('jimp' in lib && (typeof lib.jimp.read === 'function' || typeof lib.jimp.Jimp?.read === 'function')) {
185
+ const JimpClass = lib.jimp.Jimp || lib.jimp;
186
+ const jimp = await JimpClass.read(bufferOrFilePath);
181
187
  const dimensions = {
182
- width: jimp.getWidth(),
183
- height: jimp.getHeight()
188
+ width: typeof jimp.getWidth === 'function' ? jimp.getWidth() : jimp.width,
189
+ height: typeof jimp.getHeight === 'function' ? jimp.getHeight() : jimp.height
190
+ };
191
+ let buffer;
192
+ if (typeof jimp.getBufferAsync === 'function') {
193
+ const { MIME_JPEG, RESIZE_BEZIER, AUTO } = lib.jimp;
194
+ buffer = await jimp
195
+ .quality(quality)
196
+ .resize(width, AUTO, RESIZE_BEZIER)
197
+ .getBufferAsync(MIME_JPEG);
198
+ } else {
199
+ jimp.resize({ w: width });
200
+ buffer = await jimp.getBuffer(lib.jimp.JimpMime?.jpeg || 'image/jpeg', { quality });
184
201
  }
185
- const buffer = await jimp
186
- .quality(quality)
187
- .resize(width, AUTO, RESIZE_BEZIER)
188
- .getBufferAsync(MIME_JPEG)
189
202
  return {
190
203
  buffer,
191
204
  original: dimensions
192
- }
205
+ };
193
206
  }
194
207
  else {
195
208
  throw new Boom('No image processing library available')
@@ -227,11 +240,21 @@ const generateProfilePicture = async (mediaUpload, dimensions) => {
227
240
  quality: 50
228
241
  }).toBuffer()
229
242
  }
230
- else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
231
- const jimp = await lib.jimp.read(buffer)
232
- const min = Math.min(jimp.width, jimp.height)
233
- const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min })
234
- img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 })
243
+ else if ('jimp' in lib && (typeof lib.jimp?.read === 'function' || typeof lib.jimp?.Jimp?.read === 'function')) {
244
+ const JimpClass = lib.jimp.Jimp || lib.jimp;
245
+ const jimp = await JimpClass.read(buffer);
246
+ const imgWidth = typeof jimp.getWidth === 'function' ? jimp.getWidth() : jimp.width;
247
+ const imgHeight = typeof jimp.getHeight === 'function' ? jimp.getHeight() : jimp.height;
248
+ const min = Math.min(imgWidth, imgHeight);
249
+ const cropped = typeof jimp.crop === 'function' && jimp.crop.length > 1
250
+ ? jimp.crop(0, 0, min, min)
251
+ : jimp.crop({ x: 0, y: 0, w: min, h: min });
252
+ const mode = lib.jimp.ResizeStrategy?.BILINEAR || lib.jimp.RESIZE_BILINEAR;
253
+ if (typeof cropped.getBufferAsync === 'function') {
254
+ img = cropped.resize(w, h, mode).getBufferAsync(lib.jimp.MIME_JPEG);
255
+ } else {
256
+ img = cropped.resize({ w, h, mode }).getBuffer(lib.jimp.JimpMime?.jpeg || 'image/jpeg', { quality: 50 });
257
+ }
235
258
  }
236
259
  else {
237
260
  throw new Boom('No image processing library available');
@@ -251,7 +274,7 @@ async function getAudioDuration(buffer) {
251
274
  const options = {
252
275
  duration: true
253
276
  }
254
-
277
+ const { parseBuffer, parseFile, parseStream } = await getMusicMetadata();
255
278
  let metadata
256
279
 
257
280
  if (Buffer.isBuffer(buffer)) {
@@ -2,7 +2,22 @@
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", { value: true })
4
4
 
5
- const { default: PQueue } = require("p-queue")
5
+ let PQueue
6
+ try {
7
+ const pq = require("p-queue")
8
+ PQueue = pq.default || pq
9
+ } catch {
10
+ PQueue = class SimpleQueue {
11
+ constructor() {
12
+ this._chain = Promise.resolve()
13
+ }
14
+ add(fn) {
15
+ const result = this._chain.then(() => fn())
16
+ this._chain = result.catch(() => {})
17
+ return result
18
+ }
19
+ }
20
+ }
6
21
 
7
22
  /**
8
23
  * Manages pre-key operations with proper concurrency control
@@ -30,9 +30,9 @@ const getUserAgent = (config) => {
30
30
  secondary: config.version[1],
31
31
  tertiary: config.version[2],
32
32
  },
33
- platform: /*config.browser[1].toLocaleLowerCase().includes('android')
34
- ? proto.ClientPayload.UserAgent.Platform.ANDROID
35
- :*/ proto.ClientPayload.UserAgent.Platform.WEB,
33
+ platform: config.browser?.[1]?.toLowerCase().includes('android')
34
+ ? proto.ClientPayload.UserAgent.Platform.ANDROID
35
+ : proto.ClientPayload.UserAgent.Platform.WEB,
36
36
  releaseChannel: proto.ClientPayload.UserAgent.ReleaseChannel.RELEASE,
37
37
  osVersion: config.browser?.[2] || '10.0.22631',
38
38
  device: 'Desktop',
@@ -46,7 +46,7 @@ const getUserAgent = (config) => {
46
46
 
47
47
  const PLATFORM_MAP = {
48
48
  'Mac OS': proto.ClientPayload.WebInfo.WebSubPlatform.DARWIN,
49
- 'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN32
49
+ 'Windows': proto.ClientPayload.WebInfo.WebSubPlatform.WIN_HYBRID
50
50
  }
51
51
 
52
52
  const getWebInfo = (config) => {
@@ -66,10 +66,9 @@ const getClientPayload = (config) => {
66
66
  userAgent: getUserAgent(config),
67
67
  }
68
68
 
69
- /*if (!config.browser[1].toLocaleLowerCase().includes('android')) {
70
- payload.webInfo = getWebInfo(config)
71
- }*/
72
- payload.webInfo = getWebInfo(config)
69
+ if (!config.browser?.[1]?.toLowerCase().includes('android')) {
70
+ payload.webInfo = getWebInfo(config)
71
+ }
73
72
 
74
73
  return payload
75
74
  }
@@ -91,9 +90,9 @@ const generateLoginNode = (userJid, config) => {
91
90
  const getPlatformType = (platform) => {
92
91
  const platformType = platform.toUpperCase()
93
92
 
94
- /*if (platformType === 'ANDROID') {
95
- return proto.DeviceProps.PlatformType.ANDROID_PHONE
96
- }*/
93
+ if (platformType === 'ANDROID') {
94
+ return proto.DeviceProps.PlatformType.ANDROID_PHONE
95
+ }
97
96
 
98
97
  return (proto.DeviceProps.PlatformType[platformType] ||
99
98
  proto.DeviceProps.PlatformType.CHROME)
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+
5
+ const crypto = require("crypto");
6
+
7
+ /**
8
+ * WhatsApp VoIP SRTP Key Derivation & Packet Protection
9
+ * Conforms to WhatsApp VoIP specifications (wacrg / AstraCalls / RFC 3711)
10
+ */
11
+
12
+ const SRTP_LABEL = {
13
+ CALLER_AUDIO_KEY: "WhatsApp VoIP Master Key Caller Audio",
14
+ CALLER_AUDIO_SALT: "WhatsApp VoIP Master Salt Caller Audio",
15
+ CALLEE_AUDIO_KEY: "WhatsApp VoIP Master Key Callee Audio",
16
+ CALLEE_AUDIO_SALT: "WhatsApp VoIP Master Salt Callee Audio",
17
+ CALLER_VIDEO_KEY: "WhatsApp VoIP Master Key Caller Video",
18
+ CALLER_VIDEO_SALT: "WhatsApp VoIP Master Salt Caller Video",
19
+ CALLEE_VIDEO_KEY: "WhatsApp VoIP Master Key Callee Video",
20
+ CALLEE_VIDEO_SALT: "WhatsApp VoIP Master Salt Callee Video",
21
+ };
22
+
23
+ /**
24
+ * Standard HKDF extraction & expansion using SHA-256
25
+ */
26
+ function hkdfDerive(ikm, salt, info, length) {
27
+ const actualSalt = salt && salt.length > 0 ? salt : Buffer.alloc(32, 0);
28
+ const prk = crypto.createHmac("sha256", actualSalt).update(ikm).digest();
29
+
30
+ let prev = Buffer.alloc(0);
31
+ const buffers = [];
32
+ let currentLen = 0;
33
+ let counter = 1;
34
+
35
+ while (currentLen < length) {
36
+ const hmac = crypto.createHmac("sha256", prk);
37
+ hmac.update(prev);
38
+ if (info) {
39
+ hmac.update(Buffer.isBuffer(info) ? info : Buffer.from(info, "utf8"));
40
+ }
41
+ hmac.update(Buffer.from([counter]));
42
+ prev = hmac.digest();
43
+ buffers.push(prev);
44
+ currentLen += prev.length;
45
+ counter++;
46
+ }
47
+
48
+ return Buffer.concat(buffers).subarray(0, length);
49
+ }
50
+
51
+ /**
52
+ * Derives all SRTP Master Keys and Salts from the 32-byte callKey
53
+ * @param {Buffer|Uint8Array} callKey 32-byte master call key negotiated via Signal
54
+ * @param {Buffer|Uint8Array} [salt] Optional salt (defaults to 32 zero bytes)
55
+ */
56
+ function deriveVoIPKeys(callKey, salt = null) {
57
+ const keyBuf = Buffer.isBuffer(callKey) ? callKey : Buffer.from(callKey);
58
+ const saltBuf = salt ? (Buffer.isBuffer(salt) ? salt : Buffer.from(salt)) : Buffer.alloc(32, 0);
59
+
60
+ return {
61
+ // Caller (TX if caller, RX if callee)
62
+ callerAudio: {
63
+ masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_AUDIO_KEY, 16),
64
+ masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_AUDIO_SALT, 14),
65
+ },
66
+ // Callee (RX if caller, TX if callee)
67
+ calleeAudio: {
68
+ masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_AUDIO_KEY, 16),
69
+ masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_AUDIO_SALT, 14),
70
+ },
71
+ // Video Caller
72
+ callerVideo: {
73
+ masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_VIDEO_KEY, 16),
74
+ masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLER_VIDEO_SALT, 14),
75
+ },
76
+ // Video Callee
77
+ calleeVideo: {
78
+ masterKey: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_VIDEO_KEY, 16),
79
+ masterSalt: hkdfDerive(keyBuf, saltBuf, SRTP_LABEL.CALLEE_VIDEO_SALT, 14),
80
+ },
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Generates an SRTP Initialization Vector (IV) from SSRC, Sequence Number, and Master Salt
86
+ * Conforms to RFC 3711 Section 4.1.1
87
+ */
88
+ function computeSRTPIV(masterSalt, ssrc, seqNo, roc = 0) {
89
+ const iv = Buffer.alloc(16, 0);
90
+
91
+ // 48-bit index: (roc << 16) | seqNo
92
+ const indexBuf = Buffer.alloc(8, 0);
93
+ indexBuf.writeUInt32BE(roc, 0);
94
+ indexBuf.writeUInt16BE(seqNo, 4);
95
+
96
+ // IV = (ssrc << 64) ^ (index << 16) ^ (masterSalt << 16)
97
+ masterSalt.copy(iv, 0, 0, 14);
98
+
99
+ // XOR SSRC at bytes 4-7
100
+ iv[4] ^= (ssrc >>> 24) & 0xff;
101
+ iv[5] ^= (ssrc >>> 16) & 0xff;
102
+ iv[6] ^= (ssrc >>> 8) & 0xff;
103
+ iv[7] ^= ssrc & 0xff;
104
+
105
+ // XOR index at bytes 6-13
106
+ for (let i = 0; i < 6; i++) {
107
+ iv[8 + i] ^= indexBuf[2 + i];
108
+ }
109
+
110
+ return iv;
111
+ }
112
+
113
+ /**
114
+ * Encrypts an RTP payload using AES-128-CTR (SRTP Default)
115
+ */
116
+ function encryptRTPPayload(payload, key, iv) {
117
+ const cipher = crypto.createCipheriv("aes-128-ctr", key, iv);
118
+ return Buffer.concat([cipher.update(payload), cipher.final()]);
119
+ }
120
+
121
+ /**
122
+ * Decrypts an SRTP payload using AES-128-CTR
123
+ */
124
+ function decryptRTPPayload(encryptedPayload, key, iv) {
125
+ const decipher = crypto.createDecipheriv("aes-128-ctr", key, iv);
126
+ return Buffer.concat([decipher.update(encryptedPayload), decipher.final()]);
127
+ }
128
+
129
+ /**
130
+ * Computes HMAC-SHA1-80 authentication tag (10 bytes) for SRTP packet
131
+ */
132
+ function computeAuthTag(authKey, packetData, roc = 0) {
133
+ const rocBuf = Buffer.alloc(4);
134
+ rocBuf.writeUInt32BE(roc, 0);
135
+
136
+ const hmac = crypto.createHmac("sha1", authKey);
137
+ hmac.update(packetData);
138
+ hmac.update(rocBuf);
139
+ return hmac.digest().subarray(0, 10);
140
+ }
141
+
142
+ module.exports = {
143
+ SRTP_LABEL,
144
+ hkdfDerive,
145
+ deriveVoIPKeys,
146
+ computeSRTPIV,
147
+ encryptRTPPayload,
148
+ decryptRTPPayload,
149
+ computeAuthTag,
150
+ };