libp2r2p 0.11.22 → 0.11.24

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.
@@ -56,6 +56,7 @@ import { normalizeRelayUrl } from '../url/index.js'
56
56
  import { deliveryInfo } from '../private-channel/helpers/rumor.js'
57
57
  import { migrateMessageQueue } from './services/migrate-message-queue.js'
58
58
  import { createPendingDecryption } from './services/pending-decryption.js'
59
+ import { createDecryptionScheduler } from './services/decryption-scheduler.js'
59
60
  import { decryptionError, isDeferredDecryptionError } from '../private-channel/helpers/decryption-error.js'
60
61
  import { bytesToBase64 } from '../base64/index.js'
61
62
  import { ValidationError } from '../error/index.js'
@@ -236,6 +237,7 @@ export class PrivateMessenger {
236
237
  autoDeletionCapability = true,
237
238
  _indexedDB = globalThis.indexedDB,
238
239
  useContentKeys = true,
240
+ pendingDecryptionReady = true,
239
241
  onContentKeyChange,
240
242
  onMessageQueued,
241
243
  onStateChanged,
@@ -248,6 +250,8 @@ export class PrivateMessenger {
248
250
  _subscribeRelayListUpdates = subscribeRelayListUpdates,
249
251
  _isOnline = isOnline,
250
252
  _waitForForeground,
253
+ _observeForeground,
254
+ _now = Date.now,
251
255
  _onOnline = onOnline,
252
256
  _random = Math.random,
253
257
  _setTimeout = globalThis.setTimeout.bind(globalThis),
@@ -259,6 +263,7 @@ export class PrivateMessenger {
259
263
  _BroadcastChannel = _indexedDB === globalThis.indexedDB ? globalThis.BroadcastChannel : undefined
260
264
  } = {}) {
261
265
  if (onStateChanged != null && typeof onStateChanged !== 'function') throw new ValidationError('INVALID_ON_STATE_CHANGED')
266
+ if (typeof pendingDecryptionReady !== 'boolean') throw new ValidationError('INVALID_PENDING_DECRYPTION_READY')
262
267
  this.fallbackRelays = normalizeFallbackRelays(fallbackRelays)
263
268
  this.fallbackDelayMs = normalizeFallbackDelay(fallbackDelayMs)
264
269
  this.offlineRecoverySeconds = normalizeOfflineRecoverySeconds(offlineRecoverySeconds)
@@ -383,6 +388,9 @@ export class PrivateMessenger {
383
388
  this.closePromise = null
384
389
  this.initSettledPromise = null
385
390
  this.initialized = false
391
+ this.pendingDecryptionReady = pendingDecryptionReady
392
+ this.decryptionScheduler = createDecryptionScheduler({ ready: pendingDecryptionReady, now: _now, setTimer: _setTimeout, clearTimer: _clearTimeout })
393
+ this.stopDecryptionForeground = _observeForeground?.(active => this.decryptionScheduler.foreground(active))
386
394
  this.notifyStatus()
387
395
  }
388
396
 
@@ -1222,6 +1230,7 @@ export class PrivateMessenger {
1222
1230
  onSeed: seed => this.receive(pubkey, () => this.enqueueSeed(pubkey, seed), seed),
1223
1231
  onContentKeyUsage: usage => this.handleContentKeyUsage(pubkey, usage),
1224
1232
  onDecryptionFailure: failure => this.deferDecryption(failure),
1233
+ _onProcessing: pubkey => this.decryptionScheduler.activity(pubkey),
1225
1234
  isDecryptionPending: record => this.pendingDecryption.has(record),
1226
1235
  receivedChunkTtlMs: this.receivedChunkTtlMsFor(channel),
1227
1236
  receivedChunkIndexedDB: this._indexedDB,
@@ -1734,7 +1743,10 @@ export class PrivateMessenger {
1734
1743
  unwatch (channels) {
1735
1744
  const pubkeys = channels ? uniq(Array.isArray(channels) ? channels : [channels]) : [...this.desiredChannels]
1736
1745
  for (const extension of this.extensions) extension.unwatch?.(pubkeys)
1737
- for (const pubkey of pubkeys) this.desiredChannels.delete(pubkey)
1746
+ for (const pubkey of pubkeys) {
1747
+ this.desiredChannels.delete(pubkey)
1748
+ this.decryptionScheduler.remove(pubkey)
1749
+ }
1738
1750
  for (const [key, pending] of this.pendingStorageWrites) if (pubkeys.includes(pending.record.channelPubkey)) this.pendingStorageWrites.delete(key)
1739
1751
  this.syncPauseRecovery()
1740
1752
  for (const pubkey of pubkeys) this.dropRecoveryState(pubkey)
@@ -1904,6 +1916,7 @@ export class PrivateMessenger {
1904
1916
  if (job) { jobs.push(job.work); continue }
1905
1917
  const controller = new AbortController()
1906
1918
  const channel = this.channels.get(pubkey)
1919
+ const releaseActivity = this.decryptionScheduler.activity(pubkey)
1907
1920
  job = { controller, work: null }
1908
1921
  this.channelBackground.set(pubkey, job)
1909
1922
  const active = () => this.initialized && !controller.signal.aborted && !this.closePromise && !this.pauseReasons.size && this.channels.get(pubkey) === channel && this.desiredChannels.has(pubkey)
@@ -1940,6 +1953,7 @@ export class PrivateMessenger {
1940
1953
  throw error
1941
1954
  })
1942
1955
  job.work = Promise.race([work, cancelled]).finally(() => {
1956
+ releaseActivity()
1943
1957
  controller.signal.removeEventListener('abort', stopAbort)
1944
1958
  if (this.channelBackground.get(pubkey) === job) this.channelBackground.delete(pubkey)
1945
1959
  })
@@ -1949,18 +1963,19 @@ export class PrivateMessenger {
1949
1963
  return jobs
1950
1964
  }
1951
1965
 
1952
- receive (pubkey, operation, message = {}) {
1966
+ receive (pubkey, operation, message = {}, { trackActivity = true } = {}) {
1953
1967
  if (this.closePromise) {
1954
1968
  if (this.storageActive) this.recordInterruption([pubkey], messageTime(message))
1955
1969
  return Promise.reject(new Error('PRIVATE_MESSENGER_CLOSED'))
1956
1970
  }
1971
+ const releaseActivity = trackActivity ? this.decryptionScheduler.activity(pubkey) : () => {}
1957
1972
  return this.queueIncoming(async () => {
1958
1973
  if (this.pauseReasons.size || !this.desiredChannels.has(pubkey)) {
1959
1974
  this.recordInterruption([pubkey], messageTime(message))
1960
1975
  throw this.pausedError()
1961
1976
  }
1962
1977
  return operation()
1963
- })
1978
+ }).finally(releaseActivity)
1964
1979
  }
1965
1980
 
1966
1981
  nip65WatchChannelPubkeys () {
@@ -2274,180 +2289,204 @@ export class PrivateMessenger {
2274
2289
 
2275
2290
  async ask ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkey, relays, relayToReceivers, message, code, payload, error, content, deletionPubkey }) {
2276
2291
  const channel = this.requireWritableChannel(channelPubkey)
2277
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [receiverPubkey], relays, relayToReceivers })
2278
- this.debugSend('ask', channelPubkey, { code, receiverPubkey })
2279
- return this._privateMessage.ask({
2280
- senderSigner: this.userSigner,
2281
- imkcSigner: this.contentKeySigner,
2282
- privateChannelSigner: channel.signer,
2283
- privateChannelReaderPubkey: channel.readerPubkey,
2284
- receiverPubkey,
2285
- ...routing,
2286
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2287
- temporaryStorageArea: this.temporaryStorageArea,
2288
- deletionPubkey,
2289
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2290
- message,
2291
- code,
2292
- payload,
2293
- error,
2294
- content,
2295
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2296
- _getIykcProofs: this.contentKeyLookup()
2297
- })
2292
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2293
+ try {
2294
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [receiverPubkey], relays, relayToReceivers })
2295
+ this.debugSend('ask', channelPubkey, { code, receiverPubkey })
2296
+ return await this._privateMessage.ask({
2297
+ senderSigner: this.userSigner,
2298
+ imkcSigner: this.contentKeySigner,
2299
+ privateChannelSigner: channel.signer,
2300
+ privateChannelReaderPubkey: channel.readerPubkey,
2301
+ receiverPubkey,
2302
+ ...routing,
2303
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2304
+ temporaryStorageArea: this.temporaryStorageArea,
2305
+ deletionPubkey,
2306
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2307
+ message,
2308
+ code,
2309
+ payload,
2310
+ error,
2311
+ content,
2312
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2313
+ _getIykcProofs: this.contentKeyLookup()
2314
+ })
2315
+ } finally { releaseActivity() }
2298
2316
  }
