zapo-js 1.6.2 → 1.7.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.
Files changed (218) hide show
  1. package/README.md +26 -12
  2. package/dist/abprops-spec.d.ts +10 -0
  3. package/dist/abprops-spec.js +25 -0
  4. package/dist/appstate/crypto/WaAppStateCrypto.js +4 -6
  5. package/dist/auth/WaAuthClient.d.ts +14 -1
  6. package/dist/auth/WaAuthClient.js +22 -1
  7. package/dist/auth/credentials-flow.d.ts +3 -1
  8. package/dist/auth/credentials-flow.js +3 -0
  9. package/dist/auth/pairing/WaPairingFlow.d.ts +5 -0
  10. package/dist/auth/pairing/WaPairingFlow.js +66 -14
  11. package/dist/auth/types.d.ts +13 -0
  12. package/dist/client/WaClient.d.ts +11 -0
  13. package/dist/client/WaClient.js +60 -0
  14. package/dist/client/WaClientFactory.d.ts +2 -2
  15. package/dist/client/WaClientFactory.js +53 -13
  16. package/dist/client/coordinators/WaAbPropsCoordinator.d.ts +2 -2
  17. package/dist/client/coordinators/WaAbPropsCoordinator.js +16 -4
  18. package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +2 -1
  19. package/dist/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  20. package/dist/client/coordinators/WaMessageCoordinator.d.ts +96 -0
  21. package/dist/client/coordinators/WaMessageCoordinator.js +163 -8
  22. package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +64 -0
  23. package/dist/client/coordinators/WaMessageDispatchCoordinator.js +252 -57
  24. package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +97 -19
  25. package/dist/client/coordinators/WaPrivacyCoordinator.js +152 -101
  26. package/dist/client/coordinators/WaRetryCoordinator.d.ts +22 -0
  27. package/dist/client/coordinators/WaRetryCoordinator.js +74 -11
  28. package/dist/client/events/dirty.d.ts +3 -0
  29. package/dist/client/events/dirty.js +12 -7
  30. package/dist/client/events/offline.d.ts +9 -0
  31. package/dist/client/events/offline.js +95 -0
  32. package/dist/client/events/privacy.d.ts +45 -0
  33. package/dist/client/events/privacy.js +130 -0
  34. package/dist/client/messaging/group-metadata.d.ts +12 -0
  35. package/dist/client/messaging/group-metadata.js +24 -8
  36. package/dist/client/messaging/link-preview.d.ts +7 -0
  37. package/dist/client/messaging/link-preview.js +27 -0
  38. package/dist/client/messaging/messages.js +16 -12
  39. package/dist/client/newsletter/content.d.ts +4 -1
  40. package/dist/client/newsletter/content.js +43 -18
  41. package/dist/client/newsletter/discovery.js +4 -2
  42. package/dist/client/newsletter/messaging.d.ts +2 -0
  43. package/dist/client/newsletter/messaging.js +2 -1
  44. package/dist/client/newsletter/mex.d.ts +2 -2
  45. package/dist/client/persistence/WriteBehindPersistence.js +2 -1
  46. package/dist/client/persistence/ephemeral-setting.d.ts +42 -0
  47. package/dist/client/persistence/ephemeral-setting.js +120 -0
  48. package/dist/client/persistence/group-history.d.ts +29 -0
  49. package/dist/client/persistence/group-history.js +149 -0
  50. package/dist/client/persistence/history-blob.d.ts +25 -0
  51. package/dist/client/persistence/history-blob.js +38 -0
  52. package/dist/client/persistence/history-sync.d.ts +20 -0
  53. package/dist/client/persistence/history-sync.js +165 -71
  54. package/dist/client/plugins/types.d.ts +10 -2
  55. package/dist/client/types.d.ts +150 -16
  56. package/dist/crypto/core/index.d.ts +1 -1
  57. package/dist/crypto/core/index.js +2 -1
  58. package/dist/crypto/core/primitives.d.ts +19 -0
  59. package/dist/crypto/core/primitives.js +48 -0
  60. package/dist/crypto/core/xeddsa.js +35 -4
  61. package/dist/crypto/curves/X25519.js +29 -2
  62. package/dist/crypto/nativeBackend.d.ts +20 -0
  63. package/dist/crypto/nativeBackend.js +95 -0
  64. package/dist/esm/abprops-spec.js +15 -0
  65. package/dist/esm/appstate/crypto/WaAppStateCrypto.js +4 -6
  66. package/dist/esm/auth/WaAuthClient.js +22 -1
  67. package/dist/esm/auth/credentials-flow.js +3 -0
  68. package/dist/esm/auth/pairing/WaPairingFlow.js +67 -15
  69. package/dist/esm/client/WaClient.js +60 -0
  70. package/dist/esm/client/WaClientFactory.js +54 -14
  71. package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +17 -5
  72. package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
  73. package/dist/esm/client/coordinators/WaMessageCoordinator.js +166 -11
  74. package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +254 -60
  75. package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +145 -94
  76. package/dist/esm/client/coordinators/WaRetryCoordinator.js +74 -11
  77. package/dist/esm/client/events/dirty.js +12 -7
  78. package/dist/esm/client/events/offline.js +92 -0
  79. package/dist/esm/client/events/privacy.js +123 -0
  80. package/dist/esm/client/messaging/group-metadata.js +25 -9
  81. package/dist/esm/client/messaging/link-preview.js +29 -2
  82. package/dist/esm/client/messaging/messages.js +16 -12
  83. package/dist/esm/client/newsletter/content.js +44 -19
  84. package/dist/esm/client/newsletter/discovery.js +4 -2
  85. package/dist/esm/client/newsletter/messaging.js +2 -1
  86. package/dist/esm/client/persistence/WriteBehindPersistence.js +2 -1
  87. package/dist/esm/client/persistence/ephemeral-setting.js +116 -0
  88. package/dist/esm/client/persistence/group-history.js +145 -0
  89. package/dist/esm/client/persistence/history-blob.js +34 -0
  90. package/dist/esm/client/persistence/history-sync.js +157 -65
  91. package/dist/esm/crypto/core/index.js +1 -1
  92. package/dist/esm/crypto/core/primitives.js +47 -0
  93. package/dist/esm/crypto/core/xeddsa.js +36 -5
  94. package/dist/esm/crypto/curves/X25519.js +29 -2
  95. package/dist/esm/crypto/nativeBackend.js +93 -0
  96. package/dist/esm/index.js +3 -1
  97. package/dist/esm/media/crypto/WaMediaCrypto.js +38 -19
  98. package/dist/esm/message/context-info.js +39 -0
  99. package/dist/esm/message/crypto/reporting-token.js +38 -216
  100. package/dist/esm/message/encode/content.js +73 -12
  101. package/dist/esm/message/encode/media-payload.js +3 -1
  102. package/dist/esm/message/kinds/group-history.js +36 -0
  103. package/dist/esm/message/primitives/incoming.js +32 -13
  104. package/dist/esm/protocol/abprops.js +18 -162
  105. package/dist/esm/protocol/constants.js +4 -3
  106. package/dist/esm/protocol/defaults.js +7 -0
  107. package/dist/esm/protocol/device-identity.js +10 -5
  108. package/dist/esm/protocol/jid.js +83 -36
  109. package/dist/esm/protocol/media.js +1 -0
  110. package/dist/esm/protocol/message.js +8 -0
  111. package/dist/esm/protocol/notification.js +1 -0
  112. package/dist/esm/protocol/privacy.js +70 -4
  113. package/dist/esm/signal/group/SenderKeyChain.js +1 -2
  114. package/dist/esm/signal/group/SenderKeyManager.js +1 -7
  115. package/dist/esm/signal/session/SignalProtocol.js +22 -21
  116. package/dist/esm/signal/session/SignalRatchet.js +20 -17
  117. package/dist/esm/signal/session/encoding.js +25 -3
  118. package/dist/esm/signal/session/resolver.js +11 -5
  119. package/dist/esm/store/contracts/chat-metadata.store.js +1 -0
  120. package/dist/esm/store/createStore.js +19 -2
  121. package/dist/esm/store/index.js +1 -0
  122. package/dist/esm/store/memory/chat-metadata.store.js +66 -0
  123. package/dist/esm/store/noop.store.js +8 -0
  124. package/dist/esm/transport/binary/constants.js +19 -0
  125. package/dist/esm/transport/binary/decoder.js +17 -19
  126. package/dist/esm/transport/binary/encoder.js +61 -18
  127. package/dist/esm/transport/node/builders/global.js +20 -0
  128. package/dist/esm/transport/node/builders/message.js +32 -15
  129. package/dist/esm/transport/node/builders/privacy.js +63 -4
  130. package/dist/esm/transport/noise/WaClientPayload.js +43 -22
  131. package/dist/esm/transport/noise/WaFrameCodec.js +14 -4
  132. package/dist/esm/util/coercion.js +12 -4
  133. package/dist/esm/util/index.js +1 -1
  134. package/dist/esm/util/protoscan.js +181 -0
  135. package/dist/esm/util/runtime.js +34 -0
  136. package/dist/index.d.ts +11 -6
  137. package/dist/index.js +15 -2
  138. package/dist/media/crypto/WaMediaCrypto.js +36 -17
  139. package/dist/media/types.d.ts +1 -1
  140. package/dist/message/context-info.d.ts +10 -0
  141. package/dist/message/context-info.js +40 -0
  142. package/dist/message/crypto/reporting-token.d.ts +3 -1
  143. package/dist/message/crypto/reporting-token.js +38 -216
  144. package/dist/message/encode/content.d.ts +24 -4
  145. package/dist/message/encode/content.js +74 -12
  146. package/dist/message/encode/media-payload.d.ts +5 -2
  147. package/dist/message/encode/media-payload.js +3 -1
  148. package/dist/message/kinds/group-history.d.ts +30 -0
  149. package/dist/message/kinds/group-history.js +40 -0
  150. package/dist/message/primitives/incoming.d.ts +5 -0
  151. package/dist/message/primitives/incoming.js +32 -13
  152. package/dist/message/types.d.ts +10 -0
  153. package/dist/protocol/abprops.d.ts +27 -140
  154. package/dist/protocol/abprops.js +18 -163
  155. package/dist/protocol/constants.d.ts +4 -3
  156. package/dist/protocol/constants.js +10 -3
  157. package/dist/protocol/defaults.d.ts +7 -0
  158. package/dist/protocol/defaults.js +7 -0
  159. package/dist/protocol/device-identity.d.ts +14 -4
  160. package/dist/protocol/device-identity.js +9 -4
  161. package/dist/protocol/jid.d.ts +4 -1
  162. package/dist/protocol/jid.js +83 -36
  163. package/dist/protocol/media.d.ts +1 -0
  164. package/dist/protocol/media.js +1 -0
  165. package/dist/protocol/message.d.ts +6 -0
  166. package/dist/protocol/message.js +9 -0
  167. package/dist/protocol/notification.d.ts +1 -0
  168. package/dist/protocol/notification.js +1 -0
  169. package/dist/protocol/privacy.d.ts +52 -0
  170. package/dist/protocol/privacy.js +71 -5
  171. package/dist/signal/group/SenderKeyChain.d.ts +1 -1
  172. package/dist/signal/group/SenderKeyChain.js +1 -2
  173. package/dist/signal/group/SenderKeyManager.js +1 -7
  174. package/dist/signal/session/SignalProtocol.d.ts +13 -0
  175. package/dist/signal/session/SignalProtocol.js +22 -21
  176. package/dist/signal/session/SignalRatchet.d.ts +4 -4
  177. package/dist/signal/session/SignalRatchet.js +19 -16
  178. package/dist/signal/session/SignalSession.d.ts +8 -1
  179. package/dist/signal/session/encoding.d.ts +5 -0
  180. package/dist/signal/session/encoding.js +25 -3
  181. package/dist/signal/session/resolver.js +11 -5
  182. package/dist/store/contracts/chat-metadata.store.d.ts +34 -0
  183. package/dist/store/contracts/chat-metadata.store.js +2 -0
  184. package/dist/store/contracts/group-metadata.store.d.ts +7 -0
  185. package/dist/store/contracts/thread.store.d.ts +6 -0
  186. package/dist/store/createStore.d.ts +10 -2
  187. package/dist/store/createStore.js +18 -1
  188. package/dist/store/index.d.ts +3 -1
  189. package/dist/store/index.js +3 -1
  190. package/dist/store/memory/chat-metadata.store.d.ts +26 -0
  191. package/dist/store/memory/chat-metadata.store.js +70 -0
  192. package/dist/store/noop.store.d.ts +2 -0
  193. package/dist/store/noop.store.js +9 -1
  194. package/dist/store/types.d.ts +190 -39
  195. package/dist/transport/binary/constants.d.ts +8 -0
  196. package/dist/transport/binary/constants.js +20 -1
  197. package/dist/transport/binary/decoder.js +17 -19
  198. package/dist/transport/binary/encoder.js +60 -17
  199. package/dist/transport/node/builders/global.d.ts +8 -0
  200. package/dist/transport/node/builders/global.js +21 -0
  201. package/dist/transport/node/builders/message.js +32 -15
  202. package/dist/transport/node/builders/privacy.d.ts +26 -2
  203. package/dist/transport/node/builders/privacy.js +63 -3
  204. package/dist/transport/noise/WaClientPayload.js +43 -22
  205. package/dist/transport/noise/WaFrameCodec.d.ts +1 -0
  206. package/dist/transport/noise/WaFrameCodec.js +14 -4
  207. package/dist/transport/noise/types.d.ts +17 -0
  208. package/dist/util/coercion.d.ts +6 -0
  209. package/dist/util/coercion.js +13 -4
  210. package/dist/util/index.d.ts +1 -1
  211. package/dist/util/index.js +2 -1
  212. package/dist/util/protoscan.d.ts +33 -0
  213. package/dist/util/protoscan.js +186 -0
  214. package/dist/util/runtime.d.ts +12 -0
  215. package/dist/util/runtime.js +35 -0
  216. package/package.json +3 -1
  217. package/spec/abprops/index.d.ts +4402 -0
  218. package/spec/abprops/index.js +4372 -0
