libp2r2p 0.11.31 → 0.11.32

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 CHANGED
@@ -1284,3 +1284,9 @@ The 0.11.31 pending-decryption retry groups records by
1284
1284
  remaining records only when the local signer confirms `CONTENT_KEY_NOT_FOUND`
1285
1285
  with `hasOwnContentKey: false`. MAC, proof, parsing and operational failures are
1286
1286
  never generalized to the group, and every deferred record remains persisted.
1287
+
1288
+ The 0.11.32 local recovery-seed queue serializes incoming and outgoing writes
1289
+ within one PrivateMessenger. Duplicate `bySeedKey` races retry up to five times:
1290
+ NYM rows remain idempotent and router rows re-read the winning timestamps before
1291
+ replacement. External event-store seed adapters retain their independent batched
1292
+ writes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libp2r2p",
3
- "version": "0.11.31",
3
+ "version": "0.11.32",
4
4
  "description": "Peer-to-relay-to-peer",
5
5
  "keywords": [
6
6
  "p2r2p",
@@ -108,6 +108,7 @@ export {
108
108
  const DEFAULT_OFFLINE_RECOVERY_SECONDS = 7 * 24 * 60 * 60
109
109
  const MAX_OFFLINE_RECOVERY_SECONDS = Math.floor(Number.MAX_SAFE_INTEGER / 1000)
110
110
  const RECOVERY_STORAGE_BATCH_SIZE = 100
111
+ const SEED_QUEUE_UPSERT_ATTEMPTS = 5
111
112
  const STORAGE_POLICY_BROADCAST_CHANNEL = 'libp2r2p:private-messenger:storage-policy'
112
113
  const DEFAULT_OFFLINE_SKEW_SECONDS = 30
113
114
  const DEFAULT_RELOAD_GAP_DELAY_MS = 500
@@ -374,6 +375,7 @@ export class PrivateMessenger {
374
375
  this.watchRevisionByChannel = new Map()
375
376
  this.presenceTimers = new Map()
376
377
  this.presenceJobs = new Map()
378
+ this.seedQueueOperationTail = Promise.resolve()
377
379
  this.stopRelayListWatcher = null
378
380
  this.relayListWatcherPubkey = ''
379
381
  this.relayListRefreshPromise = null
@@ -686,6 +688,13 @@ export class PrivateMessenger {
686
688
  return this.runQueueOperation(operation)
687
689
  }
688
690
 
691
+ runSeedQueueOperation (operation) {
692
+ if (this.closePromise) return Promise.reject(new Error('PRIVATE_MESSENGER_CLOSED'))
693
+ const run = this.seedQueueOperationTail.then(operation)
694
+ this.seedQueueOperationTail = run.catch(() => {})
695
+ return run
696
+ }
697
+
689
698
  debug (action, detail = {}) {
690
699
  try {
691
700
  this.onDebug?.({ source: 'private-messenger', action, ...detail })
@@ -2140,8 +2149,8 @@ export class PrivateMessenger {
2140
2149
 
2141
2150
  async enqueueSeed (channelPubkey, seed) {
2142
2151
  if (!this.offlineRecoverySecondsFor(channelPubkey)) return
2143
- const receivedAt = nowSeconds()
2144
2152
  if (this.seedStorage) {
2153
+ const receivedAt = nowSeconds()
2145
2154
  const rows = seed.recordType === NYM_CARRIER_SEED_RECORD_TYPE || seed.carriers?.length
2146
2155
  ? [{ recordType: NYM_CARRIER_SEED_RECORD_TYPE, carriers: compactSeedNymCarriers(seed.carriers) }]
2147
2156
  : compactSeedRouterRows(seed)
@@ -2155,22 +2164,37 @@ export class PrivateMessenger {
2155
2164
  }
2156
2165
  return
2157
2166
  }
2167
+ return this.runSeedQueueOperation(() => this.enqueueQueueSeed(channelPubkey, seed))
2168
+ }
2169
+
2170
+ async enqueueQueueSeed (channelPubkey, seed) {
2171
+ this.assertOpen()
2172
+ if (!this.channels.has(channelPubkey) || !this.offlineRecoverySecondsFor(channelPubkey)) return
2173
+ const receivedAt = nowSeconds()
2158
2174
  if (seed.recordType === NYM_CARRIER_SEED_RECORD_TYPE || seed.carriers?.length) {
2159
2175
  const carriers = compactSeedNymCarriers(seed.carriers)
2160
2176
  const recordTime = nymCarrierRecordTime({ carriers }) || seed.outer?.created_at || receivedAt
2161
2177
  const key = nymCarrierSeedKey({ channelPubkey, carriers })
2162
2178
  const seedKey = key ? `nym:${key}` : ''
2163
- if (seedKey && await this.seedQueue.someBy('bySeedKey', seedKey)) return
2164
- await this.seedQueue.enqueue({
2165
- type: 'seed',
2166
- recordType: NYM_CARRIER_SEED_RECORD_TYPE,
2167
- channelPubkey,
2168
- receivedAt,
2169
- carriers,
2170
- meta: { channelPubkey: seed.channelPubkey },
2171
- [SEED_KEY]: seedKey || undefined,
2172
- [SEED_TIME]: recordTime
2173
- })
2179
+ for (let attempt = 0; attempt < SEED_QUEUE_UPSERT_ATTEMPTS; attempt++) {
2180
+ if (seedKey && await this.seedQueue.someBy('bySeedKey', seedKey)) break
2181
+ try {
2182
+ await this.seedQueue.enqueue({
2183
+ type: 'seed',
2184
+ recordType: NYM_CARRIER_SEED_RECORD_TYPE,
2185
+ channelPubkey,
2186
+ receivedAt,
2187
+ carriers,
2188
+ meta: { channelPubkey: seed.channelPubkey },
2189
+ [SEED_KEY]: seedKey || undefined,
2190
+ [SEED_TIME]: recordTime
2191
+ })
2192
+ break
2193
+ } catch (error) {
2194
+ if (error?.name !== 'ConstraintError') throw error
2195
+ if (attempt === SEED_QUEUE_UPSERT_ATTEMPTS - 1) throw error
2196
+ }
2197
+ }
2174
2198
  await this.pruneStoredSeeds(channelPubkey)
2175
2199
  return
2176
2200
  }
@@ -2180,21 +2204,29 @@ export class PrivateMessenger {
2180
2204
  const rowTime = row.lastSeenAt || row.router?.created_at || receivedAt
2181
2205
  const rowKey = routerSeedRowKey({ ...row, channelPubkey })
2182
2206
  const seedKey = `router:${rowKey}`
2183
- const [previous] = await this.seedQueue.removeBy('bySeedKey', seedKey)
2184
- const firstSeenAt = Math.min(previous?.firstSeenAt ?? rowTime, row.firstSeenAt ?? rowTime)
2185
- const lastSeenAt = Math.max(previous?.lastSeenAt ?? rowTime, row.lastSeenAt ?? rowTime)
2186
- await this.seedQueue.enqueue({
2187
- ...row,
2188
- type: 'seed',
2189
- recordType: ROUTER_SEED_RECORD_TYPE,
2190
- channelPubkey,
2191
- receivedAt,
2192
- firstSeenAt,
2193
- lastSeenAt,
2194
- meta: { channelPubkey: seed.channelPubkey },
2195
- [SEED_KEY]: seedKey,
2196
- [SEED_TIME]: lastSeenAt || rowTime
2197
- })
2207
+ for (let attempt = 0; attempt < SEED_QUEUE_UPSERT_ATTEMPTS; attempt++) {
2208
+ const [previous] = await this.seedQueue.removeBy('bySeedKey', seedKey)
2209
+ const firstSeenAt = Math.min(previous?.firstSeenAt ?? rowTime, row.firstSeenAt ?? rowTime)
2210
+ const lastSeenAt = Math.max(previous?.lastSeenAt ?? rowTime, row.lastSeenAt ?? rowTime)
2211
+ try {
2212
+ await this.seedQueue.enqueue({
2213
+ ...row,
2214
+ type: 'seed',
2215
+ recordType: ROUTER_SEED_RECORD_TYPE,
2216
+ channelPubkey,
2217
+ receivedAt,
2218
+ firstSeenAt,
2219
+ lastSeenAt,
2220
+ meta: { channelPubkey: seed.channelPubkey },
2221
+ [SEED_KEY]: seedKey,
2222
+ [SEED_TIME]: lastSeenAt || rowTime
2223
+ })
2224
+ break
2225
+ } catch (error) {
2226
+ if (error?.name !== 'ConstraintError') throw error
2227
+ if (attempt === SEED_QUEUE_UPSERT_ATTEMPTS - 1) throw error
2228
+ }
2229
+ }
2198
2230
  }
2199
2231
  await this.pruneStoredSeeds(channelPubkey)
2200
2232
  }