2299
2317
 
2300
2318
  async reply ({ channelPubkey = this.defaultChannelPubkey(), question, receiverPubkey, relays, relayToReceivers, message, code, payload, error, content, deletionPubkey }) {
2301
2319
  const channel = this.requireWritableChannel(channelPubkey)
2302
- const resolvedReceiverPubkey = receiverPubkey || question?.pubkey || ''
2303
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [resolvedReceiverPubkey], relays, relayToReceivers })
2304
- this.debugSend('reply', channelPubkey, { code, receiverPubkey: receiverPubkey || question?.pubkey || '' })
2305
- return this._privateMessage.reply({
2306
- senderSigner: this.userSigner,
2307
- imkcSigner: this.contentKeySigner,
2308
- privateChannelSigner: channel.signer,
2309
- privateChannelReaderPubkey: channel.readerPubkey,
2310
- question,
2311
- receiverPubkey,
2312
- ...routing,
2313
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2314
- temporaryStorageArea: this.temporaryStorageArea,
2315
- deletionPubkey,
2316
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2317
- message,
2318
- code,
2319
- payload,
2320
- error,
2321
- content,
2322
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2323
- _getIykcProofs: this.contentKeyLookup()
2324
- })
2320
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2321
+ try {
2322
+ const resolvedReceiverPubkey = receiverPubkey || question?.pubkey || ''
2323
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [resolvedReceiverPubkey], relays, relayToReceivers })
2324
+ this.debugSend('reply', channelPubkey, { code, receiverPubkey: receiverPubkey || question?.pubkey || '' })
2325
+ return await this._privateMessage.reply({
2326
+ senderSigner: this.userSigner,
2327
+ imkcSigner: this.contentKeySigner,
2328
+ privateChannelSigner: channel.signer,
2329
+ privateChannelReaderPubkey: channel.readerPubkey,
2330
+ question,
2331
+ receiverPubkey,
2332
+ ...routing,
2333
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2334
+ temporaryStorageArea: this.temporaryStorageArea,
2335
+ deletionPubkey,
2336
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2337
+ message,
2338
+ code,
2339
+ payload,
2340
+ error,
2341
+ content,
2342
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2343
+ _getIykcProofs: this.contentKeyLookup()
2344
+ })
2345
+ } finally { releaseActivity() }
2325
2346
  }