@@ -25,6 +25,14 @@ class SignalProtocol {
25
25
  this.logger = logger;
26
26
  this.sessionMutationLock = new StoreLock_1.StoreLock();
27
27
  }
28
+ /**
29
+ * Loads the local registration identity once so batch callers can share
30
+ * it across many `prepareOutgoingSession` calls via
31
+ * `options.localIdentity` instead of re-reading the store per address.
32
+ */
33
+ async loadLocalIdentity() {
34
+ return (0, SignalSession_1.requireLocalIdentity)(this.stores.signal);
35
+ }
28
36
  /**
29
37
  * Builds an outgoing Signal session against a remote prekey bundle. Set
30
38
  * `options.reuseExisting` to skip the handshake when a session already
@@ -45,7 +53,7 @@ class SignalProtocol {
45
53
  }
46
54
  }
47
55
  const [local, localOneTimeBase] = await Promise.all([
48
- (0, SignalSession_1.requireLocalIdentity)(this.stores.signal),
56
+ options.localIdentity ?? (0, SignalSession_1.requireLocalIdentity)(this.stores.signal),
49
57
  (0, SignalSession_1.generateSerializedKeyPair)()
50
58
  ]);
51
59
  const session = await (0, SignalSession_1.initiateSessionOutgoing)(local, remoteBundle, localOneTimeBase);
@@ -78,7 +86,7 @@ class SignalProtocol {
78
86
  }
79
87
  }
80
88
  const [local, localOneTimeBase] = await Promise.all([
81
- (0, SignalSession_1.requireLocalIdentity)(this.stores.signal),
89
+ options.localIdentity ?? (0, SignalSession_1.requireLocalIdentity)(this.stores.signal),
82
90
  (0, SignalSession_1.generateSerializedKeyPair)()
83
91
  ]);
84
92
  const session = await (0, SignalSession_1.initiateSessionOutgoing)(local, remoteBundle, localOneTimeBase);
@@ -165,31 +173,24 @@ class SignalProtocol {
165
173
  prefetchedByAddress.set((0, jid_1.signalAddressKey)(entry.address), entry.session);
166
174
  }
167
175
  }
168
- const uniqueAddressKeys = new Array(requests.length);
169
- const uniqueAddresses = new Array(requests.length);
170
- let uniqueAddressCount = 0;
176
+ const addressKeys = new Array(requests.length);
177
+ const seenAddressKeys = new Set();
178
+ const uniqueAddressKeys = [];
179
+ const uniqueAddresses = [];
171
180
  for (let index = 0; index < requests.length; index += 1) {
172
181
  const address = requests[index].address;
173
182
  const addressKey = (0, jid_1.signalAddressKey)(address);
174
- let isDuplicate = false;
175
- for (let dedupIndex = 0; dedupIndex < uniqueAddressCount; dedupIndex += 1) {
176
- if (uniqueAddressKeys[dedupIndex] === addressKey) {
177
- isDuplicate = true;
178
- break;
179
- }
180
- }
181
- if (isDuplicate) {
183
+ addressKeys[index] = addressKey;
184
+ if (seenAddressKeys.has(addressKey)) {
182
185
  continue;
183
186
  }
184
- uniqueAddressKeys[uniqueAddressCount] = addressKey;
185
- uniqueAddresses[uniqueAddressCount] = address;
186
- uniqueAddressCount += 1;
187
+ seenAddressKeys.add(addressKey);
188
+ uniqueAddressKeys.push(addressKey);
189
+ uniqueAddresses.push(address);
187
190
  }
188
- uniqueAddressKeys.length = uniqueAddressCount;
189
- uniqueAddresses.length = uniqueAddressCount;
190
191
  const currentSessions = await this.stores.session.getSessionsBatch(uniqueAddresses);
191
192
  const latestSessionByAddress = new Map();
192
- for (let index = 0; index < uniqueAddressCount; index += 1) {
193
+ for (let index = 0; index < uniqueAddresses.length; index += 1) {
193
194
  const addressKey = uniqueAddressKeys[index];
194
195
  const current = currentSessions[index];
195
196
  if (current) {
@@ -207,7 +208,7 @@ class SignalProtocol {
207
208
  for (let index = 0; index < requests.length; index += 1) {
208
209
  const request = requests[index];
209
210
  const address = request.address;
210
- const addressKey = (0, jid_1.signalAddressKey)(address);
211
+ const addressKey = addressKeys[index];
211
212
  const session = latestSessionByAddress.get(addressKey);
212
213
  if (!session) {
213
214
  throw new Error('signal session not found');
@@ -216,7 +217,7 @@ class SignalProtocol {
216
217
  !(0, bytes_1.uint8Equal)((0, _crypto_1.toSerializedPubKey)(request.expectedIdentity), session.remote.pubKey)) {
217
218
  throw new Error('identity mismatch');
218
219
  }
219
- const [updatedSession, encrypted] = await (0, SignalRatchet_1.encryptMsg)(session, request.plaintext);
220
+ const [updatedSession, encrypted] = (0, SignalRatchet_1.encryptMsg)(session, request.plaintext);
220
221
  latestSessionByAddress.set(addressKey, updatedSession);
221
222
  sessionUpdatesByAddress.set(addressKey, {
222
223
  address,
@@ -12,13 +12,13 @@ export declare function deriveMsgKey(index: number, chainKey: Uint8Array): {
12
12
  readonly nextChainKey: Uint8Array;
13
13
  readonly messageKey: SignalMessageKey;
14
14
  };
15
- export declare function selectMessageKey(chain: SignalRecvChain, targetCounter: number): Promise<{
15
+ export declare function selectMessageKey(chain: SignalRecvChain, targetCounter: number): {
16
16
  readonly messageKey: SignalMessageKey;
17
17
  readonly updatedChain: SignalRecvChain;
18
- }>;
19
- export declare function encryptMsg(session: SignalSessionRecord, plaintext: Uint8Array): Promise<readonly [SignalSessionRecord, {
18
+ };
19
+ export declare function encryptMsg(session: SignalSessionRecord, plaintext: Uint8Array): readonly [SignalSessionRecord, {
20
20
  type: 'msg' | 'pkmsg';
21
21
  ciphertext: Uint8Array;
22
- }]>;
22
+ }];
23
23
  export declare function decryptMsg(session: SignalSessionRecord | null, parsed: ParsedSignalMessage, onPrevSessionDecryptError?: (error: Error, previousSessionIndex: number) => void): Promise<DecryptOutcome>;
24
24
  export declare function decryptMsgFromSession(session: SignalSessionRecord, message: ParsedSignalMessage | ParsedPreKeySignalMessage): Promise<readonly [SignalSessionRecord, Uint8Array]>;
@@ -27,8 +27,7 @@ function splitMsgKey(index, bytes) {
27
27
  function deriveMsgKey(index, chainKey) {
28
28
  return deriveMsgKeyFromChainKey(index, chainKey);
29
29
  }
30
- // eslint-disable-next-line @typescript-eslint/require-await
31
- async function selectMessageKey(chain, targetCounter) {
30
+ function selectMessageKey(chain, targetCounter) {
32
31
  const delta = targetCounter - chain.nextMsgIndex;
33
32
  if (delta > constants_1.FUTURE_MESSAGES_MAX) {
34
33
  throw new Error('message too far in future');
@@ -108,8 +107,7 @@ function deriveMsgKeyFromChainKey(index, chainKey) {
108
107
  messageKey: splitMsgKey(index, expanded)
109
108
  };
110
109
  }
111
- // eslint-disable-next-line @typescript-eslint/require-await
112
- async function encryptMsg(session, plaintext) {
110
+ function encryptMsg(session, plaintext) {
113
111
  const { nextChainKey, messageKey } = deriveMsgKey(session.sendChain.nextMsgIndex, session.sendChain.chainKey);
114
112
  const ciphertext = (0, _crypto_1.aesCbcEncrypt)(messageKey.cipherKey, messageKey.iv, plaintext);
115
113
  const signalPayload = _proto_1.proto.SignalMessage.encode({
@@ -118,13 +116,16 @@ async function encryptMsg(session, plaintext) {
118
116
  previousCounter: session.prevSendChainHighestIndex,
119
117
  ciphertext
120
118
  }).finish();
121
- const versionedSignalPayload = (0, _crypto_1.prependVersion)(signalPayload, constants_1.SIGNAL_VERSION);
119
+ const versionedLength = 1 + signalPayload.length;
120
+ const signalMessage = new Uint8Array(versionedLength + constants_1.SIGNAL_MAC_SIZE);
121
+ signalMessage[0] = ((constants_1.SIGNAL_VERSION << 4) | constants_1.SIGNAL_VERSION) & 0xff;
122
+ signalMessage.set(signalPayload, 1);
122
123
  const mac = (0, _crypto_1.hmacSha256Sign)(messageKey.macKey, [
123
124
  session.local.pubKey,
124
125
  session.remote.pubKey,
125
- versionedSignalPayload
126
+ signalMessage.subarray(0, versionedLength)
126
127
  ]);
127
- const signalMessage = (0, bytes_1.concatBytes)([versionedSignalPayload, mac.subarray(0, constants_1.SIGNAL_MAC_SIZE)]);
128
+ signalMessage.set(mac.subarray(0, constants_1.SIGNAL_MAC_SIZE), versionedLength);
128
129
  let type = 'msg';
129
130
  let output = signalMessage;
130
131
  if (session.initialExchangeInfo) {
@@ -197,10 +198,14 @@ async function decryptMsg(session, parsed, onPrevSessionDecryptError) {
197
198
  }
198
199
  async function decryptMsgFromSession(session, message) {
199
200
  const ratchetPubKey = (0, _crypto_1.toSerializedPubKey)(message.ratchetPubKey);
200
- const recvChainIndex = session.recvChains.findIndex((raw) => {
201
- const key = raw.senderRatchetKey;
202
- return key !== null && key !== undefined && (0, bytes_1.uint8Equal)(key, ratchetPubKey);
203
- });
201
+ let recvChainIndex = -1;
202
+ for (let index = 0; index < session.recvChains.length; index += 1) {
203
+ const key = session.recvChains[index].senderRatchetKey;
204
+ if (key !== null && key !== undefined && (0, bytes_1.uint8Equal)(key, ratchetPubKey)) {
205
+ recvChainIndex = index;
206
+ break;
207
+ }
208
+ }
204
209
  let selectedMessageKey;
205
210
  let updatedSession;
206
211
  if (recvChainIndex === -1) {
@@ -211,10 +216,8 @@ async function decryptMsgFromSession(session, message) {
211
216
  chainKey: recvRatchet.chainKey,
212
217
  unusedMsgKeys: []
213
218
  };
214
- const [selected, newSendRatchet] = await Promise.all([
215
- selectMessageKey(freshRecvChain, message.counter),
216
- (0, SignalSession_1.generateSerializedKeyPair)()
217
- ]);
219
+ const selected = selectMessageKey(freshRecvChain, message.counter);
220
+ const newSendRatchet = await (0, SignalSession_1.generateSerializedKeyPair)();
218
221
  selectedMessageKey = selected.messageKey;
219
222
  const sendRatchet = await (0, SignalSession_1.calculateRatchet)(recvRatchet.rootKey, newSendRatchet, ratchetPubKey);
220
223
  const nextRecvChains = session.recvChains.slice(-MAX_TRACKED_RECV_CHAINS);
@@ -234,7 +237,7 @@ async function decryptMsgFromSession(session, message) {
234
237
  }
235
238
  else {
236
239
  const decoded = (0, encoding_1.decodeSignalRecvChain)(session.recvChains[recvChainIndex], `recvChains[${recvChainIndex}]`);
237
- const selected = await selectMessageKey(decoded, message.counter);
240
+ const selected = selectMessageKey(decoded, message.counter);
238
241
  selectedMessageKey = selected.messageKey;
239
242
  const nextRecvChains = session.recvChains.slice();
240
243
  nextRecvChains[recvChainIndex] = (0, encoding_1.encodeSignalRecvChain)(selected.updatedChain);
@@ -1,6 +1,13 @@
1
1
  import type { SignalSerializedKeyPair, SignalSessionRecord, SignalSessionSnapshot } from '../types';
2
2
  import type { WaSignalStore } from '../../store/contracts/signal.store';
3
- interface LocalIdentityContext {
3
+ /**
4
+ * Local registration identity used to initiate outgoing Signal sessions.
5
+ *
6
+ * @sensitive `staticKeyPair.privKey` is the account's Signal identity
7
+ * private key. Do not `JSON.stringify`, log, or persist instances; the
8
+ * store layer is responsible for encryption at rest.
9
+ */
10
+ export interface LocalIdentityContext {
4
11
  readonly regId: number;
5
12
  readonly staticKeyPair: SignalSerializedKeyPair;
6
13
  }
