zapo-js 1.6.2 → 1.7.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 (141) hide show
  1. package/dist/abprops-spec.d.ts +10 -0
  2. package/dist/abprops-spec.js +25 -0
  3. package/dist/client/WaClient.d.ts +11 -0
  4. package/dist/client/WaClient.js +60 -0
  5. package/dist/client/WaClientFactory.d.ts +2 -2
  6. package/dist/client/WaClientFactory.js +50 -12
  7. package/dist/client/coordinators/WaAbPropsCoordinator.d.ts +2 -2
  8. package/dist/client/coordinators/WaAbPropsCoordinator.js +16 -4
  9. package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +2 -1
  10. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  11. package/dist/client/coordinators/WaMessageCoordinator.d.ts +96 -0
  12. package/dist/client/coordinators/WaMessageCoordinator.js +163 -8
  13. package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +64 -0
  14. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +217 -30
  15. package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +97 -19
  16. package/dist/client/coordinators/WaPrivacyCoordinator.js +152 -101
  17. package/dist/client/coordinators/WaRetryCoordinator.d.ts +22 -0
  18. package/dist/client/coordinators/WaRetryCoordinator.js +74 -11
  19. package/dist/client/events/dirty.d.ts +3 -0
  20. package/dist/client/events/dirty.js +12 -7
  21. package/dist/client/events/offline.d.ts +9 -0
  22. package/dist/client/events/offline.js +95 -0
  23. package/dist/client/events/privacy.d.ts +45 -0
  24. package/dist/client/events/privacy.js +130 -0
  25. package/dist/client/messaging/group-metadata.d.ts +12 -0
  26. package/dist/client/messaging/group-metadata.js +24 -8
  27. package/dist/client/messaging/link-preview.d.ts +7 -0
  28. package/dist/client/messaging/link-preview.js +27 -0
  29. package/dist/client/newsletter/content.d.ts +4 -1
  30. package/dist/client/newsletter/content.js +43 -18
  31. package/dist/client/newsletter/messaging.d.ts +2 -0
  32. package/dist/client/newsletter/messaging.js +2 -1
  33. package/dist/client/newsletter/mex.d.ts +2 -2
  34. package/dist/client/persistence/WriteBehindPersistence.js +2 -1
  35. package/dist/client/persistence/ephemeral-setting.d.ts +42 -0
  36. package/dist/client/persistence/ephemeral-setting.js +120 -0
  37. package/dist/client/persistence/group-history.d.ts +29 -0
  38. package/dist/client/persistence/group-history.js +149 -0
  39. package/dist/client/persistence/history-blob.d.ts +25 -0
  40. package/dist/client/persistence/history-blob.js +38 -0
  41. package/dist/client/persistence/history-sync.d.ts +2 -0
  42. package/dist/client/persistence/history-sync.js +31 -29
  43. package/dist/client/types.d.ts +150 -16
  44. package/dist/crypto/core/xeddsa.js +16 -0
  45. package/dist/crypto/curves/X25519.js +13 -0
  46. package/dist/crypto/nativeBackend.d.ts +20 -0
  47. package/dist/crypto/nativeBackend.js +95 -0
  48. package/dist/esm/abprops-spec.js +15 -0
  49. package/dist/esm/client/WaClient.js +60 -0
  50. package/dist/esm/client/WaClientFactory.js +51 -13
  51. package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +17 -5
  52. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  53. package/dist/esm/client/coordinators/WaMessageCoordinator.js +166 -11
  54. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +218 -32
  55. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +145 -94
  56. package/dist/esm/client/coordinators/WaRetryCoordinator.js +74 -11
  57. package/dist/esm/client/events/dirty.js +12 -7
  58. package/dist/esm/client/events/offline.js +92 -0
  59. package/dist/esm/client/events/privacy.js +123 -0
  60. package/dist/esm/client/messaging/group-metadata.js +25 -9
  61. package/dist/esm/client/messaging/link-preview.js +29 -2
  62. package/dist/esm/client/newsletter/content.js +44 -19
  63. package/dist/esm/client/newsletter/messaging.js +2 -1
  64. package/dist/esm/client/persistence/WriteBehindPersistence.js +2 -1
  65. package/dist/esm/client/persistence/ephemeral-setting.js +116 -0
  66. package/dist/esm/client/persistence/group-history.js +145 -0
  67. package/dist/esm/client/persistence/history-blob.js +34 -0
  68. package/dist/esm/client/persistence/history-sync.js +24 -22
  69. package/dist/esm/crypto/core/xeddsa.js +17 -1
  70. package/dist/esm/crypto/curves/X25519.js +13 -0
  71. package/dist/esm/crypto/nativeBackend.js +93 -0
  72. package/dist/esm/index.js +3 -1
  73. package/dist/esm/message/context-info.js +39 -0
  74. package/dist/esm/message/encode/content.js +20 -4
  75. package/dist/esm/message/encode/media-payload.js +3 -1
  76. package/dist/esm/message/kinds/group-history.js +36 -0
  77. package/dist/esm/message/primitives/incoming.js +18 -6
  78. package/dist/esm/protocol/abprops.js +18 -162
  79. package/dist/esm/protocol/constants.js +4 -3
  80. package/dist/esm/protocol/defaults.js +6 -0
  81. package/dist/esm/protocol/media.js +1 -0
  82. package/dist/esm/protocol/message.js +8 -0
  83. package/dist/esm/protocol/notification.js +1 -0
  84. package/dist/esm/protocol/privacy.js +70 -4
  85. package/dist/esm/store/contracts/chat-metadata.store.js +1 -0
  86. package/dist/esm/store/createStore.js +19 -2
  87. package/dist/esm/store/index.js +1 -0
  88. package/dist/esm/store/memory/chat-metadata.store.js +66 -0
  89. package/dist/esm/store/noop.store.js +8 -0
  90. package/dist/esm/transport/node/builders/privacy.js +63 -4
  91. package/dist/esm/util/coercion.js +12 -4
  92. package/dist/esm/util/index.js +1 -1
  93. package/dist/index.d.ts +11 -6
  94. package/dist/index.js +15 -2
  95. package/dist/media/types.d.ts +1 -1
  96. package/dist/message/context-info.d.ts +10 -0
  97. package/dist/message/context-info.js +40 -0
  98. package/dist/message/encode/content.d.ts +7 -3
  99. package/dist/message/encode/content.js +20 -4
  100. package/dist/message/encode/media-payload.d.ts +5 -2
  101. package/dist/message/encode/media-payload.js +3 -1
  102. package/dist/message/kinds/group-history.d.ts +30 -0
  103. package/dist/message/kinds/group-history.js +40 -0
  104. package/dist/message/primitives/incoming.d.ts +5 -0
  105. package/dist/message/primitives/incoming.js +18 -6
  106. package/dist/protocol/abprops.d.ts +27 -140
  107. package/dist/protocol/abprops.js +18 -163
  108. package/dist/protocol/constants.d.ts +4 -3
  109. package/dist/protocol/constants.js +10 -3
  110. package/dist/protocol/defaults.d.ts +6 -0
  111. package/dist/protocol/defaults.js +6 -0
  112. package/dist/protocol/media.d.ts +1 -0
  113. package/dist/protocol/media.js +1 -0
  114. package/dist/protocol/message.d.ts +6 -0
  115. package/dist/protocol/message.js +9 -0
  116. package/dist/protocol/notification.d.ts +1 -0
  117. package/dist/protocol/notification.js +1 -0
  118. package/dist/protocol/privacy.d.ts +52 -0
  119. package/dist/protocol/privacy.js +71 -5
  120. package/dist/store/contracts/chat-metadata.store.d.ts +34 -0
  121. package/dist/store/contracts/chat-metadata.store.js +2 -0
  122. package/dist/store/contracts/group-metadata.store.d.ts +7 -0
  123. package/dist/store/contracts/thread.store.d.ts +6 -0
  124. package/dist/store/createStore.d.ts +10 -2
  125. package/dist/store/createStore.js +18 -1
  126. package/dist/store/index.d.ts +3 -1
  127. package/dist/store/index.js +3 -1
  128. package/dist/store/memory/chat-metadata.store.d.ts +26 -0
  129. package/dist/store/memory/chat-metadata.store.js +70 -0
  130. package/dist/store/noop.store.d.ts +2 -0
  131. package/dist/store/noop.store.js +9 -1
  132. package/dist/store/types.d.ts +190 -39
  133. package/dist/transport/node/builders/privacy.d.ts +26 -2
  134. package/dist/transport/node/builders/privacy.js +63 -3
  135. package/dist/util/coercion.d.ts +6 -0
  136. package/dist/util/coercion.js +13 -4
  137. package/dist/util/index.d.ts +1 -1
  138. package/dist/util/index.js +2 -1
  139. package/package.json +3 -1
  140. package/spec/abprops/index.d.ts +4402 -0
  141. package/spec/abprops/index.js +4372 -0