2326
2347
 
2327
2348
  async tell ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkey, relays, relayToReceivers, message, code, payload, error, content, deletionPubkey }) {
2328
2349
  const channel = this.requireWritableChannel(channelPubkey)
2329
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [receiverPubkey], relays, relayToReceivers })
2330
- this.debugSend('tell', channelPubkey, { code, receiverPubkey })
2331
- return this._privateMessage.tell({
2332
- senderSigner: this.userSigner,
2333
- imkcSigner: this.contentKeySigner,
2334
- privateChannelSigner: channel.signer,
2335
- privateChannelReaderPubkey: channel.readerPubkey,
2336
- receiverPubkey,
2337
- ...routing,
2338
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2339
- temporaryStorageArea: this.temporaryStorageArea,
2340
- deletionPubkey,
2341
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2342
- message,
2343
- code,
2344
- payload,
2345
- error,
2346
- content,
2347
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2348
- _getIykcProofs: this.contentKeyLookup()
2349
- })
2350
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2351
+ try {
2352
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys: [receiverPubkey], relays, relayToReceivers })
2353
+ this.debugSend('tell', channelPubkey, { code, receiverPubkey })
2354
+ return await this._privateMessage.tell({
2355
+ senderSigner: this.userSigner,
2356
+ imkcSigner: this.contentKeySigner,
2357
+ privateChannelSigner: channel.signer,
2358
+ privateChannelReaderPubkey: channel.readerPubkey,
2359
+ receiverPubkey,
2360
+ ...routing,
2361
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2362
+ temporaryStorageArea: this.temporaryStorageArea,
2363
+ deletionPubkey,
2364
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2365
+ message,
2366
+ code,
2367
+ payload,
2368
+ error,
2369
+ content,
2370
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2371
+ _getIykcProofs: this.contentKeyLookup()
2372
+ })
2373
+ } finally { releaseActivity() }
2350
2374
  }
