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.
- package/README.md +26 -12
- package/dist/abprops-spec.d.ts +10 -0
- package/dist/abprops-spec.js +25 -0
- package/dist/appstate/crypto/WaAppStateCrypto.js +4 -6
- package/dist/auth/WaAuthClient.d.ts +14 -1
- package/dist/auth/WaAuthClient.js +22 -1
- package/dist/auth/credentials-flow.d.ts +3 -1
- package/dist/auth/credentials-flow.js +3 -0
- package/dist/auth/pairing/WaPairingFlow.d.ts +5 -0
- package/dist/auth/pairing/WaPairingFlow.js +66 -14
- package/dist/auth/types.d.ts +13 -0
- package/dist/client/WaClient.d.ts +11 -0
- package/dist/client/WaClient.js +60 -0
- package/dist/client/WaClientFactory.d.ts +2 -2
- package/dist/client/WaClientFactory.js +53 -13
- package/dist/client/coordinators/WaAbPropsCoordinator.d.ts +2 -2
- package/dist/client/coordinators/WaAbPropsCoordinator.js +16 -4
- package/dist/client/coordinators/WaIncomingNodeCoordinator.d.ts +2 -1
- package/dist/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
- package/dist/client/coordinators/WaMessageCoordinator.d.ts +96 -0
- package/dist/client/coordinators/WaMessageCoordinator.js +163 -8
- package/dist/client/coordinators/WaMessageDispatchCoordinator.d.ts +64 -0
- package/dist/client/coordinators/WaMessageDispatchCoordinator.js +252 -57
- package/dist/client/coordinators/WaPrivacyCoordinator.d.ts +97 -19
- package/dist/client/coordinators/WaPrivacyCoordinator.js +152 -101
- package/dist/client/coordinators/WaRetryCoordinator.d.ts +22 -0
- package/dist/client/coordinators/WaRetryCoordinator.js +74 -11
- package/dist/client/events/dirty.d.ts +3 -0
- package/dist/client/events/dirty.js +12 -7
- package/dist/client/events/offline.d.ts +9 -0
- package/dist/client/events/offline.js +95 -0
- package/dist/client/events/privacy.d.ts +45 -0
- package/dist/client/events/privacy.js +130 -0
- package/dist/client/messaging/group-metadata.d.ts +12 -0
- package/dist/client/messaging/group-metadata.js +24 -8
- package/dist/client/messaging/link-preview.d.ts +7 -0
- package/dist/client/messaging/link-preview.js +27 -0
- package/dist/client/messaging/messages.js +16 -12
- package/dist/client/newsletter/content.d.ts +4 -1
- package/dist/client/newsletter/content.js +43 -18
- package/dist/client/newsletter/discovery.js +4 -2
- package/dist/client/newsletter/messaging.d.ts +2 -0
- package/dist/client/newsletter/messaging.js +2 -1
- package/dist/client/newsletter/mex.d.ts +2 -2
- package/dist/client/persistence/WriteBehindPersistence.js +2 -1
- package/dist/client/persistence/ephemeral-setting.d.ts +42 -0
- package/dist/client/persistence/ephemeral-setting.js +120 -0
- package/dist/client/persistence/group-history.d.ts +29 -0
- package/dist/client/persistence/group-history.js +149 -0
- package/dist/client/persistence/history-blob.d.ts +25 -0
- package/dist/client/persistence/history-blob.js +38 -0
- package/dist/client/persistence/history-sync.d.ts +20 -0
- package/dist/client/persistence/history-sync.js +165 -71
- package/dist/client/plugins/types.d.ts +10 -2
- package/dist/client/types.d.ts +150 -16
- package/dist/crypto/core/index.d.ts +1 -1
- package/dist/crypto/core/index.js +2 -1
- package/dist/crypto/core/primitives.d.ts +19 -0
- package/dist/crypto/core/primitives.js +48 -0
- package/dist/crypto/core/xeddsa.js +35 -4
- package/dist/crypto/curves/X25519.js +29 -2
- package/dist/crypto/nativeBackend.d.ts +20 -0
- package/dist/crypto/nativeBackend.js +95 -0
- package/dist/esm/abprops-spec.js +15 -0
- package/dist/esm/appstate/crypto/WaAppStateCrypto.js +4 -6
- package/dist/esm/auth/WaAuthClient.js +22 -1
- package/dist/esm/auth/credentials-flow.js +3 -0
- package/dist/esm/auth/pairing/WaPairingFlow.js +67 -15
- package/dist/esm/client/WaClient.js +60 -0
- package/dist/esm/client/WaClientFactory.js +54 -14
- package/dist/esm/client/coordinators/WaAbPropsCoordinator.js +17 -5
- package/dist/esm/client/coordinators/WaIncomingNodeCoordinator.js +4 -0
- package/dist/esm/client/coordinators/WaMessageCoordinator.js +166 -11
- package/dist/esm/client/coordinators/WaMessageDispatchCoordinator.js +254 -60
- package/dist/esm/client/coordinators/WaPrivacyCoordinator.js +145 -94
- package/dist/esm/client/coordinators/WaRetryCoordinator.js +74 -11
- package/dist/esm/client/events/dirty.js +12 -7
- package/dist/esm/client/events/offline.js +92 -0
- package/dist/esm/client/events/privacy.js +123 -0
- package/dist/esm/client/messaging/group-metadata.js +25 -9
- package/dist/esm/client/messaging/link-preview.js +29 -2
- package/dist/esm/client/messaging/messages.js +16 -12
- package/dist/esm/client/newsletter/content.js +44 -19
- package/dist/esm/client/newsletter/discovery.js +4 -2
- package/dist/esm/client/newsletter/messaging.js +2 -1
- package/dist/esm/client/persistence/WriteBehindPersistence.js +2 -1
- package/dist/esm/client/persistence/ephemeral-setting.js +116 -0
- package/dist/esm/client/persistence/group-history.js +145 -0
- package/dist/esm/client/persistence/history-blob.js +34 -0
- package/dist/esm/client/persistence/history-sync.js +157 -65
- package/dist/esm/crypto/core/index.js +1 -1
- package/dist/esm/crypto/core/primitives.js +47 -0
- package/dist/esm/crypto/core/xeddsa.js +36 -5
- package/dist/esm/crypto/curves/X25519.js +29 -2
- package/dist/esm/crypto/nativeBackend.js +93 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/media/crypto/WaMediaCrypto.js +38 -19
- package/dist/esm/message/context-info.js +39 -0
- package/dist/esm/message/crypto/reporting-token.js +38 -216
- package/dist/esm/message/encode/content.js +73 -12
- package/dist/esm/message/encode/media-payload.js +3 -1
- package/dist/esm/message/kinds/group-history.js +36 -0
- package/dist/esm/message/primitives/incoming.js +32 -13
- package/dist/esm/protocol/abprops.js +18 -162
- package/dist/esm/protocol/constants.js +4 -3
- package/dist/esm/protocol/defaults.js +7 -0
- package/dist/esm/protocol/device-identity.js +10 -5
- package/dist/esm/protocol/jid.js +83 -36
- package/dist/esm/protocol/media.js +1 -0
- package/dist/esm/protocol/message.js +8 -0
- package/dist/esm/protocol/notification.js +1 -0
- package/dist/esm/protocol/privacy.js +70 -4
- package/dist/esm/signal/group/SenderKeyChain.js +1 -2
- package/dist/esm/signal/group/SenderKeyManager.js +1 -7
- package/dist/esm/signal/session/SignalProtocol.js +22 -21
- package/dist/esm/signal/session/SignalRatchet.js +20 -17
- package/dist/esm/signal/session/encoding.js +25 -3
- package/dist/esm/signal/session/resolver.js +11 -5
- package/dist/esm/store/contracts/chat-metadata.store.js +1 -0
- package/dist/esm/store/createStore.js +19 -2
- package/dist/esm/store/index.js +1 -0
- package/dist/esm/store/memory/chat-metadata.store.js +66 -0
- package/dist/esm/store/noop.store.js +8 -0
- package/dist/esm/transport/binary/constants.js +19 -0
- package/dist/esm/transport/binary/decoder.js +17 -19
- package/dist/esm/transport/binary/encoder.js +61 -18
- package/dist/esm/transport/node/builders/global.js +20 -0
- package/dist/esm/transport/node/builders/message.js +32 -15
- package/dist/esm/transport/node/builders/privacy.js +63 -4
- package/dist/esm/transport/noise/WaClientPayload.js +43 -22
- package/dist/esm/transport/noise/WaFrameCodec.js +14 -4
- package/dist/esm/util/coercion.js +12 -4
- package/dist/esm/util/index.js +1 -1
- package/dist/esm/util/protoscan.js +181 -0
- package/dist/esm/util/runtime.js +34 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +15 -2
- package/dist/media/crypto/WaMediaCrypto.js +36 -17
- package/dist/media/types.d.ts +1 -1
- package/dist/message/context-info.d.ts +10 -0
- package/dist/message/context-info.js +40 -0
- package/dist/message/crypto/reporting-token.d.ts +3 -1
- package/dist/message/crypto/reporting-token.js +38 -216
- package/dist/message/encode/content.d.ts +24 -4
- package/dist/message/encode/content.js +74 -12
- package/dist/message/encode/media-payload.d.ts +5 -2
- package/dist/message/encode/media-payload.js +3 -1
- package/dist/message/kinds/group-history.d.ts +30 -0
- package/dist/message/kinds/group-history.js +40 -0
- package/dist/message/primitives/incoming.d.ts +5 -0
- package/dist/message/primitives/incoming.js +32 -13
- package/dist/message/types.d.ts +10 -0
- package/dist/protocol/abprops.d.ts +27 -140
- package/dist/protocol/abprops.js +18 -163
- package/dist/protocol/constants.d.ts +4 -3
- package/dist/protocol/constants.js +10 -3
- package/dist/protocol/defaults.d.ts +7 -0
- package/dist/protocol/defaults.js +7 -0
- package/dist/protocol/device-identity.d.ts +14 -4
- package/dist/protocol/device-identity.js +9 -4
- package/dist/protocol/jid.d.ts +4 -1
- package/dist/protocol/jid.js +83 -36
- package/dist/protocol/media.d.ts +1 -0
- package/dist/protocol/media.js +1 -0
- package/dist/protocol/message.d.ts +6 -0
- package/dist/protocol/message.js +9 -0
- package/dist/protocol/notification.d.ts +1 -0
- package/dist/protocol/notification.js +1 -0
- package/dist/protocol/privacy.d.ts +52 -0
- package/dist/protocol/privacy.js +71 -5
- package/dist/signal/group/SenderKeyChain.d.ts +1 -1
- package/dist/signal/group/SenderKeyChain.js +1 -2
- package/dist/signal/group/SenderKeyManager.js +1 -7
- package/dist/signal/session/SignalProtocol.d.ts +13 -0
- package/dist/signal/session/SignalProtocol.js +22 -21
- package/dist/signal/session/SignalRatchet.d.ts +4 -4
- package/dist/signal/session/SignalRatchet.js +19 -16
- package/dist/signal/session/SignalSession.d.ts +8 -1
- package/dist/signal/session/encoding.d.ts +5 -0
- package/dist/signal/session/encoding.js +25 -3
- package/dist/signal/session/resolver.js +11 -5
- package/dist/store/contracts/chat-metadata.store.d.ts +34 -0
- package/dist/store/contracts/chat-metadata.store.js +2 -0
- package/dist/store/contracts/group-metadata.store.d.ts +7 -0
- package/dist/store/contracts/thread.store.d.ts +6 -0
- package/dist/store/createStore.d.ts +10 -2
- package/dist/store/createStore.js +18 -1
- package/dist/store/index.d.ts +3 -1
- package/dist/store/index.js +3 -1
- package/dist/store/memory/chat-metadata.store.d.ts +26 -0
- package/dist/store/memory/chat-metadata.store.js +70 -0
- package/dist/store/noop.store.d.ts +2 -0
- package/dist/store/noop.store.js +9 -1
- package/dist/store/types.d.ts +190 -39
- package/dist/transport/binary/constants.d.ts +8 -0
- package/dist/transport/binary/constants.js +20 -1
- package/dist/transport/binary/decoder.js +17 -19
- package/dist/transport/binary/encoder.js +60 -17
- package/dist/transport/node/builders/global.d.ts +8 -0
- package/dist/transport/node/builders/global.js +21 -0
- package/dist/transport/node/builders/message.js +32 -15
- package/dist/transport/node/builders/privacy.d.ts +26 -2
- package/dist/transport/node/builders/privacy.js +63 -3
- package/dist/transport/noise/WaClientPayload.js +43 -22
- package/dist/transport/noise/WaFrameCodec.d.ts +1 -0
- package/dist/transport/noise/WaFrameCodec.js +14 -4
- package/dist/transport/noise/types.d.ts +17 -0
- package/dist/util/coercion.d.ts +6 -0
- package/dist/util/coercion.js +13 -4
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +2 -1
- package/dist/util/protoscan.d.ts +33 -0
- package/dist/util/protoscan.js +186 -0
- package/dist/util/runtime.d.ts +12 -0
- package/dist/util/runtime.js +35 -0
- package/package.json +3 -1
- package/spec/abprops/index.d.ts +4402 -0
- 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
|
|
169
|
-
const
|
|
170
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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 <
|
|
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 =
|
|
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] =
|
|
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):
|
|
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):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
126
|
+
signalMessage.subarray(0, versionedLength)
|
|
126
127
|
]);
|
|
127
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
215
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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 (!
|
|
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
|
+
}
|
|
@@ -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,
|
|
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<
|
|
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 =
|
|
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
|
},
|
package/dist/store/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/store/index.js
CHANGED
|
@@ -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;
|