@@ -0,0 +1,10 @@
1
+ import { type WaAbPropName } from '../spec/abprops';
2
+ export { WA_ABPROPS, WA_ABPROPS_BY_CODE, WA_ABPROPS_SPECIAL_EARLY, WA_ABPROPS_USED_BEFORE_INIT, WA_GROUP_ABPROPS, WA_GROUP_ABPROPS_BY_CODE } from '../spec/abprops';
3
+ export type { WaAbProp, WaAbPropName, WaAbPropType, WaAbPropValue, WaAbPropValueByName, WaAbPropValueOf, WaGroupAbPropName, WaGroupAbPropValueByName } from '../spec/abprops';
4
+ /**
5
+ * Resolves the client-side prop name for a wire config code.
6
+ *
7
+ * Returns `undefined` for codes this build does not know about - the server
8
+ * advertises props from newer bundles, and WA Web drops those rows too.
9
+ */
10
+ export declare function resolveAbPropNameByCode(configCode: number): WaAbPropName | undefined;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WA_GROUP_ABPROPS_BY_CODE = exports.WA_GROUP_ABPROPS = exports.WA_ABPROPS_USED_BEFORE_INIT = exports.WA_ABPROPS_SPECIAL_EARLY = exports.WA_ABPROPS_BY_CODE = exports.WA_ABPROPS = void 0;
4
+ exports.resolveAbPropNameByCode = resolveAbPropNameByCode;
5
+ const abprops_1 = require("../spec/abprops");
6
+ var abprops_2 = require("../spec/abprops");
7
+ Object.defineProperty(exports, "WA_ABPROPS", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS; } });
8
+ Object.defineProperty(exports, "WA_ABPROPS_BY_CODE", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_BY_CODE; } });
9
+ Object.defineProperty(exports, "WA_ABPROPS_SPECIAL_EARLY", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_SPECIAL_EARLY; } });
10
+ Object.defineProperty(exports, "WA_ABPROPS_USED_BEFORE_INIT", { enumerable: true, get: function () { return abprops_2.WA_ABPROPS_USED_BEFORE_INIT; } });
11
+ Object.defineProperty(exports, "WA_GROUP_ABPROPS", { enumerable: true, get: function () { return abprops_2.WA_GROUP_ABPROPS; } });
12
+ Object.defineProperty(exports, "WA_GROUP_ABPROPS_BY_CODE", { enumerable: true, get: function () { return abprops_2.WA_GROUP_ABPROPS_BY_CODE; } });
13
+ // The reverse map is emitted with numeric literal keys, so it cannot be indexed
14
+ // with a plain `number`. Widening it keeps the lookup a single property read and
15
+ // avoids allocating a parallel Map with 2000+ entries at module init.
16
+ const AB_PROP_NAME_BY_CODE = abprops_1.WA_ABPROPS_BY_CODE;
17
+ /**
18
+ * Resolves the client-side prop name for a wire config code.
19
+ *
20
+ * Returns `undefined` for codes this build does not know about - the server
21
+ * advertises props from newer bundles, and WA Web drops those rows too.
22
+ */
23
+ function resolveAbPropNameByCode(configCode) {
24
+ return AB_PROP_NAME_BY_CODE[configCode];
25
+ }
@@ -27,6 +27,7 @@ declare class WaClientImpl extends EventEmitter {
27
27
  private readonly appStateSync;
28
28
  private readonly mediaTransfer;
29
29
  private readonly writeBehind;
30
+ private readonly observeEphemeralSetting;
30
31
  private connectPromise;
31
32
  private acceptingIncomingEvents;
32
33
  private activeIncomingHandlers;
@@ -74,6 +75,16 @@ declare class WaClientImpl extends EventEmitter {
74
75
  getClockSkewMs(): number | null;
75
76
  private bindNodeTransportEvents;
76
77
  private handleIncomingMessageEvent;
78
+ /**
79
+ * Kicks off the group-history bundle download when the incoming message
80
+ * carries one. Fire-and-forget: the blob can be large, and the incoming
81
+ * handler must not stall behind a CDN fetch.
82
+ *
83
+ * The download outlives the handler that started it, so it takes a slot of
84
+ * its own in the drain accounting - `disconnect()` and `clearStoredState()`
85
+ * must not flush or wipe the stores while a bundle is still writing.
86
+ */
87
+ private tryProcessGroupHistoryBundle;
77
88
  private queryWithContext;
78
89
  private handleIncomingFrame;
79
90
  /**
@@ -3,12 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WaClient = void 0;
4
4
  const node_events_1 = require("node:events");
5
5
  const ignore_key_1 = require("./messaging/ignore-key");
6
+ const ephemeral_setting_1 = require("./persistence/ephemeral-setting");
7
+ const group_history_1 = require("./persistence/group-history");
6
8
  const history_sync_1 = require("./persistence/history-sync");
7
9
  const mailbox_1 = require("./persistence/mailbox");
8
10
  const WriteBehindPersistence_1 = require("./persistence/WriteBehindPersistence");
9
11
  const install_1 = require("./plugins/install");
10
12
  const WaClientFactory_1 = require("./WaClientFactory");
11
13
  const ConsoleLogger_1 = require("../infra/log/ConsoleLogger");
14
+ const content_1 = require("../message/encode/content");
12
15
  const _proto_1 = require("../proto");
13
16
  const constants_1 = require("../protocol/constants");
14
17
  const jid_1 = require("../protocol/jid");
@@ -45,6 +48,10 @@ class WaClientImpl extends node_events_1.EventEmitter {
45
48
  threadStore: this.stores.threads,
46
49
  contactStore: this.stores.contacts
47
50
  }, this.logger, this.options.writeBehind);
51
+ this.observeEphemeralSetting = (0, ephemeral_setting_1.createEphemeralObserver)({
52
+ logger: this.logger,
53
+ chatMetadataStore: this.stores.chatMetadata
54
+ });
48
55
  const dependencies = (0, WaClientFactory_1.buildWaClientDependencies)({
49
56
  base,
50
57
  runtime: {
@@ -185,6 +192,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
185
192
  }
186
193
  try {
187
194
  this.emit('message', event);
195
+ this.observeEphemeralSetting(event);
188
196
  void (0, mailbox_1.persistIncomingMailboxEntities)({
189
197
  logger: this.logger,
190
198
  writeBehind: this.writeBehind,
@@ -210,6 +218,9 @@ class WaClientImpl extends node_events_1.EventEmitter {
210
218
  });
211
219
  });
212
220
  }
221
+ if (this.options.history?.groupBundles === true && event.message && !event.key.fromMe) {
222
+ this.tryProcessGroupHistoryBundle(event);
223
+ }
213
224
  const protocolMessage = event.message?.protocolMessage;
214
225
  if (!protocolMessage) {
215
226
  return;
@@ -261,6 +272,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
261
272
  await (0, history_sync_1.runHistorySyncNotification)({
262
273
  logger: this.logger,
263
274
  mediaTransfer: this.mediaTransfer,
275
+ chatMetadataStore: this.stores.chatMetadata,
264
276
  writeBehind: this.writeBehind,
265
277
  emitEvent: this.emit.bind(this),
266
278
  onPrivacyTokens: (conversations) => this.deps.trustedContactToken.hydrateFromHistorySync(conversations),
@@ -273,6 +285,16 @@ class WaClientImpl extends node_events_1.EventEmitter {
273
285
  }
274
286
  return;
275
287
  }
288
+ if (protocolType === _proto_1.proto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING) {
289
+ (0, ephemeral_setting_1.persistIncomingEphemeralSetting)({
290
+ logger: this.logger,
291
+ writeBehind: this.writeBehind,
292
+ chatMetadataStore: this.stores.chatMetadata,
293
+ event,
294
+ protocolMessage
295
+ });
296
+ return;
297
+ }
276
298
  if (SYNC_RELATED_PROTOCOL_TYPES.has(protocolType)) {
277
299
  this.logger.info('incoming sync-related protocol message', {
278
300
  id: event.key.id,
@@ -291,6 +313,43 @@ class WaClientImpl extends node_events_1.EventEmitter {
291
313
  this.leaveIncomingHandler();
292
314
  }
293
315
  }
316
+ /**
317
+ * Kicks off the group-history bundle download when the incoming message
318
+ * carries one. Fire-and-forget: the blob can be large, and the incoming
319
+ * handler must not stall behind a CDN fetch.
320
+ *
321
+ * The download outlives the handler that started it, so it takes a slot of
322
+ * its own in the drain accounting - `disconnect()` and `clearStoredState()`
323
+ * must not flush or wipe the stores while a bundle is still writing.
324
+ */
325
+ tryProcessGroupHistoryBundle(event) {
326
+ const bundle = (0, content_1.unwrapMessage)(event.message ?? {}).messageHistoryBundle;
327
+ const groupJid = event.key.remoteJid;
328
+ if (!bundle || !groupJid) {
329
+ return;
330
+ }
331
+ if (!this.tryEnterIncomingHandler()) {
332
+ return;
333
+ }
334
+ const credentials = this.deps.authClient.getCurrentCredentials();
335
+ void (0, group_history_1.runGroupHistoryBundle)({
336
+ logger: this.logger,
337
+ mediaTransfer: this.mediaTransfer,
338
+ writeBehind: this.writeBehind,
339
+ emitEvent: this.emit.bind(this),
340
+ meJid: credentials?.meJid,
341
+ meLid: credentials?.meLid,
342
+ getAbPropNumber: (name) => this.deps.abPropsCoordinator.getConfigValue(name)
343
+ }, {
344
+ bundle,
345
+ groupJid,
346
+ senderJid: event.key.participant ?? undefined,
347
+ bundleMessageId: event.key.id,
348
+ sentAtSeconds: event.timestampSeconds
349
+ }).finally(() => {
350
+ this.leaveIncomingHandler();
351
+ });
352
+ }
294
353
  async queryWithContext(context, node, timeoutMs = this.options.iqTimeoutMs ?? constants_1.WA_DEFAULTS.IQ_TIMEOUT_MS, contextData = {}, options = {}) {
295
354
  return (0, query_1.queryWithContext)(async (queryNode, queryTimeoutMs) => this.deps.lowLevelCoordinator.query(queryNode, queryTimeoutMs, options), this.logger, context, node, timeoutMs, contextData);
296
355
  }
@@ -365,6 +424,7 @@ class WaClientImpl extends node_events_1.EventEmitter {
365
424
  * your own backoff.
366
425
  */
367
426
  async disconnect() {
427
+ this.deps.privacyCoordinator.stopAccountSyncRefresh();
368
428
  await this.pauseIncomingEventsAndWaitDrain();
369
429
  const writeBehindFlush = await this.writeBehind.flush(this.options.writeBehind?.flushTimeoutMs);
370
430
  if (writeBehindFlush.remaining > 0) {
@@ -18,7 +18,7 @@ import { WaMobileCoordinator } from './coordinators/WaMobileCoordinator';
18
18
  import { type WaNewsletterCoordinator } from './coordinators/WaNewsletterCoordinator';
19
19
  import { WaPassiveTasksCoordinator } from './coordinators/WaPassiveTasksCoordinator';
20
20
  import { type WaPresenceCoordinator } from './coordinators/WaPresenceCoordinator';
21
- import { type WaPrivacyCoordinator } from './coordinators/WaPrivacyCoordinator';
21
+ import { type WaPrivacyCoordinatorRuntime } from './coordinators/WaPrivacyCoordinator';
22
22
  import { type WaProfileCoordinator } from './coordinators/WaProfileCoordinator';
23
23
  import { WaRetryCoordinator } from './coordinators/WaRetryCoordinator';
24
24
  import { type WaStatusCoordinator } from './coordinators/WaStatusCoordinator';
@@ -110,7 +110,7 @@ export interface WaClientDependencies {
110
110
  readonly statusCoordinator: WaStatusCoordinator;
111
111
  readonly broadcastListCoordinator: WaBroadcastListCoordinator;
112
112
  readonly newsletterCoordinator: WaNewsletterCoordinator;
113
- readonly privacyCoordinator: WaPrivacyCoordinator;
113
+ readonly privacyCoordinator: WaPrivacyCoordinatorRuntime;
114
114
  readonly profileCoordinator: WaProfileCoordinator;
115
115
  readonly businessCoordinator: WaBusinessCoordinator;
116
116
  readonly botCoordinator: WaBotCoordinator;
@@ -149,6 +149,7 @@ function createIncomingNodeRuntime(input) {
149
149
  emitIncomingFailure: (event) => emitEvent('stream_failure', event),
150
150
  emitIncomingErrorStanza: (event) => emitEvent('stanza_error', event),
151
151
  emitIncomingNotification: (event) => emitEvent('debug_notification', event),
152
+ emitOfflineThreadMetadata: (event) => emitEvent('offline_thread_metadata', event),
152
153
  emitMexNotification: (event) => emitEvent('mex_notification', event),
153
154
  emitRegistrationCode: (event) => emitEvent('mobile_registration_code', event),
154
155
  emitAccountTakeoverNotice: (event) => emitEvent('mobile_account_takeover_notice', event),
@@ -244,6 +245,15 @@ function buildWaClientDependencies(input) {
244
245
  allowPrivateHosts: linkPreviewOptions.allowPrivateHosts,
245
246
  proxy: linkPreviewOptions.proxy ?? options.proxy?.linkPreview
246
247
  });
248
+ const createLinkPreviewResolver = (surface) => (content) => (0, link_preview_1.resolveLinkPreview)(content.text, content.linkPreview, {
249
+ logger,
250
+ mediaTransfer,
251
+ getMediaConn: () => (0, messages_1.getMediaConn)(mediaMessageBuildOptions),
252
+ fetcher: linkPreviewFetcher,
253
+ options: linkPreviewOptions,
254
+ serverClock,
255
+ surface
256
+ });
247
257
  const mediaMessageBuildOptions = {
248
258
  logger,
249
259
  mediaTransfer,
@@ -261,14 +271,7 @@ function buildWaClientDependencies(input) {
261
271
  },
262
272
  serverClock,
263
273
  media: options.media,
264
- linkPreviewResolver: (content) => (0, link_preview_1.resolveLinkPreview)(content.text, content.linkPreview, {
265
- logger,
266
- mediaTransfer,
267
- getMediaConn: () => (0, messages_1.getMediaConn)(mediaMessageBuildOptions),
268
- fetcher: linkPreviewFetcher,
269
- options: linkPreviewOptions,
270
- serverClock
271
- })
274
+ linkPreviewResolver: createLinkPreviewResolver()
272
275
  };
273
276
  const messageClient = new WaMessageClient_1.WaMessageClient({
274
277
  logger,
@@ -371,12 +374,16 @@ function buildWaClientDependencies(input) {
371
374
  generateStanzaId: () => messageDispatch.generateOutgoingMessageId(),
372
375
  mediaTransfer,
373
376
  getMediaConn: () => (0, messages_1.getMediaConn)(mediaMessageBuildOptions),
377
+ linkPreviewResolver: createLinkPreviewResolver('newsletter'),
374
378
  getAbPropString: (name) => abPropsCoordinator.getConfigValue(name),
375
379
  logger
376
380
  });
377
381
  const privacyCoordinator = (0, WaPrivacyCoordinator_1.createPrivacyCoordinator)({
382
+ logger,
378
383
  queryWithContext: runtime.queryWithContext,
379
- resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid)
384
+ resolveUserJidPair: (userJid) => signalDeviceSync.resolveUserJidPair(userJid),
385
+ getSelfLid: () => getCurrentCredentials()?.meLid ?? null,
386
+ emitPrivacy: (event) => runtime.emitEvent('privacy', event)
380
387
  });
381
388
  const businessCoordinator = (0, WaBusinessCoordinator_1.createBusinessCoordinator)({
382
389
  queryWithContext: runtime.queryWithContext,
@@ -415,7 +422,8 @@ function buildWaClientDependencies(input) {
415
422
  }
416
423
  return {
417
424
  participants: participantJids,
418
- ephemeral: metadata.ephemeral
425
+ ephemeral: metadata.ephemeral,
426
+ ephemeralTrigger: metadata.ephemeralTrigger
419
427
  };
420
428
  },
421
429
  logger
@@ -470,6 +478,8 @@ function buildWaClientDependencies(input) {
470
478
  sessionStore: sessionStore.session,
471
479
  identityStore: sessionStore.identity,
472
480
  deviceListStore: sessionStore.deviceList,
481
+ threadStore: sessionStore.threads,
482
+ chatMetadataStore: sessionStore.chatMetadata,
473
483
  signalDeviceSync,
474
484
  messageSecretStore: sessionStore.messageSecret,
475
485
  persistAllMessageSecrets: options.addons?.persistAllSecrets === true,
@@ -512,7 +522,9 @@ function buildWaClientDependencies(input) {
512
522
  trustedContactToken,
513
523
  emitAddon: (event) => runtime.emitEvent('message_addon', event),
514
524
  mexSocket: { query: runtime.query },
515
- peerDataOperation
525
+ peerDataOperation,
526
+ isGroupHistorySendEnabled: () => abPropsCoordinator.getConfigValue('group_history_send'),
527
+ getAbPropNumber: (name) => abPropsCoordinator.getConfigValue(name)
516
528
  });
517
529
  const retryCoordinator = new WaRetryCoordinator_1.WaRetryCoordinator({
518
530
  logger,
@@ -537,7 +549,8 @@ function buildWaClientDependencies(input) {
537
549
  .handleIncomingMessageEvent(event)
538
550
  .catch((err) => runtime.handleError((0, primitives_1.toError)(err)));
539
551
  },
540
- isMobilePrimary
552
+ isMobilePrimary,
553
+ getAbPropNumber: (name) => abPropsCoordinator.getConfigValue(name)
541
554
  });
542
555
  const botCoordinator = (0, WaBotCoordinator_1.createBotCoordinator)({
543
556
  logger,
@@ -649,6 +662,7 @@ function buildWaClientDependencies(input) {
649
662
  const disconnectWithClientSideEffects = async (reason, isLogout, code) => {
650
663
  abPropsCoordinator.reset();
651
664
  offlineResume.reset();
665
+ privacyCoordinator.stopAccountSyncRefresh();
652
666
  await connectionManager?.disconnect();
653
667
  runtime.emitEvent('connection', {
654
668
  status: 'close',
@@ -695,6 +709,7 @@ function buildWaClientDependencies(input) {
695
709
  signalProtocol,
696
710
  senderKeyManager,
697
711
  onDecryptFailure: (context, error) => retryCoordinator.onDecryptFailure(context, error),
712
+ requestPlaceholderResend: (context) => retryCoordinator.onUnavailableMessage(context),
698
713
  emitIncomingMessage: (event) => {
699
714
  void runtime
700
715
  .handleIncomingMessageEvent(event)
@@ -710,6 +725,10 @@ function buildWaClientDependencies(input) {
710
725
  getCurrentCredentials,
711
726
  syncAppState: runtime.syncAppState,
712
727
  generateUsyncSid,
728
+ syncAccountPrivacy: async () => {
729
+ await privacyCoordinator.refreshFromAccountSync();
730
+ },
731
+ emitBlocklist: (blocklist) => runtime.emitEvent('blocklist', blocklist),
713
732
  newsletterListSubscribed: () => newsletterCoordinator.listSubscribed()
714
733
  }, dirtyBits);
715
734
  const offlineResume = new WaOfflineResumeCoordinator_1.WaOfflineResumeCoordinator({
@@ -891,6 +910,25 @@ function buildWaClientDependencies(input) {
891
910
  return false;
892
911
  }
893
912
  });
913
+ incomingNode.registerIncomingHandler({
914
+ tag: constants_1.WA_NODE_TAGS.NOTIFICATION,
915
+ subtype: constants_1.WA_NOTIFICATION_TYPES.ACCOUNT_SYNC,
916
+ prepend: true,
917
+ /**
918
+ * The stanza carries the changed `<category>` inline, but it is a
919
+ * trigger only: the payload is a partial view, so the whole set is
920
+ * refetched instead. Observer only - returning false keeps the
921
+ * generic notification handler responsible for the ack and the
922
+ * `debug_notification` emit, leaving wire behavior unchanged.
923
+ */
924
+ // eslint-disable-next-line @typescript-eslint/require-await
925
+ handler: async (node) => {
926
+ if ((0, helpers_1.findNodeChild)(node, constants_1.WA_NODE_TAGS.PRIVACY)) {
927
+ privacyCoordinator.scheduleAccountSyncRefresh();
928
+ }
929
+ return false;
930
+ }
931
+ });
894
932
  incomingNode.registerIncomingHandler({
895
933
  tag: constants_1.WA_NODE_TAGS.NOTIFICATION,
896
934
  subtype: constants_1.WA_NOTIFICATION_TYPES.DEVICES,
@@ -1,5 +1,5 @@
1
+ import { type WaAbPropName, type WaAbPropValue } from '../../abprops-spec';
1
2
  import type { Logger } from '../../infra/log/types';
2
- import { type AbPropName, type AbPropValue } from '../../protocol/abprops';
3
3
  import type { BinaryNode } from '../../transport/types';
4
4
  type WaAbPropsRuntime = {
5
5
  readonly queryWithContext: (context: string, node: BinaryNode, timeoutMs?: number, contextData?: Readonly<Record<string, unknown>>) => Promise<BinaryNode>;
@@ -16,7 +16,7 @@ export declare class WaAbPropsCoordinator {
16
16
  readonly logger: Logger;
17
17
  readonly runtime: WaAbPropsRuntime;
18
18
  });
19
- getConfigValue<T extends AbPropValue>(name: AbPropName): T;
19
+ getConfigValue<T extends WaAbPropValue>(name: WaAbPropName): T;
20
20
  sync(): void;
21
21
  reset(): void;
22
22
  private startSync;
@@ -1,11 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WaAbPropsCoordinator = void 0;
4
+ const _abprops_spec_1 = require("../../abprops-spec");
4
5
  const abprops_1 = require("../events/abprops");
5
6
  const abprops_2 = require("../../protocol/abprops");
6
7
  const constants_1 = require("../../protocol/constants");
7
8
  const abprops_3 = require("../../transport/node/builders/abprops");
8
9
  const primitives_1 = require("../../util/primitives");
10
+ // The generated table types every entry with literal `code` / `defaultValue`
11
+ // types. Reading through a widened view keeps a lookup by a non-literal name
12
+ // from expanding into a 2000-member union at every call site.
13
+ const AB_PROPS = _abprops_spec_1.WA_ABPROPS;
9
14
  class WaAbPropsCoordinator {
10
15
  constructor(options) {
11
16
  this.logger = options.logger;
@@ -21,7 +26,7 @@ class WaAbPropsCoordinator {
21
26
  if (cached !== undefined) {
22
27
  return cached;
23
28
  }
24
- return abprops_2.AB_PROP_CONFIGS[name].defaultValue;
29
+ return AB_PROPS[name].defaultValue;
25
30
  }
26
31
  sync() {
27
32
  if (this.syncPromise) {
@@ -104,11 +109,11 @@ class WaAbPropsCoordinator {
104
109
  let applied = 0;
105
110
  for (let i = 0; i < result.props.length; i += 1) {
106
111
  const entry = result.props[i];
107
- const name = (0, abprops_2.resolveAbPropNameByCode)(entry.configCode);
112
+ const name = (0, _abprops_spec_1.resolveAbPropNameByCode)(entry.configCode);
108
113
  if (!name) {
109
114
  continue;
110
115
  }
111
- const config = abprops_2.AB_PROP_CONFIGS[name];
116
+ const config = AB_PROPS[name];
112
117
  const parsed = parseConfigValue(entry.configValue, config.type, config.defaultValue);
113
118
  this.cache.set(name, parsed);
114
119
  applied += 1;
@@ -123,6 +128,8 @@ class WaAbPropsCoordinator {
123
128
  }
124
129
  }
125
130
  exports.WaAbPropsCoordinator = WaAbPropsCoordinator;
131
+ // parseInt, not a digits-only parse: negative values are in band, several props
132
+ // ship -1 as their default. Divergence: WA Web caches NaN, we keep the default.
126
133
  function parseConfigValue(value, type, defaultValue) {
127
134
  if (value === null) {
128
135
  return defaultValue;
@@ -131,7 +138,12 @@ function parseConfigValue(value, type, defaultValue) {
131
138
  return value === '1' || value === 'true' || value === 'True';
132
139
  }
133
140
  if (type === 'int') {
134
- return (0, primitives_1.parseOptionalInt)(value) ?? defaultValue;
141
+ const parsed = Number.parseInt(value, 10);
142
+ return Number.isNaN(parsed) ? defaultValue : parsed;
143
+ }
144
+ if (type === 'float') {
145
+ const parsed = Number.parseFloat(value);
146
+ return Number.isNaN(parsed) ? defaultValue : parsed;
135
147
  }
136
148
  return value;
137
149
  }
@@ -1,7 +1,7 @@
1
1
  import type { WaAuthCredentials, WaSuccessPersistAttributes } from '../../auth/types';
2
2
  import type { WaOfflineResumeCoordinator } from '../coordinators/WaOfflineResumeCoordinator';
3
3
  import type { WaDirtyBit } from '../events/dirty';
4
- import type { WaAccountTakeoverNoticeEvent, WaBusinessEvent, WaGroupEvent, WaIncomingCallEvent, WaIncomingChatstateEvent, WaIncomingErrorStanzaEvent, WaIncomingFailureEvent, WaIncomingNodeHandlerRegistration, WaIncomingNotificationEvent, WaIncomingPresenceEvent, WaIncomingReceiptEvent, WaIncomingStanzaFilter, WaIncomingUnhandledStanzaEvent, WaMexNotificationEvent, WaPictureEvent, WaRegistrationCodeEvent } from '../types';
4
+ import type { WaAccountTakeoverNoticeEvent, WaBusinessEvent, WaGroupEvent, WaIncomingCallEvent, WaIncomingChatstateEvent, WaIncomingErrorStanzaEvent, WaIncomingFailureEvent, WaIncomingNodeHandlerRegistration, WaIncomingNotificationEvent, WaIncomingPresenceEvent, WaIncomingReceiptEvent, WaIncomingStanzaFilter, WaIncomingUnhandledStanzaEvent, WaMexNotificationEvent, WaOfflineThreadMetadataEvent, WaPictureEvent, WaRegistrationCodeEvent } from '../types';
5
5
  import type { Logger } from '../../infra/log/types';
6
6
  import type { WaConnectionCode, WaDisconnectReason } from '../../protocol/stream';
7
7
  import { type WaStreamControlNodeResult } from '../../transport/stream/parse';
@@ -31,6 +31,7 @@ interface WaIncomingNodeRuntime {
31
31
  readonly emitIncomingFailure: (event: WaIncomingFailureEvent) => void;
32
32
  readonly emitIncomingErrorStanza: (event: WaIncomingErrorStanzaEvent) => void;
33
33
  readonly emitIncomingNotification: (event: WaIncomingNotificationEvent) => void;
34
+ readonly emitOfflineThreadMetadata: (event: WaOfflineThreadMetadataEvent) => void;
34
35
  readonly emitMexNotification: (event: WaMexNotificationEvent) => void;
35
36
  readonly emitRegistrationCode: (event: WaRegistrationCodeEvent) => void;
36
37
  readonly emitAccountTakeoverNotice: (event: WaAccountTakeoverNoticeEvent) => void;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WaIncomingNodeCoordinator = void 0;
4
4
  const chatstate_1 = require("../events/chatstate");
5
5
  const incoming_1 = require("../events/incoming");
6
+ const offline_1 = require("../events/offline");
6
7
  const presence_1 = require("../events/presence");
7
8
  const constants_1 = require("../../protocol/constants");
8
9
  const helpers_1 = require("../../transport/node/helpers");
@@ -381,6 +382,9 @@ class WaIncomingNodeCoordinator {
381
382
  if (child.tag === 'offline') {
382
383
  this.offlineResume.handleOfflineComplete((0, primitives_1.parseOptionalInt)(child.attrs.count) ?? 0);
383
384
  }
385
+ if (child.tag === 'thread_metadata') {
386
+ this.runtime.emitOfflineThreadMetadata((0, offline_1.parseOfflineThreadMetadata)(child));
387
+ }
384
388
  handled = true;
385
389
  }
386
390
  }
@@ -1,4 +1,5 @@
1
1
  import type { Readable } from 'node:stream';
2
+ import type { WaAbPropName } from '../../abprops-spec';
2
3
  import type { WaMessageDispatchCoordinator } from '../coordinators/WaMessageDispatchCoordinator';
3
4
  import type { WaTrustedContactTokenCoordinator } from '../coordinators/WaTrustedContactTokenCoordinator';
4
5
  import { type WaUploadMediaSource } from '../media';
@@ -25,6 +26,65 @@ export interface WaMessageCoordinatorDeps {
25
26
  readonly emitAddon: (event: WaIncomingAddonEvent) => void;
26
27
  readonly mexSocket: WaMexQuerySocket;
27
28
  readonly peerDataOperation: PeerDataOperationRequester;
29
+ /**
30
+ * Server-synced `group_history_send` AB-prop. WhatsApp gates the sender
31
+ * side per account, and rejects the stanza with SMAX_INVALID when it is
32
+ * off, so {@link WaMessageCoordinator.shareGroupHistory} checks it before
33
+ * spending an upload.
34
+ */
35
+ readonly isGroupHistorySendEnabled: () => boolean;
36
+ /**
37
+ * Reads a server-synced numeric AB-prop, falling back to its shipped
38
+ * default. Sole source for the group-history message-count limit, so a
39
+ * server-side change takes effect without a release.
40
+ */
41
+ readonly getAbPropNumber: (name: WaAbPropName) => number;
42
+ }
43
+ export interface WaShareGroupHistoryInput {
44
+ /**
45
+ * Members to share the history with. Must be current members of the group,
46
+ * written in the group's own addressing mode - a LID-addressed group only
47
+ * matches `@lid` entries, and a PN one only matches `@s.whatsapp.net`.
48
+ * Anything else throws, naming the mode the group uses. Read the mode off
49
+ * `client.group.queryGroupMetadata()`, whose participants carry both forms.
50
+ */
51
+ readonly toJids: readonly string[];
52
+ /**
53
+ * How many of the most recent messages to read from the mailbox store.
54
+ * Defaults to WhatsApp's `group_history_message_count_limit` (100).
55
+ * Ignored when {@link messages} is supplied - that list is bundled as-is.
56
+ */
57
+ readonly count?: number;
58
+ /**
59
+ * Only read messages at or after this timestamp (ms) from the mailbox
60
+ * store. Ignored when {@link messages} is supplied.
61
+ */
62
+ readonly sinceMs?: number;
63
+ /**
64
+ * Messages to bundle, bypassing the mailbox store entirely - {@link count}
65
+ * and {@link sinceMs} do not apply, so the caller owns the windowing.
66
+ * Required when the `messages` store domain is `'none'` (the default),
67
+ * since there is nothing to read back in that case.
68
+ */
69
+ readonly messages?: readonly Proto.IWebMessageInfo[];
70
+ /**
71
+ * Pinned messages older than the shared window. The receiver injects these
72
+ * regardless of the age cutoff it applies to `messages`.
73
+ */
74
+ readonly outOfWindowPinnedMessages?: readonly Proto.IWebMessageInfo[];
75
+ }
76
+ export interface WaShareGroupHistoryResult {
77
+ /** Stanza id of the bundle message, fanned out only to `historyReceivers`. */
78
+ readonly bundleMessageId: string;
79
+ /**
80
+ * Stanza id of the notice message, sent to the whole group. Absent when the
81
+ * notice failed to send: the bundle still reached its receivers, so do not
82
+ * retry the share - it would upload and deliver the history a second time.
83
+ */
84
+ readonly noticeMessageId?: string;
85
+ readonly messagesCount: number;
86
+ readonly historyReceivers: readonly string[];
87
+ readonly nonHistoryReceivers: readonly string[];
28
88
  }
29
89
  export interface WaRequestHistorySyncInput {
30
90
  /** Chat the older messages should be fetched from. */
@@ -111,6 +171,8 @@ export declare class WaMessageCoordinator {
111
171
  private readonly emitAddon;
112
172
  private readonly mexSocket;
113
173
  private readonly peerDataOperation;
174
+ private readonly isGroupHistorySendEnabled;
175
+ private readonly getAbPropNumber;
114
176
  constructor(deps: WaMessageCoordinatorDeps);
115
177
  /**
116
178
  * Asks the server to backfill older messages for `chatJid` beyond what
@@ -128,6 +190,40 @@ export declare class WaMessageCoordinator {
128
190
  requestHistorySync(input: WaRequestHistorySyncInput): Promise<{
129
191
  readonly messageId: string;
130
192
  }>;
193
+ /**
194
+ * Shares recent group history with members who joined after the fact - the
195
+ * counterpart of the `group_history_bundle` event on the receiving side.
196
+ *
197
+ * Sends two messages: the bundle itself, encrypted per device and fanned
198
+ * out **only** to `toJids` (never to the whole group), followed by a hidden
199
+ * notice addressed to everyone so other clients can render the "history was
200
+ * shared" marker.
201
+ *
202
+ * WhatsApp gates the sender side per account through the `group_history_send`
203
+ * AB-prop and rejects the stanza with SMAX_INVALID when it is off, so this
204
+ * throws up front rather than spending an upload. Admin-only groups
205
+ * (`memberShareGroupHistoryMode: 'admin_share'`) reject a share from a
206
+ * regular member server-side - read `client.group.queryGroupMetadata()`
207
+ * first if you want to check that too.
208
+ *
209
+ * @throws when the account is not allowed to share group history, when
210
+ * `groupJid` is not a group, when any of `toJids` is not a current member
211
+ * (or is this account), or when there is nothing to bundle.
212
+ * @example
213
+ * ```ts
214
+ * await client.message.shareGroupHistory('12036@g.us', {
215
+ * toJids: ['5511999999999@s.whatsapp.net'],
216
+ * count: 50
217
+ * })
218
+ * ```
219
+ */
220
+ shareGroupHistory(groupJid: string, input: WaShareGroupHistoryInput): Promise<WaShareGroupHistoryResult>;
221
+ /**
222
+ * Reads the most recent messages of a group back out of the mailbox store
223
+ * and rebuilds the `WebMessageInfo` shape a bundle carries. Yields nothing
224
+ * when the `messages` store domain is `'none'`.
225
+ */
226
+ private loadGroupHistoryMessages;
131
227
  /**
132
228
  * Fetches the server-side "reachout" timelock that throttles cold outreach
133
229
  * to non-contacts, returning the active window when enforcement is on.