2351
2375
 
2352
2376
  async yell ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkeys, relays, relayToReceivers, message, code, payload, error, content, deletionPubkey }) {
2353
2377
  const channel = this.requireWritableChannel(channelPubkey)
2354
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2355
- this.debugSend('yell', channelPubkey, { code, receiverPubkeys })
2356
- return this._privateMessage.yell({
2357
- senderSigner: this.userSigner,
2358
- imkcSigner: this.contentKeySigner,
2359
- privateChannelSigner: channel.signer,
2360
- privateChannelReaderPubkey: channel.readerPubkey,
2361
- receiverPubkeys,
2362
- ...routing,
2363
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2364
- temporaryStorageArea: this.temporaryStorageArea,
2365
- deletionPubkey,
2366
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2367
- message,
2368
- code,
2369
- payload,
2370
- error,
2371
- content,
2372
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2373
- _getIykcProofs: this.contentKeyLookup()
2374
- })
2378
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2379
+ try {
2380
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2381
+ this.debugSend('yell', channelPubkey, { code, receiverPubkeys })
2382
+ return await this._privateMessage.yell({
2383
+ senderSigner: this.userSigner,
2384
+ imkcSigner: this.contentKeySigner,
2385
+ privateChannelSigner: channel.signer,
2386
+ privateChannelReaderPubkey: channel.readerPubkey,
2387
+ receiverPubkeys,
2388
+ ...routing,
2389
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2390
+ temporaryStorageArea: this.temporaryStorageArea,
2391
+ deletionPubkey,
2392
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2393
+ message,
2394
+ code,
2395
+ payload,
2396
+ error,
2397
+ content,
2398
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2399
+ _getIykcProofs: this.contentKeyLookup()
2400
+ })
2401
+ } finally { releaseActivity() }
2375
2402
  }
2376
2403
 
