whalibmob 3.0.0 → 3.3.1

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.
@@ -224,6 +224,15 @@ class DeviceManager {
224
224
  return readyJids;
225
225
  }
226
226
 
227
+ // ─── Clear device cache (used on phash mismatch / 421 retry) ──────────────
228
+ clearCache(phones) {
229
+ if (!phones || phones.length === 0) {
230
+ this._deviceCache.clear();
231
+ } else {
232
+ for (const p of phones) this._deviceCache.delete(p);
233
+ }
234
+ }
235
+
227
236
  // ─── Build participants node ───────────────────────────────────────────────
228
237
  static buildParticipantsNode(encryptedList) {
229
238
  const toNodes = encryptedList.map(({ jid, type, ciphertext }) =>
@@ -242,7 +242,7 @@ function buildPayload(store, waVersion, useToken, extraPairs) {
242
242
  'rc', String(RELEASE_CHANNEL),
243
243
  'lg', meta.lg,
244
244
  'lc', meta.lc,
245
- 'authkey', store.noiseKeyPair.public.toString('base64url'),
245
+ 'authkey', rawKey(store.noiseKeyPair.public).toString('base64url'),
246
246
  'e_regid', intToBytes(store.registrationId, 4).toString('base64url'),
247
247
  'e_keytype', Buffer.from([SIGNAL_KEY_TYPE]).toString('base64url'),
248
248
  'e_ident', rawKey(store.identityKeyPair.public).toString('base64url'),
package/lib/Store.js CHANGED
@@ -200,14 +200,15 @@ function fromSixParts(sixParts) {
200
200
  private: spkPair.private,
201
201
  signature: sig
202
202
  },
203
- registrationId: 1,
203
+ registrationId: (crypto.randomBytes(2).readUInt16BE(0) & 0x3fff) + 1,
204
204
  fdid: uuidv4(),
205
205
  deviceId: crypto.randomBytes(16),
206
206
  identityId,
207
207
  registered: true,
208
208
  name: 'User',
209
209
  version: IOS_VERSION_FALLBACK,
210
- device: IOS_DEVICE
210
+ device: IOS_DEVICE,
211
+ advIdentity: null
211
212
  };
212
213
  }
213
214
 
@@ -281,7 +281,7 @@ class MessageSender {
281
281
  isAnimated: options.isAnimated || false,
282
282
  contextInfo: options.contextInfo
283
283
  }));
284
- return this._sendMessage(toJid, msgId, stkBuf, 'text', options);
284
+ return this._sendMessage(toJid, msgId, stkBuf, 'media', options);
285
285
  }
286
286
 
287
287
  // ─── Reaction ─────────────────────────────────────────────────────────────
@@ -302,9 +302,31 @@ class MessageSender {
302
302
 
303
303
  async _sendMessage(toJid, msgId, plaintext, mediaType, options) {
304
304
  if (isGroupJid(toJid)) {
305
- return this._sendGroupMessage(toJid, msgId, plaintext, mediaType);
305
+ try {
306
+ return await this._sendGroupMessage(toJid, msgId, plaintext, mediaType);
307
+ } catch (err) {
308
+ // Error 421 = phash mismatch — server's participant list differs from ours.
309
+ // Flush device cache for group members and retry once (matches Cobalt behaviour).
310
+ if (err && /\b421\b/.test(err.message)) {
311
+ const members = this._client._getGroupMembers
312
+ ? this._client._getGroupMembers(toJid)
313
+ : [];
314
+ this._devMgr.clearCache(members.map(phoneFromJid));
315
+ return this._sendGroupMessage(toJid, msgId, plaintext, mediaType);
316
+ }
317
+ throw err;
318
+ }
319
+ }
320
+ try {
321
+ return await this._sendDMMessage(toJid, msgId, plaintext, mediaType);
322
+ } catch (err) {
323
+ // Same phash-mismatch retry for 1-to-1 messages.
324
+ if (err && /\b421\b/.test(err.message)) {
325
+ this._devMgr.clearCache([phoneFromJid(toJid)]);
326
+ return this._sendDMMessage(toJid, msgId, plaintext, mediaType);
327
+ }
328
+ throw err;
306
329
  }
307
- return this._sendDMMessage(toJid, msgId, plaintext, mediaType);
308
330
  }
309
331
 
310
332
  // ─── 1-to-1 multi-device fanout ───────────────────────────────────────────
@@ -403,6 +425,13 @@ class MessageSender {
403
425
 
404
426
  const allTargets = [...memberDevices, ...ownDevices];
405
427
 
428
+ // phash MUST include the sender's own primary device.
429
+ // Cobalt's calculateGroupPhash() explicitly adds senderDevice to the set.
430
+ // ownDevices contains only linked devices (device != 0); ownJid is device 0.
431
+ const phashTargets = allTargets.includes(ownJid)
432
+ ? allTargets
433
+ : [ownJid, ...allTargets];
434
+
406
435
  // senderKeyMap: only send SKDM to devices that haven't received it yet.
407
436
  // Persisted in .sk.json so we don't re-send on every group message.
408
437
  const skStore = this._signal.senderKeyStore;
@@ -418,8 +447,8 @@ class MessageSender {
418
447
  // SenderKey encrypt the actual group message (one ciphertext for all)
419
448
  const skmsgCiphertext = this._signal.senderKeyEncrypt(groupJid, ownJid, plaintext);
420
449
 
421
- // phash over all group member devices
422
- const phash = allTargets.length > 0 ? computePhash(allTargets) : null;
450
+ // phash over all group member devices + sender primary device
451
+ const phash = phashTargets.length > 0 ? computePhash(phashTargets) : null;
423
452
 
424
453
  // ── Feature 2: device_identity for pkmsg in SKDM ─────────────────────────
425
454
  // SKDM messages sent to devices with no prior Signal session are pkmsg.
package/lib/noise.js CHANGED
@@ -285,7 +285,7 @@ class NoiseSocket extends EventEmitter {
285
285
  const payload = encodeClientPayload({
286
286
  username: BigInt(this.store.phoneNumber),
287
287
  passive: false,
288
- pushName: this.store.name || 'User',
288
+ pushName: this.store.registered ? (this.store.name || null) : null,
289
289
  shortConnect: true,
290
290
  connectType: 1,
291
291
  connectReason: 1,
package/lib/proto.js CHANGED
@@ -43,7 +43,7 @@ function encodeUserAgent({ platform, version, mcc, mnc, osVersion, manufacturer,
43
43
  ];
44
44
  if (osVersion) parts.push(field(5, LEN, string(osVersion)));
45
45
  if (manufacturer) parts.push(field(6, LEN, string(manufacturer)));
46
- if (device) parts.push(field(7, LEN, string(device)));
46
+ if (device) parts.push(field(7, LEN, string(String(device).replace(/_/g, ' '))));
47
47
  if (osBuildNumber) parts.push(field(8, LEN, string(osBuildNumber)));
48
48
  if (phoneId) parts.push(field(9, LEN, string(phoneId)));
49
49
  parts.push(field(10, VARINT, varint(releaseChannel || 0)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whalibmob",
3
- "version": "3.0.0",
3
+ "version": "3.3.1",
4
4
  "description": "WhatsApp mobile-only Node.js client library with Signal Protocol encryption",
5
5
  "main": "index.js",
6
6
  "bin": {