@@ -17,6 +17,11 @@ export declare function decodeSignalSessionSnapshot(session: Proto.ISessionStruc
17
17
  /**
18
18
  * Serializes a {@link SignalSessionRecord} (current + previous sessions) into
19
19
  * the Signal `RecordStructure` protobuf encoding used by the session stores.
20
+ *
21
+ * The `previousSessions` segment only depends on the `prevSessions` array,
22
+ * which every mutation path replaces wholesale (never edits in place), so its
23
+ * encoded bytes are cached per array instance and only the current session is
24
+ * re-encoded on the per-message write path.
20
25
  */
21
26
  export declare function encodeSignalSessionRecord(record: SignalSessionRecord): Uint8Array;
22
27
  /**
@@ -152,15 +152,37 @@ function decodeSignalSessionSnapshot(session, field) {
152
152
  aliceBaseKey
153
153
  };
154
154
  }
155
+ const prevSessionsSuffixCache = new WeakMap();
155
156
  /**
156
157
  * Serializes a {@link SignalSessionRecord} (current + previous sessions) into
157
158
  * the Signal `RecordStructure` protobuf encoding used by the session stores.
159
+ *
160
+ * The `previousSessions` segment only depends on the `prevSessions` array,
161
+ * which every mutation path replaces wholesale (never edits in place), so its
162
+ * encoded bytes are cached per array instance and only the current session is
163
+ * re-encoded on the per-message write path.
158
164
  */
159
165
  function encodeSignalSessionRecord(record) {
160
- return _proto_1.proto.RecordStructure.encode({
161
- currentSession: encodeSignalSessionSnapshot(record),
162
- previousSessions: record.prevSessions
166
+ const prevSessions = record.prevSessions;
167
+ if (prevSessions.length === 0) {
168
+ return _proto_1.proto.RecordStructure.encode({
169
+ currentSession: encodeSignalSessionSnapshot(record)
170
+ }).finish();
171
+ }
172
+ let suffix = prevSessionsSuffixCache.get(prevSessions);
173
+ if (!suffix) {
174
+ suffix = _proto_1.proto.RecordStructure.encode({
175
+ previousSessions: prevSessions
176
+ }).finish();
177
+ prevSessionsSuffixCache.set(prevSessions, suffix);
178
+ }
179
+ const current = _proto_1.proto.RecordStructure.encode({
180
+ currentSession: encodeSignalSessionSnapshot(record)
163
181
  }).finish();
182
+ const out = new Uint8Array(current.length + suffix.length);
183
+ out.set(current, 0);
184
+ out.set(suffix, current.length);
185
+ return out;
164
186
  }
165
187
  /**
166
188
  * Decodes a Signal `RecordStructure` payload (as stored on disk) back into a
@@ -183,6 +183,7 @@ function createSignalSessionResolver(options) {
183
183
  const preparePromises = new Array(missingIndices.length);
184
184
  let prepareCount = 0;
185
185
  const missingBundleTargets = [];
186
+ let localIdentityPromise = null;
186
187
  for (let index = 0; index < missingIndices.length; index += 1) {
187
188
  const targetIndex = missingIndices[index];
188
189
  const targetJid = normalizedTargetJids[targetIndex];
@@ -212,11 +213,13 @@ function createSignalSessionResolver(options) {
212
213
  const targetAddress = normalizedTargetAddresses[targetIndex];
213
214
  const bundle = batchResult.bundle;
214
215
  prepareTargetIndices[prepareCount] = targetIndex;
215
- preparePromises[prepareCount] = signalProtocol
216
- .prepareOutgoingSession(targetAddress, bundle, {
216
+ localIdentityPromise ?? (localIdentityPromise = signalProtocol.loadLocalIdentity());
217
+ preparePromises[prepareCount] = localIdentityPromise
218
+ .then((localIdentity) => signalProtocol.prepareOutgoingSession(targetAddress, bundle, {
217
219
  reuseExisting: true,
218
- knownAbsent: true
219
- })
220
+ knownAbsent: true,
221
+ localIdentity
222
+ }))
220
223
  .then((prep) => ({
221
224
  session: prep.session,
222
225
  remoteIdentity: prep.remoteIdentity
@@ -239,6 +242,7 @@ function createSignalSessionResolver(options) {
239
242
  const batchEntries = [];
240
243
  const entryToTargetIndex = [];
241
244
  const fallbackIndices = [];
245
+ const fallbackIndexSet = new Set();
242
246
  for (let i = 0; i < prepareTargetIndices.length; i += 1) {
243
247
  const targetIndex = prepareTargetIndices[i];
244
248
  const result = prepareResults[i];
@@ -274,6 +278,7 @@ function createSignalSessionResolver(options) {
274
278
  message: normalized.message
275
279
  });
276
280
  for (let i = 0; i < entryToTargetIndex.length; i += 1) {
281
+ fallbackIndexSet.add(entryToTargetIndex[i]);
277
282
  fallbackIndices.push(entryToTargetIndex[i]);
278
283
  }
279
284
  persistResult = { resolved: [], skipped: [] };
@@ -302,7 +307,8 @@ function createSignalSessionResolver(options) {
302
307
  if (skippedByAddressKey.has(addrKey)) {
303
308
  continue;
304
309
  }
305
- if (!fallbackIndices.includes(targetIndex)) {
310
+ if (!fallbackIndexSet.has(targetIndex)) {
311
+ fallbackIndexSet.add(targetIndex);
306
312
  fallbackIndices.push(targetIndex);
307
313
  }
308
314
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Protocol-derived per-chat state, cached so an outgoing send never depends on
3
+ * a user-configured archive being reachable.
4
+ *
5
+ * Distinct from the `threads` mailbox domain, which is the user-facing chat
6
+ * archive (name, unread, archived, pinned, mute). This domain holds state the
7
+ * runtime derives from the protocol and can always rebuild: history sync, an
8
+ * `EPHEMERAL_SETTING` protocol message, or an incoming `ContextInfo` all
9
+ * repopulate it. Losing it costs freshness, never data.
10
+ *
11
+ * The `threads` record persists the same disappearing-message fields as the
12
+ * durable copy; this domain is the hot path's source and falls back to it on a
13
+ * cold miss - the same relationship `messageSecret` has with the archived
14
+ * message bytes.
15
+ */
16
+ export interface WaChatMetadataSnapshot {
17
+ readonly chatJid: string;
18
+ /** Disappearing-message lifetime in seconds. `0` when the chat is not ephemeral. */
19
+ readonly ephemeralExpiration?: number;
20
+ /**
21
+ * Unix seconds when disappearing mode was enabled. History sync delivers
22
+ * milliseconds and `ContextInfo` seconds; ingest normalizes before writing.
23
+ */
24
+ readonly ephemeralSettingTimestamp?: number;
25
+ readonly updatedAtMs: number;
26
+ }
27
+ export interface WaChatMetadataStore {
28
+ destroy?(): Promise<void>;
29
+ upsertChatMetadata(snapshot: WaChatMetadataSnapshot): Promise<void>;
30
+ getChatMetadata(chatJid: string, nowMs?: number): Promise<WaChatMetadataSnapshot | null>;
31
+ deleteChatMetadata(chatJid: string): Promise<number>;
32
+ cleanupExpired(nowMs: number): Promise<number>;
33
+ clear(): Promise<void>;
34
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -2,6 +2,13 @@ export interface WaGroupMetadataSnapshot {
2
2
  readonly groupJid: string;
3
3
  readonly participants: readonly string[];
4
4
  readonly ephemeral?: number;
5
+ /**
6
+ * `<ephemeral trigger>` from the group metadata, mirrored into
7
+ * `contextInfo.disappearingMode.trigger` on outgoing group messages. Absent
8
+ * when the group reports no trigger - the field is then omitted on the wire
9
+ * rather than defaulted.
10
+ */
11
+ readonly ephemeralTrigger?: number;
5
12
  readonly updatedAtMs: number;
6
13
  }
7
14
  export interface WaGroupMetadataStore {
@@ -7,6 +7,12 @@ export interface WaStoredThreadRecord {
7
7
  readonly muteEndMs?: number;
8
8
  readonly markedAsUnread?: boolean;
9
9
  readonly ephemeralExpiration?: number;
10
+ /**
11
+ * Unix seconds when disappearing mode was enabled, normalized on ingest to
12
+ * the unit `ContextInfo` uses on the wire. Set only for ephemeral 1:1 chats;
13
+ * groups do not carry it on outgoing messages.
14
+ */
15
+ readonly ephemeralSettingTimestamp?: number;
10
16
  }
11
17
  export interface WaThreadStore {
12
18
  upsert(record: WaStoredThreadRecord): Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { WaCreateStoreOptions, WaCreateStoreOptionsStrict, WaStore } from './types';
1
+ import type { WaCreateStoreOptions, WaCreateStoreOptionsStrictFor, WaStore, WaStoreBackendMap } from './types';
2
2
  /**
3
3
  * Builds a {@link WaStore} from the configured providers/backends. Each call
4
4
  * to `store.session(sessionId)` returns a cached, lock-wrapped per-domain
@@ -18,6 +18,14 @@ import type { WaCreateStoreOptions, WaCreateStoreOptionsStrict, WaStore } from '
18
18
  * messageSecret) stay opt-in and default to memory - they are small,
19
19
  * hot, and cheap to rebuild.
20
20
  *
21
+ * **Domain coverage:** the backend map is inferred, so each domain only
22
+ * accepts the backends whose bundle actually declares it. A backend that
23
+ * covers part of the matrix (`WaStoreBackend<'auth', never>`, say) can only
24
+ * be named on the domains it implements - the rest fall back to `'memory'`
25
+ * / `'none'`, checked by the compiler rather than by the
26
+ * `backend '<name>' does not provide <kind>.<domain>` throw on first
27
+ * `session()`.
28
+ *
21
29
  * @example
22
30
  * ```ts
23
31
  * // Persistent setup with @zapo-js/store-sqlite (recommended for production)
@@ -51,4 +59,4 @@ export declare function createStore(options?: Omit<WaCreateStoreOptions<never>,
51
59
  readonly backends?: undefined;
52
60
  readonly providers?: undefined;
53
61
  }): WaStore;
54
- export declare function createStore<B extends string>(options: WaCreateStoreOptionsStrict<B>): WaStore;
62
+ export declare function createStore<TBackends extends WaStoreBackendMap>(options: WaCreateStoreOptionsStrictFor<TBackends>): WaStore;
@@ -22,6 +22,7 @@ const signal_lock_1 = require("./locks/signal.lock");
22
22
  const thread_lock_1 = require("./locks/thread.lock");
23
23
  const appstate_store_1 = require("./memory/appstate.store");
24
24
  const auth_store_1 = require("./memory/auth.store");
25
+ const chat_metadata_store_1 = require("./memory/chat-metadata.store");
25
26
  const contact_store_1 = require("./memory/contact.store");
26
27
  const device_list_store_1 = require("./memory/device-list.store");
27
28
  const group_metadata_store_1 = require("./memory/group-metadata.store");
@@ -41,6 +42,7 @@ const primitives_1 = require("../util/primitives");
41
42
  const DEFAULT_CACHE_TTLS_MS = Object.freeze({
42
43
  retryMs: 60 * 1000,
43
44
  groupMetadataMs: 5 * 60 * 1000,
45
+ chatMetadataMs: 30 * 60 * 1000,
44
46
  deviceListMs: 5 * 60 * 1000,
45
47
  messageSecretMs: 30 * 60 * 1000
46
48
  });
@@ -87,7 +89,7 @@ function resolveStore(sessionId, backends, provider, domain, kind, fallback) {
87
89
  }
88
90
  function createStore(options) {
89
91
  options = options ?? {};
90
- const backends = (options.backends ?? {});
92
+ const backends = options.backends ?? {};
91
93
  const providers = options.providers ?? {};
92
94
  const cacheProviders = options.cacheProviders ?? {};
93
95
  const cacheLayer = options.cacheLayer ?? {};
@@ -109,6 +111,7 @@ function createStore(options) {
109
111
  const cacheTtlsMs = Object.freeze({
110
112
  retry: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.retryMs, DEFAULT_CACHE_TTLS_MS.retryMs, 'memory.cacheTtlMs.retryMs'),
111
113
  groupMetadata: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.groupMetadataMs, DEFAULT_CACHE_TTLS_MS.groupMetadataMs, 'memory.cacheTtlMs.groupMetadataMs'),
114
+ chatMetadata: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.chatMetadataMs, DEFAULT_CACHE_TTLS_MS.chatMetadataMs, 'memory.cacheTtlMs.chatMetadataMs'),
112
115
  deviceList: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.deviceListMs, DEFAULT_CACHE_TTLS_MS.deviceListMs, 'memory.cacheTtlMs.deviceListMs'),
113
116
  messageSecret: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
114
117
  });
@@ -174,6 +177,15 @@ function createStore(options) {
174
177
  })
175
178
  })
176
179
  : noop_store_1.NOOP_GROUP_METADATA_STORE)),
180
+ chatMetadata: resolveStore(id, backends, cacheProviders.chatMetadata ?? 'memory', 'chatMetadata', 'caches', () => cacheProviders.chatMetadata === 'memory' || !cacheProviders.chatMetadata
181
+ ? new chat_metadata_store_1.WaChatMetadataMemoryStore(cacheTtlsMs.chatMetadata, {
182
+ maxChats: ml.chatMetadataChats,
183
+ logger: memoryLogger?.child({
184
+ domain: 'chatMetadata',
185
+ sessionId: id
186
+ })
187
+ })
188
+ : noop_store_1.NOOP_CHAT_METADATA_STORE),
177
189
  deviceList: (0, device_list_lock_1.withDeviceListLock)(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
178
190
  ? new device_list_store_1.WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
179
191
  maxUsers: ml.deviceListUsers,
@@ -221,12 +233,14 @@ function createStore(options) {
221
233
  const cleared = await Promise.allSettled([
222
234
  target.retry.clear(),
223
235
  target.groupMetadata.clear(),
236
+ target.chatMetadata.clear(),
224
237
  target.deviceList.clear(),
225
238
  target.messageSecret.clear()
226
239
  ]);
227
240
  const destroyed = await Promise.allSettled([
228
241
  destroyIfSupported(target.retry),
229
242
  destroyIfSupported(target.groupMetadata),
243
+ destroyIfSupported(target.chatMetadata),
230
244
  destroyIfSupported(target.deviceList),
231
245
  destroyIfSupported(target.messageSecret)
232
246
  ]);
@@ -310,6 +324,9 @@ function createStore(options) {
310
324
  get groupMetadata() {
311
325
  return caches.groupMetadata;
312
326
  },
327
+ get chatMetadata() {
328
+ return caches.chatMetadata;
329
+ },
313
330
  get deviceList() {
314
331
  return caches.deviceList;
315
332
  },
@@ -1,4 +1,4 @@
1
- export type { WaCreateStoreOptions, WaCreateStoreOptionsStrict, WaStore, WaStoreBackend, WaCacheDomain, WaStoreDomain, WaStoreMemoryLimitSelection, WaStoreSession } from './types';
1
+ export type { WaAnyStoreBackend, WaBackendsProviding, WaCreateStoreOptions, WaCreateStoreOptionsStrict, WaCreateStoreOptionsStrictFor, WaStore, WaStoreBackend, WaStoreBackendMap, WaCacheDomain, WaCacheDomainContracts, WaStoreDomain, WaStoreDomainContracts, WaStoreMemoryLimitSelection, WaStoreSession } from './types';
2
2
  export { createStore } from './createStore';
3
3
  export type { WaAuthStore } from './contracts/auth.store';
4
4
  export type { WaContactStore, WaStoredContactRecord } from './contracts/contact.store';
@@ -6,6 +6,7 @@ export type { WaDeviceListSnapshot, WaDeviceListStore } from './contracts/device
6
6
  export type { WaMessageSecretEntry, WaMessageSecretStore } from './contracts/message-secret.store';
7
7
  export type { WaMessageStore, WaStoredMessageRecord } from './contracts/message.store';
8
8
  export type { WaGroupMetadataSnapshot, WaGroupMetadataStore } from './contracts/group-metadata.store';
9
+ export type { WaChatMetadataSnapshot, WaChatMetadataStore } from './contracts/chat-metadata.store';
9
10
  export type { WaAppStateCollectionStateUpdate, WaAppStateCollectionStoreState, WaAppStateStore } from './contracts/appstate.store';
10
11
  export type { WaIdentityStore } from './contracts/identity.store';
11
12
  export type { WaPreKeyStore } from './contracts/pre-key.store';
@@ -24,6 +25,7 @@ export { WaIdentityMemoryStore } from './memory/identity.store';
24
25
  export { SenderKeyMemoryStore } from './memory/sender-key.store';
25
26
  export { WaRetryMemoryStore } from './memory/retry.store';
26
27
  export { WaGroupMetadataMemoryStore } from './memory/group-metadata.store';
28
+ export { WaChatMetadataMemoryStore } from './memory/chat-metadata.store';
27
29
  export { WaDeviceListMemoryStore } from './memory/device-list.store';
28
30
  export { WaContactMemoryStore } from './memory/contact.store';
29
31
  export { WaMessageSecretMemoryStore } from './memory/message-secret.store';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WaPrivacyTokenMemoryStore = exports.WaThreadMemoryStore = exports.WaMessageMemoryStore = exports.WaMessageSecretMemoryStore = exports.WaContactMemoryStore = exports.WaDeviceListMemoryStore = exports.WaGroupMetadataMemoryStore = exports.WaRetryMemoryStore = exports.SenderKeyMemoryStore = exports.WaIdentityMemoryStore = exports.WaSessionMemoryStore = exports.WaPreKeyMemoryStore = exports.WaSignalMemoryStore = exports.WaAuthMemoryStore = exports.WaAppStateMemoryStore = exports.createStore = void 0;
3
+ exports.WaPrivacyTokenMemoryStore = exports.WaThreadMemoryStore = exports.WaMessageMemoryStore = exports.WaMessageSecretMemoryStore = exports.WaContactMemoryStore = exports.WaDeviceListMemoryStore = exports.WaChatMetadataMemoryStore = exports.WaGroupMetadataMemoryStore = exports.WaRetryMemoryStore = exports.SenderKeyMemoryStore = exports.WaIdentityMemoryStore = exports.WaSessionMemoryStore = exports.WaPreKeyMemoryStore = exports.WaSignalMemoryStore = exports.WaAuthMemoryStore = exports.WaAppStateMemoryStore = exports.createStore = void 0;
4
4
  var createStore_1 = require("./createStore");
5
5
  Object.defineProperty(exports, "createStore", { enumerable: true, get: function () { return createStore_1.createStore; } });
6
6
  var appstate_store_1 = require("./memory/appstate.store");
@@ -21,6 +21,8 @@ var retry_store_1 = require("./memory/retry.store");
21
21
  Object.defineProperty(exports, "WaRetryMemoryStore", { enumerable: true, get: function () { return retry_store_1.WaRetryMemoryStore; } });
22
22
  var group_metadata_store_1 = require("./memory/group-metadata.store");
23
23
  Object.defineProperty(exports, "WaGroupMetadataMemoryStore", { enumerable: true, get: function () { return group_metadata_store_1.WaGroupMetadataMemoryStore; } });
24
+ var chat_metadata_store_1 = require("./memory/chat-metadata.store");
25
+ Object.defineProperty(exports, "WaChatMetadataMemoryStore", { enumerable: true, get: function () { return chat_metadata_store_1.WaChatMetadataMemoryStore; } });
24
26
  var device_list_store_1 = require("./memory/device-list.store");
25
27
  Object.defineProperty(exports, "WaDeviceListMemoryStore", { enumerable: true, get: function () { return device_list_store_1.WaDeviceListMemoryStore; } });
26
28
  var contact_store_1 = require("./memory/contact.store");
@@ -0,0 +1,26 @@
1
+ import type { Logger } from '../../infra/log/types';
2
+ import type { WaChatMetadataSnapshot, WaChatMetadataStore } from '../contracts/chat-metadata.store';
3
+ export interface WaChatMetadataMemoryStoreOptions {
4
+ readonly maxChats?: number;
5
+ /**
6
+ * Logger for capacity-saturation warnings. Emits a single `warn` the first
7
+ * time the bounded map evicts an entry; later evictions are silent.
8
+ */
9
+ readonly logger?: Logger;
10
+ }
11
+ export declare class WaChatMetadataMemoryStore implements WaChatMetadataStore {
12
+ private readonly records;
13
+ private readonly ttlMs;
14
+ private readonly maxChats;
15
+ private readonly cleanup;
16
+ private readonly logger;
17
+ private capacityWarned;
18
+ constructor(ttlMs?: number, options?: WaChatMetadataMemoryStoreOptions);
19
+ private warnCapacity;
20
+ upsertChatMetadata(snapshot: WaChatMetadataSnapshot): Promise<void>;
21
+ getChatMetadata(chatJid: string, nowMs?: number): Promise<WaChatMetadataSnapshot | null>;
22
+ deleteChatMetadata(chatJid: string): Promise<number>;
23
+ cleanupExpired(nowMs: number): Promise<number>;
24
+ clear(): Promise<void>;
25
+ destroy(): Promise<void>;
26
+ }
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WaChatMetadataMemoryStore = void 0;
4
+ const coercion_1 = require("../../util/coercion");
5
+ const collections_1 = require("../../util/collections");
6
+ const DEFAULTS = Object.freeze({
7
+ ttlMs: 30 * 60 * 1000,
8
+ maxChats: 2048
9
+ });
10
+ class WaChatMetadataMemoryStore {
11
+ constructor(ttlMs = DEFAULTS.ttlMs, options = {}) {
12
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
13
+ throw new Error('chatMetadata ttlMs must be a positive finite number');
14
+ }
15
+ this.records = new Map();
16
+ this.ttlMs = ttlMs;
17
+ this.maxChats = (0, coercion_1.resolvePositive)(options.maxChats, DEFAULTS.maxChats, 'WaChatMetadataMemoryStoreOptions.maxChats');
18
+ this.logger = options.logger;
19
+ this.capacityWarned = false;
20
+ this.cleanup = (0, collections_1.createPeriodicCleanup)(ttlMs, () => {
21
+ void this.cleanupExpired(Date.now());
22
+ });
23
+ }
24
+ warnCapacity() {
25
+ if (this.capacityWarned || !this.logger)
26
+ return;
27
+ this.capacityWarned = true;
28
+ this.logger.warn('chat metadata store at capacity, evicting oldest', {
29
+ max: this.maxChats
30
+ });
31
+ }
32
+ async upsertChatMetadata(snapshot) {
33
+ (0, collections_1.setBoundedMapEntry)(this.records, snapshot.chatJid, {
34
+ ...snapshot,
35
+ expiresAtMs: snapshot.updatedAtMs + this.ttlMs
36
+ }, this.maxChats, () => this.warnCapacity());
37
+ }
38
+ async getChatMetadata(chatJid, nowMs = Date.now()) {
39
+ const record = this.records.get(chatJid);
40
+ if (!record) {
41
+ return null;
42
+ }
43
+ if (record.expiresAtMs <= nowMs) {
44
+ this.records.delete(chatJid);
45
+ return null;
46
+ }
47
+ return record;
48
+ }
49
+ async deleteChatMetadata(chatJid) {
50
+ return this.records.delete(chatJid) ? 1 : 0;
51
+ }
52
+ async cleanupExpired(nowMs) {
53
+ let removed = 0;
54
+ for (const [chatJid, record] of this.records) {
55
+ if (record.expiresAtMs > nowMs)
56
+ continue;
57
+ this.records.delete(chatJid);
58
+ removed += 1;
59
+ }
60
+ return removed;
61
+ }
62
+ async clear() {
63
+ this.records.clear();
64
+ }
65
+ async destroy() {
66
+ this.cleanup.destroy();
67
+ await this.clear();
68
+ }
69
+ }
70
+ exports.WaChatMetadataMemoryStore = WaChatMetadataMemoryStore;
@@ -1,3 +1,4 @@
1
+ import type { WaChatMetadataStore } from './contracts/chat-metadata.store';
1
2
  import type { WaContactStore } from './contracts/contact.store';
2
3
  import type { WaDeviceListStore } from './contracts/device-list.store';
3
4
  import type { WaGroupMetadataStore } from './contracts/group-metadata.store';
@@ -10,5 +11,6 @@ export declare const NOOP_MESSAGE_STORE: WaMessageStore;
10
11
  export declare const NOOP_THREAD_STORE: WaThreadStore;
11
12
  export declare const NOOP_CONTACT_STORE: WaContactStore;
12
13
  export declare const NOOP_RETRY_STORE: WaRetryStore;
14
+ export declare const NOOP_CHAT_METADATA_STORE: WaChatMetadataStore;
13
15
  export declare const NOOP_GROUP_METADATA_STORE: WaGroupMetadataStore;
14
16
  export declare const NOOP_DEVICE_LIST_STORE: WaDeviceListStore;