2377
2404
  async broadcastRumor ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkeys, relays, relayToReceivers, rumor, deletionPubkey, signal }) {
2378
2405
  const channel = this.requireWritableChannel(channelPubkey)
2379
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers, signal })
2380
- this.debugSend('broadcastRumor', channelPubkey, { receiverPubkeys })
2381
- return this._privateMessage.broadcastRumor({
2382
- senderSigner: this.userSigner,
2383
- imkcSigner: this.contentKeySigner,
2384
- privateChannelSigner: channel.signer,
2385
- privateChannelReaderPubkey: channel.readerPubkey,
2386
- receiverPubkeys,
2387
- ...routing,
2388
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2389
- temporaryStorageArea: this.temporaryStorageArea,
2390
- deletionPubkey,
2391
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2392
- rumor,
2393
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2394
- _getIykcProofs: this.contentKeyLookup()
2395
- })
2406
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2407
+ try {
2408
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers, signal })
2409
+ this.debugSend('broadcastRumor', channelPubkey, { receiverPubkeys })
2410
+ return await this._privateMessage.broadcastRumor({
2411
+ senderSigner: this.userSigner,
2412
+ imkcSigner: this.contentKeySigner,
2413
+ privateChannelSigner: channel.signer,
2414
+ privateChannelReaderPubkey: channel.readerPubkey,
2415
+ receiverPubkeys,
2416
+ ...routing,
2417
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2418
+ temporaryStorageArea: this.temporaryStorageArea,
2419
+ deletionPubkey,
2420
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2421
+ rumor,
2422
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2423
+ _getIykcProofs: this.contentKeyLookup()
2424
+ })
2425
+ } finally { releaseActivity() }
2396
2426
  }
2397
2427
 
2398
2428
  async broadcastEvent ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkeys, relays, relayToReceivers, event, deletionPubkey, signal }) {
2399
2429
  const channel = this.requireWritableChannel(channelPubkey)
2400
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers, signal })
2401
- this.debugSend('broadcastEvent', channelPubkey, { receiverPubkeys })
2402
- return this._privateMessage.broadcastEvent({
2403
- senderSigner: this.userSigner,
2404
- imkcSigner: this.contentKeySigner,
2405
- privateChannelSigner: channel.signer,
2406
- privateChannelReaderPubkey: channel.readerPubkey,
2407
- receiverPubkeys,
2408
- ...routing,
2409
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2410
- temporaryStorageArea: this.temporaryStorageArea,
2411
- deletionPubkey,
2412
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2413
- event,
2414
- onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2415
- _getIykcProofs: this.contentKeyLookup()
2416
- })
2430
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2431
+ try {
2432
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers, signal })
2433
+ this.debugSend('broadcastEvent', channelPubkey, { receiverPubkeys })
2434
+ return await this._privateMessage.broadcastEvent({
2435
+ senderSigner: this.userSigner,
2436
+ imkcSigner: this.contentKeySigner,
2437
+ privateChannelSigner: channel.signer,
2438
+ privateChannelReaderPubkey: channel.readerPubkey,
2439
+ receiverPubkeys,
2440
+ ...routing,
2441
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2442
+ temporaryStorageArea: this.temporaryStorageArea,
2443
+ deletionPubkey,
2444
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2445
+ event,
2446
+ onPreparedSeed: this.outgoingSeedHandler(channelPubkey),
2447
+ _getIykcProofs: this.contentKeyLookup()
2448
+ })
2449
+ } finally { releaseActivity() }
2417
2450
  }
2418
2451
 
2419
2452
  async broadcastNymRumor ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkeys, relays, relayToReceivers, rumor, nymSigner, deletionPubkey }) {
2420
2453
  const channel = this.requireWritableChannel(channelPubkey)
2421
- const resolvedNymSigner = this.requireNymSigner(channel, nymSigner)
2422
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2423
- this.debugSend('broadcastNymRumor', channelPubkey, { receiverPubkeys })
2424
- return this._privateMessage.broadcastNymRumor({
2425
- nymSigner: resolvedNymSigner,
2426
- privateChannelSigner: channel.signer,
2427
- privateChannelReaderPubkey: channel.readerPubkey,
2428
- ...routing,
2429
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2430
- deletionPubkey,
2431
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2432
- rumor
2433
- })
2454
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2455
+ try {
2456
+ const resolvedNymSigner = this.requireNymSigner(channel, nymSigner)
2457
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2458
+ this.debugSend('broadcastNymRumor', channelPubkey, { receiverPubkeys })
2459
+ return await this._privateMessage.broadcastNymRumor({
2460
+ nymSigner: resolvedNymSigner,
2461
+ privateChannelSigner: channel.signer,
2462
+ privateChannelReaderPubkey: channel.readerPubkey,
2463
+ ...routing,
2464
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2465
+ deletionPubkey,
2466
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2467
+ rumor
2468
+ })
2469
+ } finally { releaseActivity() }
2434
2470
  }
