zapo-js 1.6.1 → 1.6.2
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/dist/esm/store/createStore.js +119 -57
- package/dist/store/createStore.js +119 -57
- package/dist/store/types.d.ts +31 -0
- package/package.json +1 -1
|
@@ -34,6 +34,7 @@ import { WaSignalMemoryStore } from './memory/signal.store.js';
|
|
|
34
34
|
import { WaThreadMemoryStore } from './memory/thread.store.js';
|
|
35
35
|
import { NOOP_CONTACT_STORE, NOOP_DEVICE_LIST_STORE, NOOP_GROUP_METADATA_STORE, NOOP_MESSAGE_SECRET_STORE, NOOP_MESSAGE_STORE, NOOP_RETRY_STORE, NOOP_THREAD_STORE } from './noop.store.js';
|
|
36
36
|
import { resolvePositive } from '../util/coercion.js';
|
|
37
|
+
import { toError } from '../util/primitives.js';
|
|
37
38
|
const DEFAULT_CACHE_TTLS_MS = Object.freeze({
|
|
38
39
|
retryMs: 60 * 1000,
|
|
39
40
|
groupMetadataMs: 5 * 60 * 1000,
|
|
@@ -109,6 +110,7 @@ export function createStore(options) {
|
|
|
109
110
|
messageSecret: resolvePositive(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
|
|
110
111
|
});
|
|
111
112
|
const sessions = new Map();
|
|
113
|
+
const pendingSessionDestroys = new Set();
|
|
112
114
|
let storeDestroyed = false;
|
|
113
115
|
return {
|
|
114
116
|
session(sessionId) {
|
|
@@ -148,37 +150,48 @@ export function createStore(options) {
|
|
|
148
150
|
? new WaContactMemoryStore({ maxContacts: ml.contacts })
|
|
149
151
|
: NOOP_CONTACT_STORE);
|
|
150
152
|
const rawPrivacyToken = resolveStore(id, backends, providers.privacyToken ?? 'memory', 'privacyToken', 'stores', () => new WaPrivacyTokenMemoryStore(ml.privacyTokens));
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
maxGroups: ml.groupMetadataGroups,
|
|
161
|
-
logger: memoryLogger?.child({
|
|
162
|
-
domain: 'groupMetadata',
|
|
163
|
-
sessionId: id
|
|
153
|
+
const buildCaches = () => ({
|
|
154
|
+
retry: withRetryLock(resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
|
|
155
|
+
? new WaRetryMemoryStore(cacheTtlsMs.retry, {
|
|
156
|
+
maxOutboundMessages: ml.retryOutboundMessages,
|
|
157
|
+
maxInboundCounters: ml.retryInboundCounters,
|
|
158
|
+
logger: memoryLogger?.child({
|
|
159
|
+
domain: 'retry',
|
|
160
|
+
sessionId: id
|
|
161
|
+
})
|
|
164
162
|
})
|
|
165
|
-
|
|
166
|
-
:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
175
|
-
maxSecrets: ml.messageSecrets,
|
|
176
|
-
logger: memoryLogger?.child({
|
|
177
|
-
domain: 'messageSecret',
|
|
178
|
-
sessionId: id
|
|
163
|
+
: NOOP_RETRY_STORE)),
|
|
164
|
+
groupMetadata: withGroupMetadataLock(resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' ||
|
|
165
|
+
!cacheProviders.groupMetadata
|
|
166
|
+
? new WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
|
|
167
|
+
maxGroups: ml.groupMetadataGroups,
|
|
168
|
+
logger: memoryLogger?.child({
|
|
169
|
+
domain: 'groupMetadata',
|
|
170
|
+
sessionId: id
|
|
171
|
+
})
|
|
179
172
|
})
|
|
180
|
-
|
|
181
|
-
:
|
|
173
|
+
: NOOP_GROUP_METADATA_STORE)),
|
|
174
|
+
deviceList: withDeviceListLock(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
|
|
175
|
+
? new WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
|
|
176
|
+
maxUsers: ml.deviceListUsers,
|
|
177
|
+
logger: memoryLogger?.child({
|
|
178
|
+
domain: 'deviceList',
|
|
179
|
+
sessionId: id
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
: NOOP_DEVICE_LIST_STORE)),
|
|
183
|
+
messageSecret: withMessageSecretLock(resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' ||
|
|
184
|
+
!cacheProviders.messageSecret
|
|
185
|
+
? new WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
186
|
+
maxSecrets: ml.messageSecrets,
|
|
187
|
+
logger: memoryLogger?.child({
|
|
188
|
+
domain: 'messageSecret',
|
|
189
|
+
sessionId: id
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
: NOOP_MESSAGE_SECRET_STORE))
|
|
193
|
+
});
|
|
194
|
+
let caches = buildCaches();
|
|
182
195
|
const authStore = withAuthLock(rawAuth);
|
|
183
196
|
const signalStore = withSignalLock(rawSignal);
|
|
184
197
|
const preKeyStore = withPreKeyLock(rawPreKey);
|
|
@@ -192,41 +205,72 @@ export function createStore(options) {
|
|
|
192
205
|
? withSenderKeyCache(rawSenderKey, cacheLayer.limits?.senderKey)
|
|
193
206
|
: rawSenderKey);
|
|
194
207
|
const appStateStore = withAppStateLock(rawAppState);
|
|
195
|
-
const retryStore = withRetryLock(rawRetry);
|
|
196
|
-
const groupMetadataStore = withGroupMetadataLock(rawGroupMetadata);
|
|
197
|
-
const deviceListStore = withDeviceListLock(rawDeviceList);
|
|
198
208
|
const messageStore = withMessageLock(rawMessages);
|
|
199
|
-
const messageSecretStore = withMessageSecretLock(rawMessageSecret);
|
|
200
209
|
const threadStore = withThreadLock(rawThreads);
|
|
201
210
|
const contactStore = withContactLock(rawContacts);
|
|
202
211
|
const privacyTokenStore = withPrivacyTokenLock(cacheLayer.privacyToken && usesBackend(providers.privacyToken)
|
|
203
212
|
? withPrivacyTokenCache(rawPrivacyToken, cacheLayer.limits?.privacyToken)
|
|
204
213
|
: rawPrivacyToken);
|
|
205
|
-
let cachesDestroyed = false;
|
|
206
214
|
let sessionDestroyed = false;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
messageSecretStore.clear()
|
|
215
|
+
let destroyPromise = null;
|
|
216
|
+
let cacheLifecycle = Promise.resolve();
|
|
217
|
+
const teardownCaches = async (target) => {
|
|
218
|
+
const cleared = await Promise.allSettled([
|
|
219
|
+
target.retry.clear(),
|
|
220
|
+
target.groupMetadata.clear(),
|
|
221
|
+
target.deviceList.clear(),
|
|
222
|
+
target.messageSecret.clear()
|
|
216
223
|
]);
|
|
217
|
-
await Promise.
|
|
218
|
-
destroyIfSupported(
|
|
219
|
-
destroyIfSupported(
|
|
220
|
-
destroyIfSupported(
|
|
221
|
-
destroyIfSupported(
|
|
224
|
+
const destroyed = await Promise.allSettled([
|
|
225
|
+
destroyIfSupported(target.retry),
|
|
226
|
+
destroyIfSupported(target.groupMetadata),
|
|
227
|
+
destroyIfSupported(target.deviceList),
|
|
228
|
+
destroyIfSupported(target.messageSecret)
|
|
222
229
|
]);
|
|
230
|
+
const failures = [...cleared, ...destroyed].filter((result) => result.status === 'rejected');
|
|
231
|
+
if (failures.length > 0) {
|
|
232
|
+
storeLogger?.warn('cache teardown had failures', {
|
|
233
|
+
sessionId: id,
|
|
234
|
+
droppedCount: failures.length,
|
|
235
|
+
totalExpected: cleared.length + destroyed.length,
|
|
236
|
+
sample: toError(failures[0].reason).message
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return failures.length;
|
|
240
|
+
};
|
|
241
|
+
const destroyCaches = () => {
|
|
242
|
+
const run = cacheLifecycle.then(async () => {
|
|
243
|
+
if (sessionDestroyed)
|
|
244
|
+
return;
|
|
245
|
+
const failureCount = await teardownCaches(caches);
|
|
246
|
+
caches = buildCaches();
|
|
247
|
+
if (failureCount > 0) {
|
|
248
|
+
throw new Error(`cache reset finished with ${failureCount} teardown failure(s); ` +
|
|
249
|
+
'fresh caches are in place but old persistent entries may remain');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
cacheLifecycle = run.then(() => undefined, () => undefined);
|
|
253
|
+
return run;
|
|
223
254
|
};
|
|
224
|
-
const destroy =
|
|
225
|
-
if (
|
|
226
|
-
|
|
255
|
+
const destroy = () => {
|
|
256
|
+
if (!destroyPromise) {
|
|
257
|
+
const pending = destroyInternal().finally(() => pendingSessionDestroys.delete(pending));
|
|
258
|
+
destroyPromise = pending;
|
|
259
|
+
pendingSessionDestroys.add(pending);
|
|
260
|
+
}
|
|
261
|
+
return destroyPromise;
|
|
262
|
+
};
|
|
263
|
+
const destroyInternal = async () => {
|
|
227
264
|
sessionDestroyed = true;
|
|
228
|
-
|
|
229
|
-
|
|
265
|
+
if (sessions.get(id) === storeSession) {
|
|
266
|
+
sessions.delete(id);
|
|
267
|
+
}
|
|
268
|
+
await cacheLifecycle;
|
|
269
|
+
await teardownCaches(caches);
|
|
270
|
+
await destroyPersistentStores();
|
|
271
|
+
};
|
|
272
|
+
const destroyPersistentStores = async () => {
|
|
273
|
+
const results = await Promise.allSettled([
|
|
230
274
|
destroyIfSupported(authStore),
|
|
231
275
|
destroyIfSupported(signalStore),
|
|
232
276
|
destroyIfSupported(preKeyStore),
|
|
@@ -239,6 +283,15 @@ export function createStore(options) {
|
|
|
239
283
|
destroyIfSupported(contactStore),
|
|
240
284
|
destroyIfSupported(privacyTokenStore)
|
|
241
285
|
]);
|
|
286
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
287
|
+
if (failures.length > 0) {
|
|
288
|
+
storeLogger?.warn('persistent store teardown had failures', {
|
|
289
|
+
sessionId: id,
|
|
290
|
+
droppedCount: failures.length,
|
|
291
|
+
totalExpected: results.length,
|
|
292
|
+
sample: toError(failures[0].reason).message
|
|
293
|
+
});
|
|
294
|
+
}
|
|
242
295
|
};
|
|
243
296
|
const storeSession = {
|
|
244
297
|
auth: authStore,
|
|
@@ -248,11 +301,19 @@ export function createStore(options) {
|
|
|
248
301
|
identity: identityStore,
|
|
249
302
|
senderKey: senderKeyStore,
|
|
250
303
|
appState: appStateStore,
|
|
251
|
-
retry
|
|
252
|
-
|
|
253
|
-
|
|
304
|
+
get retry() {
|
|
305
|
+
return caches.retry;
|
|
306
|
+
},
|
|
307
|
+
get groupMetadata() {
|
|
308
|
+
return caches.groupMetadata;
|
|
309
|
+
},
|
|
310
|
+
get deviceList() {
|
|
311
|
+
return caches.deviceList;
|
|
312
|
+
},
|
|
254
313
|
messages: messageStore,
|
|
255
|
-
messageSecret
|
|
314
|
+
get messageSecret() {
|
|
315
|
+
return caches.messageSecret;
|
|
316
|
+
},
|
|
256
317
|
threads: threadStore,
|
|
257
318
|
contacts: contactStore,
|
|
258
319
|
privacyToken: privacyTokenStore,
|
|
@@ -273,6 +334,7 @@ export function createStore(options) {
|
|
|
273
334
|
const list = Array.from(sessions.values());
|
|
274
335
|
sessions.clear();
|
|
275
336
|
await Promise.all(list.map((s) => s.destroy()));
|
|
337
|
+
await Promise.all(Array.from(pendingSessionDestroys));
|
|
276
338
|
const uniqueBackends = new Set(Object.values(backends));
|
|
277
339
|
await Promise.all(Array.from(uniqueBackends, (backend) => destroyIfSupported(backend)));
|
|
278
340
|
}
|
|
@@ -37,6 +37,7 @@ const signal_store_1 = require("./memory/signal.store");
|
|
|
37
37
|
const thread_store_1 = require("./memory/thread.store");
|
|
38
38
|
const noop_store_1 = require("./noop.store");
|
|
39
39
|
const coercion_1 = require("../util/coercion");
|
|
40
|
+
const primitives_1 = require("../util/primitives");
|
|
40
41
|
const DEFAULT_CACHE_TTLS_MS = Object.freeze({
|
|
41
42
|
retryMs: 60 * 1000,
|
|
42
43
|
groupMetadataMs: 5 * 60 * 1000,
|
|
@@ -112,6 +113,7 @@ function createStore(options) {
|
|
|
112
113
|
messageSecret: (0, coercion_1.resolvePositive)(options.memory?.cacheTtlMs?.messageSecretMs, DEFAULT_CACHE_TTLS_MS.messageSecretMs, 'memory.cacheTtlMs.messageSecretMs')
|
|
113
114
|
});
|
|
114
115
|
const sessions = new Map();
|
|
116
|
+
const pendingSessionDestroys = new Set();
|
|
115
117
|
let storeDestroyed = false;
|
|
116
118
|
return {
|
|
117
119
|
session(sessionId) {
|
|
@@ -151,37 +153,48 @@ function createStore(options) {
|
|
|
151
153
|
? new contact_store_1.WaContactMemoryStore({ maxContacts: ml.contacts })
|
|
152
154
|
: noop_store_1.NOOP_CONTACT_STORE);
|
|
153
155
|
const rawPrivacyToken = resolveStore(id, backends, providers.privacyToken ?? 'memory', 'privacyToken', 'stores', () => new privacy_token_store_1.WaPrivacyTokenMemoryStore(ml.privacyTokens));
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
maxGroups: ml.groupMetadataGroups,
|
|
164
|
-
logger: memoryLogger?.child({
|
|
165
|
-
domain: 'groupMetadata',
|
|
166
|
-
sessionId: id
|
|
156
|
+
const buildCaches = () => ({
|
|
157
|
+
retry: (0, retry_lock_1.withRetryLock)(resolveStore(id, backends, cacheProviders.retry ?? 'memory', 'retry', 'caches', () => cacheProviders.retry === 'memory' || !cacheProviders.retry
|
|
158
|
+
? new retry_store_1.WaRetryMemoryStore(cacheTtlsMs.retry, {
|
|
159
|
+
maxOutboundMessages: ml.retryOutboundMessages,
|
|
160
|
+
maxInboundCounters: ml.retryInboundCounters,
|
|
161
|
+
logger: memoryLogger?.child({
|
|
162
|
+
domain: 'retry',
|
|
163
|
+
sessionId: id
|
|
164
|
+
})
|
|
167
165
|
})
|
|
168
|
-
|
|
169
|
-
:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
? new message_secret_store_1.WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
178
|
-
maxSecrets: ml.messageSecrets,
|
|
179
|
-
logger: memoryLogger?.child({
|
|
180
|
-
domain: 'messageSecret',
|
|
181
|
-
sessionId: id
|
|
166
|
+
: noop_store_1.NOOP_RETRY_STORE)),
|
|
167
|
+
groupMetadata: (0, group_metadata_lock_1.withGroupMetadataLock)(resolveStore(id, backends, cacheProviders.groupMetadata ?? 'memory', 'groupMetadata', 'caches', () => cacheProviders.groupMetadata === 'memory' ||
|
|
168
|
+
!cacheProviders.groupMetadata
|
|
169
|
+
? new group_metadata_store_1.WaGroupMetadataMemoryStore(cacheTtlsMs.groupMetadata, {
|
|
170
|
+
maxGroups: ml.groupMetadataGroups,
|
|
171
|
+
logger: memoryLogger?.child({
|
|
172
|
+
domain: 'groupMetadata',
|
|
173
|
+
sessionId: id
|
|
174
|
+
})
|
|
182
175
|
})
|
|
183
|
-
|
|
184
|
-
:
|
|
176
|
+
: noop_store_1.NOOP_GROUP_METADATA_STORE)),
|
|
177
|
+
deviceList: (0, device_list_lock_1.withDeviceListLock)(resolveStore(id, backends, cacheProviders.deviceList ?? 'memory', 'deviceList', 'caches', () => cacheProviders.deviceList === 'memory' || !cacheProviders.deviceList
|
|
178
|
+
? new device_list_store_1.WaDeviceListMemoryStore(cacheTtlsMs.deviceList, {
|
|
179
|
+
maxUsers: ml.deviceListUsers,
|
|
180
|
+
logger: memoryLogger?.child({
|
|
181
|
+
domain: 'deviceList',
|
|
182
|
+
sessionId: id
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
: noop_store_1.NOOP_DEVICE_LIST_STORE)),
|
|
186
|
+
messageSecret: (0, message_secret_lock_1.withMessageSecretLock)(resolveStore(id, backends, cacheProviders.messageSecret ?? 'memory', 'messageSecret', 'caches', () => cacheProviders.messageSecret === 'memory' ||
|
|
187
|
+
!cacheProviders.messageSecret
|
|
188
|
+
? new message_secret_store_1.WaMessageSecretMemoryStore(cacheTtlsMs.messageSecret, {
|
|
189
|
+
maxSecrets: ml.messageSecrets,
|
|
190
|
+
logger: memoryLogger?.child({
|
|
191
|
+
domain: 'messageSecret',
|
|
192
|
+
sessionId: id
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
: noop_store_1.NOOP_MESSAGE_SECRET_STORE))
|
|
196
|
+
});
|
|
197
|
+
let caches = buildCaches();
|
|
185
198
|
const authStore = (0, auth_lock_1.withAuthLock)(rawAuth);
|
|
186
199
|
const signalStore = (0, signal_lock_1.withSignalLock)(rawSignal);
|
|
187
200
|
const preKeyStore = (0, pre_key_lock_1.withPreKeyLock)(rawPreKey);
|
|
@@ -195,41 +208,72 @@ function createStore(options) {
|
|
|
195
208
|
? (0, sender_key_cache_1.withSenderKeyCache)(rawSenderKey, cacheLayer.limits?.senderKey)
|
|
196
209
|
: rawSenderKey);
|
|
197
210
|
const appStateStore = (0, appstate_lock_1.withAppStateLock)(rawAppState);
|
|
198
|
-
const retryStore = (0, retry_lock_1.withRetryLock)(rawRetry);
|
|
199
|
-
const groupMetadataStore = (0, group_metadata_lock_1.withGroupMetadataLock)(rawGroupMetadata);
|
|
200
|
-
const deviceListStore = (0, device_list_lock_1.withDeviceListLock)(rawDeviceList);
|
|
201
211
|
const messageStore = (0, message_lock_1.withMessageLock)(rawMessages);
|
|
202
|
-
const messageSecretStore = (0, message_secret_lock_1.withMessageSecretLock)(rawMessageSecret);
|
|
203
212
|
const threadStore = (0, thread_lock_1.withThreadLock)(rawThreads);
|
|
204
213
|
const contactStore = (0, contact_lock_1.withContactLock)(rawContacts);
|
|
205
214
|
const privacyTokenStore = (0, privacy_token_lock_1.withPrivacyTokenLock)(cacheLayer.privacyToken && usesBackend(providers.privacyToken)
|
|
206
215
|
? (0, privacy_token_cache_1.withPrivacyTokenCache)(rawPrivacyToken, cacheLayer.limits?.privacyToken)
|
|
207
216
|
: rawPrivacyToken);
|
|
208
|
-
let cachesDestroyed = false;
|
|
209
217
|
let sessionDestroyed = false;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
messageSecretStore.clear()
|
|
218
|
+
let destroyPromise = null;
|
|
219
|
+
let cacheLifecycle = Promise.resolve();
|
|
220
|
+
const teardownCaches = async (target) => {
|
|
221
|
+
const cleared = await Promise.allSettled([
|
|
222
|
+
target.retry.clear(),
|
|
223
|
+
target.groupMetadata.clear(),
|
|
224
|
+
target.deviceList.clear(),
|
|
225
|
+
target.messageSecret.clear()
|
|
219
226
|
]);
|
|
220
|
-
await Promise.
|
|
221
|
-
destroyIfSupported(
|
|
222
|
-
destroyIfSupported(
|
|
223
|
-
destroyIfSupported(
|
|
224
|
-
destroyIfSupported(
|
|
227
|
+
const destroyed = await Promise.allSettled([
|
|
228
|
+
destroyIfSupported(target.retry),
|
|
229
|
+
destroyIfSupported(target.groupMetadata),
|
|
230
|
+
destroyIfSupported(target.deviceList),
|
|
231
|
+
destroyIfSupported(target.messageSecret)
|
|
225
232
|
]);
|
|
233
|
+
const failures = [...cleared, ...destroyed].filter((result) => result.status === 'rejected');
|
|
234
|
+
if (failures.length > 0) {
|
|
235
|
+
storeLogger?.warn('cache teardown had failures', {
|
|
236
|
+
sessionId: id,
|
|
237
|
+
droppedCount: failures.length,
|
|
238
|
+
totalExpected: cleared.length + destroyed.length,
|
|
239
|
+
sample: (0, primitives_1.toError)(failures[0].reason).message
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return failures.length;
|
|
243
|
+
};
|
|
244
|
+
const destroyCaches = () => {
|
|
245
|
+
const run = cacheLifecycle.then(async () => {
|
|
246
|
+
if (sessionDestroyed)
|
|
247
|
+
return;
|
|
248
|
+
const failureCount = await teardownCaches(caches);
|
|
249
|
+
caches = buildCaches();
|
|
250
|
+
if (failureCount > 0) {
|
|
251
|
+
throw new Error(`cache reset finished with ${failureCount} teardown failure(s); ` +
|
|
252
|
+
'fresh caches are in place but old persistent entries may remain');
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
cacheLifecycle = run.then(() => undefined, () => undefined);
|
|
256
|
+
return run;
|
|
226
257
|
};
|
|
227
|
-
const destroy =
|
|
228
|
-
if (
|
|
229
|
-
|
|
258
|
+
const destroy = () => {
|
|
259
|
+
if (!destroyPromise) {
|
|
260
|
+
const pending = destroyInternal().finally(() => pendingSessionDestroys.delete(pending));
|
|
261
|
+
destroyPromise = pending;
|
|
262
|
+
pendingSessionDestroys.add(pending);
|
|
263
|
+
}
|
|
264
|
+
return destroyPromise;
|
|
265
|
+
};
|
|
266
|
+
const destroyInternal = async () => {
|
|
230
267
|
sessionDestroyed = true;
|
|
231
|
-
|
|
232
|
-
|
|
268
|
+
if (sessions.get(id) === storeSession) {
|
|
269
|
+
sessions.delete(id);
|
|
270
|
+
}
|
|
271
|
+
await cacheLifecycle;
|
|
272
|
+
await teardownCaches(caches);
|
|
273
|
+
await destroyPersistentStores();
|
|
274
|
+
};
|
|
275
|
+
const destroyPersistentStores = async () => {
|
|
276
|
+
const results = await Promise.allSettled([
|
|
233
277
|
destroyIfSupported(authStore),
|
|
234
278
|
destroyIfSupported(signalStore),
|
|
235
279
|
destroyIfSupported(preKeyStore),
|
|
@@ -242,6 +286,15 @@ function createStore(options) {
|
|
|
242
286
|
destroyIfSupported(contactStore),
|
|
243
287
|
destroyIfSupported(privacyTokenStore)
|
|
244
288
|
]);
|
|
289
|
+
const failures = results.filter((result) => result.status === 'rejected');
|
|
290
|
+
if (failures.length > 0) {
|
|
291
|
+
storeLogger?.warn('persistent store teardown had failures', {
|
|
292
|
+
sessionId: id,
|
|
293
|
+
droppedCount: failures.length,
|
|
294
|
+
totalExpected: results.length,
|
|
295
|
+
sample: (0, primitives_1.toError)(failures[0].reason).message
|
|
296
|
+
});
|
|
297
|
+
}
|
|
245
298
|
};
|
|
246
299
|
const storeSession = {
|
|
247
300
|
auth: authStore,
|
|
@@ -251,11 +304,19 @@ function createStore(options) {
|
|
|
251
304
|
identity: identityStore,
|
|
252
305
|
senderKey: senderKeyStore,
|
|
253
306
|
appState: appStateStore,
|
|
254
|
-
retry
|
|
255
|
-
|
|
256
|
-
|
|
307
|
+
get retry() {
|
|
308
|
+
return caches.retry;
|
|
309
|
+
},
|
|
310
|
+
get groupMetadata() {
|
|
311
|
+
return caches.groupMetadata;
|
|
312
|
+
},
|
|
313
|
+
get deviceList() {
|
|
314
|
+
return caches.deviceList;
|
|
315
|
+
},
|
|
257
316
|
messages: messageStore,
|
|
258
|
-
messageSecret
|
|
317
|
+
get messageSecret() {
|
|
318
|
+
return caches.messageSecret;
|
|
319
|
+
},
|
|
259
320
|
threads: threadStore,
|
|
260
321
|
contacts: contactStore,
|
|
261
322
|
privacyToken: privacyTokenStore,
|
|
@@ -276,6 +337,7 @@ function createStore(options) {
|
|
|
276
337
|
const list = Array.from(sessions.values());
|
|
277
338
|
sessions.clear();
|
|
278
339
|
await Promise.all(list.map((s) => s.destroy()));
|
|
340
|
+
await Promise.all(Array.from(pendingSessionDestroys));
|
|
279
341
|
const uniqueBackends = new Set(Object.values(backends));
|
|
280
342
|
await Promise.all(Array.from(uniqueBackends, (backend) => destroyIfSupported(backend)));
|
|
281
343
|
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -33,12 +33,43 @@ export interface WaStoreSession {
|
|
|
33
33
|
readonly threads: WaThreadStore;
|
|
34
34
|
readonly contacts: WaContactStore;
|
|
35
35
|
readonly privacyToken: WaPrivacyTokenStore;
|
|
36
|
+
/**
|
|
37
|
+
* Tears down the cache domains (retry/groupMetadata/deviceList/
|
|
38
|
+
* messageSecret) and swaps fresh, empty ones into this bundle. The
|
|
39
|
+
* session stays usable; the caches rebuild on demand. The swap happens
|
|
40
|
+
* after the old stores finish tearing down (so a persistent cache
|
|
41
|
+
* backend is never cleared under the fresh stores), which means cache
|
|
42
|
+
* operations issued while the reset is in flight may reject. References
|
|
43
|
+
* to the old cache stores captured before the call (e.g. by a live
|
|
44
|
+
* client) reject afterwards - recreate the client to pick up the fresh
|
|
45
|
+
* stores. Rejects when the old generation's teardown had failures - the
|
|
46
|
+
* fresh caches are still in place, but stale entries may remain in a
|
|
47
|
+
* persistent cache backend.
|
|
48
|
+
*/
|
|
36
49
|
destroyCaches(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Final teardown of every domain store in this bundle. The bundle is
|
|
52
|
+
* single-shot: every operation on it rejects afterwards. The sessionId
|
|
53
|
+
* is released as soon as destruction starts, so a concurrent
|
|
54
|
+
* `store.session(id)` builds a fresh bundle instead of returning this
|
|
55
|
+
* one. Repeat calls return the same in-flight promise. Teardown
|
|
56
|
+
* failures are logged (warn), never thrown.
|
|
57
|
+
*/
|
|
37
58
|
destroy(): Promise<void>;
|
|
38
59
|
}
|
|
39
60
|
export interface WaStore {
|
|
61
|
+
/**
|
|
62
|
+
* Returns the lock-wrapped per-domain store bundle for `sessionId`,
|
|
63
|
+
* building it on first use and caching it until its `destroy()` (or the
|
|
64
|
+
* store-wide `destroy()`) releases it.
|
|
65
|
+
*/
|
|
40
66
|
session(sessionId: string): WaStoreSession;
|
|
67
|
+
/** Runs {@link WaStoreSession.destroyCaches} on every live session. */
|
|
41
68
|
destroyCaches(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Destroys every live session bundle and the registered backends. The
|
|
71
|
+
* store is single-shot: `session()` throws afterwards.
|
|
72
|
+
*/
|
|
42
73
|
destroy(): Promise<void>;
|
|
43
74
|
}
|
|
44
75
|
export interface WaStoreBackend {
|