2435
2471
 
2436
2472
  async broadcastNymEvent ({ channelPubkey = this.defaultChannelPubkey(), receiverPubkeys, relays, relayToReceivers, event, nymSigner, deletionPubkey }) {
2437
2473
  const channel = this.requireWritableChannel(channelPubkey)
2438
- const resolvedNymSigner = this.requireNymSigner(channel, nymSigner)
2439
- const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2440
- this.debugSend('broadcastNymEvent', channelPubkey, { receiverPubkeys })
2441
- return this._privateMessage.broadcastNymEvent({
2442
- nymSigner: resolvedNymSigner,
2443
- privateChannelSigner: channel.signer,
2444
- privateChannelReaderPubkey: channel.readerPubkey,
2445
- ...routing,
2446
- expirationSeconds: this.eventExpirationSecondsFor(channel),
2447
- deletionPubkey,
2448
- autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2449
- event
2450
- })
2474
+ const releaseActivity = this.decryptionScheduler.activity(channelPubkey)
2475
+ try {
2476
+ const resolvedNymSigner = this.requireNymSigner(channel, nymSigner)
2477
+ const routing = await this.resolveSendRouting({ channel, receiverPubkeys, relays, relayToReceivers })
2478
+ this.debugSend('broadcastNymEvent', channelPubkey, { receiverPubkeys })
2479
+ return await this._privateMessage.broadcastNymEvent({
2480
+ nymSigner: resolvedNymSigner,
2481
+ privateChannelSigner: channel.signer,
2482
+ privateChannelReaderPubkey: channel.readerPubkey,
2483
+ ...routing,
2484
+ expirationSeconds: this.eventExpirationSecondsFor(channel),
2485
+ deletionPubkey,
2486
+ autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
2487
+ event
2488
+ })
2489
+ } finally { releaseActivity() }
2451
2490
  }
2452
2491
 
2453
2492
  async publishSeederPresence (channelPubkey = this.defaultChannelPubkey(), { signal } = {}) {
@@ -2881,6 +2920,7 @@ export class PrivateMessenger {
2881
2920
  onSeedEvent: seed => this.receive(channelPubkey, () => this.enqueueSeed(channelPubkey, seed), seed),
2882
2921
  onContentKeyUsage: usage => this.handleContentKeyUsage(channelPubkey, usage),
2883
2922
  onDecryptionFailure: failure => this.deferDecryption(failure),
2923
+ _onProcessing: pubkey => this.decryptionScheduler.activity(pubkey),
2884
2924
  isDecryptionPending: record => this.pendingDecryption.has(record),
2885
2925
  onError: err => { throw err }
2886
2926
  }
@@ -3146,22 +3186,36 @@ export class PrivateMessenger {
3146
3186
  }
3147
3187
  }
3148
3188
 
3189
+ setPendingDecryptionReady (ready) {
3190
+ if (typeof ready !== 'boolean') throw new ValidationError('INVALID_PENDING_DECRYPTION_READY')
3191
+ this.pendingDecryptionReady = ready
3192
+ this.decryptionScheduler.setReady(ready)
3193
+ }
3194
+
3195
+ setPendingDecryptionPriority (pubkey) {
3196
+ if (pubkey !== null && !/^[0-9a-f]{64}$/.test(pubkey || '')) throw new ValidationError('INVALID_PENDING_DECRYPTION_PRIORITY')
3197
+ this.decryptionScheduler.priority(pubkey)
3198
+ }
3199
+
3149
3200
  retryPendingDecryptions ({ force = false } = {}) {
3150
3201
  if (typeof force !== 'boolean') throw new ValidationError('INVALID_FORCE')
3151
3202
  if (this.closePromise || this.pauseReasons.size || !this.initialized) return Promise.resolve()
3152
3203
  if (this.decryptionRetry && !this.decryptionRetry.controller.signal.aborted) {
3153
- this.decryptionRetry.again = true
3204
+ // Before a pass reads its snapshot, new requests belong to that pass.
3205
+ if (this.decryptionRetry.started) this.decryptionRetry.again = true
3154
3206
  this.decryptionRetry.force ||= force
3155
3207
  return this.decryptionRetry.work
3156
3208
  }
3157
- const job = { controller: new AbortController(), again: false, force, work: null }
3209
+ const job = { controller: new AbortController(), again: false, force, started: false, work: null }
3158
3210
  const previous = this.decryptionRetry?.work
3159
3211
  this.decryptionRetry = job
3160
3212
  const { signal } = job.controller
3161
3213
  const run = async () => {
3162
3214
  do {
3215
+ await this.decryptionScheduler.waitReady(signal)
3163
3216
  signal.throwIfAborted()
3164
3217
  job.again = false
3218
+ job.started = true
3165
3219
  const force = job.force
3166
3220
  job.force = false
3167
3221
  const groups = new Map()
@@ -3169,35 +3223,59 @@ export class PrivateMessenger {
3169
3223
  if (!groups.has(record.channelPubkey)) groups.set(record.channelPubkey, [])
3170
3224
  groups.get(record.channelPubkey).push(record)
3171
3225
  }
3172
- for (const [channelPubkey, records] of groups) {
3173
- signal.throwIfAborted()
3226
+ const results = await Promise.allSettled([...groups].map(async ([channelPubkey, records]) => {
3174
3227
  const channel = this.channels.get(channelPubkey)
3175
- if (!channel || !this.desiredChannels.has(channelPubkey)) continue
3176
- const current = () => { signal.throwIfAborted(); if (this.channels.get(channelPubkey) !== channel) throw new Error('PRIVATE_CHANNEL_CHANGED') }
3177
- await this._privateChannel.replayDeferredDecryptions({
3178
- records, signal, receiverSigner: this.userSigner, receiverPubkey: this.userPubkey,
3179
- privateChannelSigner: channel.signer, privateChannelReaderSigner: channel.readerSigner,
3180
- privateChannelReaderPubkey: channel.readerPubkey, mode: channel.mode,
3181
- receivedChunkScope: `${this.prefix}:pending`, receivedChunkIndexedDB: this._indexedDB,
3182
- onEvent: (event, outer, meta) => {
3183
- current()
3184
- return this.receive(channelPubkey, () => this.enqueueRumor(eventType(event), channelPubkey, { event, outer, meta, payload: parseEventContent(event) }), { event, outer })
3185
- },
3186
- onNymEvent: (event, outer, meta) => {
3187
- current()
3188
- return this.receive(channelPubkey, () => this.enqueueRumor('nym', channelPubkey, { event, outer, meta, payload: parseEventContent(event) }), { event, outer })
3189
- },
3190
- onSeedEvent: seed => { current(); return this.receive(channelPubkey, () => this.enqueueSeed(channelPubkey, seed), seed) },
3191
- onRecovered: record => { current(); return this.pendingDecryption.recovered(record) },
3192
- onFailure: (record, error) => { current(); return this.pendingDecryption.failed(record, error) }
3193
- })
3194
- }
3228
+ if (!channel || !this.desiredChannels.has(channelPubkey)) return
3229
+ const current = () => {
3230
+ signal.throwIfAborted()
3231
+ if (this.pauseReasons.size || this.channels.get(channelPubkey) !== channel || !this.desiredChannels.has(channelPubkey)) throw new Error('PRIVATE_CHANNEL_CHANGED')
3232
+ }
3233
+ try {
3234
+ await this._privateChannel.replayDeferredDecryptions({
3235
+ records, signal, receiverSigner: this.userSigner, receiverPubkey: this.userPubkey,
3236
+ privateChannelSigner: channel.signer, privateChannelReaderSigner: channel.readerSigner,
3237
+ privateChannelReaderPubkey: channel.readerPubkey, mode: channel.mode,
3238
+ receivedChunkScope: `${this.prefix}:pending`, receivedChunkIndexedDB: this._indexedDB,
3239
+ _runRecord: async (record, process) => {
3240
+ let done = false
3241
+ while (!done) {
3242
+ current()
3243
+ done = await this.decryptionScheduler.run(channelPubkey, () => {
3244
+ const attempt = async () => {
3245
+ current()
3246
+ if (!this.pendingDecryptionReady) return false
3247
+ if (await this.pendingDecryption.current(record, { force })) await process()
3248
+ return true
3249
+ }
3250
+ const locks = globalThis.navigator?.locks
3251
+ return locks?.request ? locks.request(`${this.prefix}:pending-decryption:retry`, { signal }, attempt) : attempt()
3252
+ }, signal)
3253
+ }
3254
+ },
3255
+ onEvent: (event, outer, meta) => {
3256
+ current()
3257
+ return this.receive(channelPubkey, () => this.enqueueRumor(eventType(event), channelPubkey, { event, outer, meta, payload: parseEventContent(event) }), { event, outer }, { trackActivity: false })
3258
+ },
3259
+ onNymEvent: (event, outer, meta) => {
3260
+ current()
3261
+ return this.receive(channelPubkey, () => this.enqueueRumor('nym', channelPubkey, { event, outer, meta, payload: parseEventContent(event) }), { event, outer }, { trackActivity: false })
3262
+ },
3263
+ onSeedEvent: seed => { current(); return this.receive(channelPubkey, () => this.enqueueSeed(channelPubkey, seed), seed, { trackActivity: false }) },
3264
+ onRecovered: record => { current(); return this.pendingDecryption.recovered(record) },
3265
+ onFailure: (record, error) => { current(); return this.pendingDecryption.failed(record, error) }
3266
+ })
3267
+ } catch (error) {
3268
+ if (this.channels.get(channelPubkey) === channel && this.desiredChannels.has(channelPubkey)) throw error
3269
+ } finally { this.decryptionScheduler.finish(channelPubkey) }
3270
+ }))
3271
+ const failed = results.find(result => result.status === 'rejected')
3272
+ if (failed) throw failed.reason
3273
+ job.started = false
3195
3274
  } while (job.again)
3196
3275
  }
3197
- job.work = Promise.resolve(previous).catch(() => {}).then(() => {
3198
- const locks = globalThis.navigator?.locks
3199
- return locks?.request ? locks.request(`${this.prefix}:pending-decryption:retry`, { signal }, run) : run()
3200
- }).catch(error => { if (!signal.aborted) throw error }).finally(() => {
3276
+ job.work = Promise.resolve(previous).catch(() => {}).then(run).catch(error => {
3277
+ if (!signal.aborted) throw error
3278
+ }).finally(() => {
3201
3279
  if (this.decryptionRetry === job) this.decryptionRetry = null
3202
3280
  })
3203
3281
  return job.work
@@ -3256,6 +3334,8 @@ export class PrivateMessenger {
3256
3334
 
3257
3335
  close () {
3258
3336
  this.decryptionRetry?.controller.abort(new Error('PRIVATE_MESSENGER_CLOSED'))
3337
+ this.decryptionScheduler.close()
3338
+ this.stopDecryptionForeground?.()
3259
3339
  if (this.closePromise) return this.closePromise
3260
3340
  const initSettledPromise = this.initSettledPromise
3261
3341
  this.sendRoutingLifetime.abort()