libp2r2p 0.5.0 → 0.7.0
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 +58 -11
- package/idb-queue/index.js +60 -11
- package/package.json +1 -1
- package/private-channel/index.js +49 -31
- package/private-channel/services/received-chunks.js +453 -245
- package/private-message/index.js +62 -17
- package/private-messenger/index.js +383 -76
- package/private-messenger/recovery/index.js +3 -1
- package/private-messenger/services/channel-state.js +132 -0
- package/private-messenger/services/storage-maintenance.js +324 -0
package/README.md
CHANGED
|
@@ -20,10 +20,12 @@ import { createPrivateMessenger } from 'libp2r2p/private-messenger'
|
|
|
20
20
|
const messenger = await createPrivateMessenger({
|
|
21
21
|
userSigner,
|
|
22
22
|
contentKeySigner,
|
|
23
|
+
offlineRecoverySeconds: 7 * 24 * 60 * 60,
|
|
23
24
|
channels: [{
|
|
24
25
|
signer: privateChannelSigner,
|
|
25
26
|
relays: ['wss://relay.example'],
|
|
26
|
-
mode: 'leecher'
|
|
27
|
+
mode: 'leecher',
|
|
28
|
+
offlineRecoverySeconds: 30 * 24 * 60 * 60
|
|
27
29
|
}],
|
|
28
30
|
onError: err => console.warn('private messenger failed', err)
|
|
29
31
|
})
|
|
@@ -126,27 +128,71 @@ one `['k', '3560']` tag, explicit matching `e` targets, and no `a` tags. A
|
|
|
126
128
|
regular NIP-09 kind `5` request signed by the outer event's private-channel key
|
|
127
129
|
must not delete a kind `3560` event, whether or not that event has an `s` tag.
|
|
128
130
|
|
|
129
|
-
###
|
|
131
|
+
### Storage Maintenance
|
|
130
132
|
|
|
131
133
|
While an outgoing private message is being assembled, the messenger keeps
|
|
132
134
|
encrypted envelope rows and router chunks in `sessionStorage`. They are
|
|
133
135
|
removed when the send finishes, but an interrupted browser operation can leave
|
|
134
136
|
them behind until cleanup runs.
|
|
135
137
|
|
|
136
|
-
`PrivateMessenger.init()`
|
|
137
|
-
`PrivateMessenger.
|
|
138
|
-
|
|
138
|
+
`PrivateMessenger.init()` awaits storage maintenance automatically. Call
|
|
139
|
+
`PrivateMessenger.maintainStorage()` during app startup when messenger
|
|
140
|
+
initialization may be delayed, such as while an account is locked:
|
|
139
141
|
|
|
140
142
|
```js
|
|
141
143
|
import { PrivateMessenger } from 'libp2r2p/private-messenger'
|
|
142
144
|
|
|
143
|
-
PrivateMessenger.
|
|
145
|
+
PrivateMessenger.maintainStorage().catch(console.warn)
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
Maintenance removes interrupted-send staging, expired receive chunks, and
|
|
149
|
+
storage belonging to inactive principal identities. It also
|
|
150
|
+
resumes any interrupted database-set deletion. An application does not need to
|
|
151
|
+
know database names or enumerate IndexedDB. Pass `temporaryStorageArea` only
|
|
152
|
+
when the messenger was configured to use a Storage area other than the default
|
|
153
|
+
`sessionStorage`.
|
|
154
|
+
|
|
155
|
+
Each principal signer owns internal message, recovery-seed, and channel-state
|
|
156
|
+
databases. The messenger updates their activity lease while it is open and
|
|
157
|
+
closes all handles in `await messenger.close()`. The complete set is removed
|
|
158
|
+
after 60 days without use, including messages that were never consumed.
|
|
159
|
+
Maintenance runs on every initialization and every six hours while a messenger
|
|
160
|
+
is active; failed deletions remain journaled and retry automatically.
|
|
161
|
+
|
|
162
|
+
Channel recovery state inside an otherwise active identity retains its separate
|
|
163
|
+
45-day stale-channel cleanup policy. Offline recovery defaults to seven days.
|
|
164
|
+
Set `offlineRecoverySeconds` on an individual channel to override the
|
|
165
|
+
messenger default for its recovery seeds, offline ranges, new outer-event
|
|
166
|
+
expiration, and new incomplete receive groups. Updating a channel applies a
|
|
167
|
+
shorter window immediately to its stored seeds and ranges; increasing it does
|
|
168
|
+
not recreate data already removed.
|
|
169
|
+
|
|
170
|
+
Set a channel's `offlineRecoverySeconds` to `0` to disable durable recovery for
|
|
171
|
+
that channel. The messenger then stores no recovery seeds, tracks no offline
|
|
172
|
+
ranges, contacts no seeders, publishes no seeder presence, and uses no recovery
|
|
173
|
+
mirror relays. Live delivery remains usable: new outer events retain the
|
|
174
|
+
private-channel two-day technical expiration and incomplete receive groups use
|
|
175
|
+
the one-hour technical TTL. Existing signed events and receive groups retain
|
|
176
|
+
the deadlines chosen when they were created.
|
|
177
|
+
|
|
178
|
+
Recovery metadata is separate from that temporary send staging. Per-channel
|
|
179
|
+
`lastSeenAt`, offline ranges, and related state are stored in IndexedDB. Raw
|
|
180
|
+
incomplete receive chunks are also stored in IndexedDB and share a 16 MiB
|
|
181
|
+
logical budget. Direct private-channel calls give each new group a one-hour
|
|
182
|
+
TTL; PrivateMessenger groups use the effective recovery window of their
|
|
183
|
+
channel, or one hour when durable recovery is disabled. The TTL is persisted
|
|
184
|
+
per group, so another caller opening the shared database or a later channel
|
|
185
|
+
configuration update cannot change it. Capacity
|
|
186
|
+
eviction removes whole
|
|
187
|
+
least-recently-used message groups so a partial group is never mistaken for a
|
|
188
|
+
complete one. `receivedChunkTtlMs`, `receivedChunkMaxBytes`, and
|
|
189
|
+
`receivedChunkIndexedDB` may be supplied to the private-channel APIs when an
|
|
190
|
+
embedding environment needs different limits or an injected IDB factory.
|
|
191
|
+
Legacy Web Storage recovery records are neither read nor migrated.
|
|
192
|
+
|
|
193
|
+
The recovery-seed queue has a shared 64 MiB logical budget by default and uses
|
|
194
|
+
FIFO eviction. A channel recovery duration is therefore a maximum retention
|
|
195
|
+
window, not a guarantee that every seed remains available until its deadline.
|
|
150
196
|
|
|
151
197
|
Signers are expected to expose the Nostr-style methods used by the messenger,
|
|
152
198
|
including `getPublicKey()`, `signEvent(event)`, and the NIP-44 v3 methods
|
|
@@ -154,7 +200,8 @@ needed by private channels. For double-DH content-key use, pass a
|
|
|
154
200
|
`contentKeySigner` or a signer implementation that handles content keys
|
|
155
201
|
internally.
|
|
156
202
|
|
|
157
|
-
Messages are stored in a bounded, durable IndexedDB queue until consumed
|
|
203
|
+
Messages are stored in a bounded, durable IndexedDB queue until consumed or
|
|
204
|
+
until the principal identity has been inactive for 60 days:
|
|
158
205
|
|
|
159
206
|
```js
|
|
160
207
|
async function handleMessages () {
|
package/idb-queue/index.js
CHANGED
|
@@ -7,6 +7,29 @@ const STATE_KEY = 'queue'
|
|
|
7
7
|
const DEFAULT_EVICTION_HEADROOM_RATIO = 0.1
|
|
8
8
|
const MAX_EVICTION_HEADROOM_BYTES = 64 * 1024 // 64 KiB
|
|
9
9
|
|
|
10
|
+
/*
|
|
11
|
+
IndexedDB schema, scoped per queue prefix:
|
|
12
|
+
|
|
13
|
+
database `${prefix}:idb-queue`, version managed additively
|
|
14
|
+
|
|
15
|
+
items, keyPath "position"
|
|
16
|
+
position integer ordering key between the queue head and tail
|
|
17
|
+
byteSize logical serialized size used by the queue capacity limit
|
|
18
|
+
item caller-provided queued value
|
|
19
|
+
|
|
20
|
+
items indexes
|
|
21
|
+
Declared by each createQueue() caller. A requested keyPath such as "status"
|
|
22
|
+
is stored as "item.status"; compound paths receive the same "item." prefix.
|
|
23
|
+
The database version is incremented when declared indexes need to be added.
|
|
24
|
+
|
|
25
|
+
state, keyPath "key"
|
|
26
|
+
key state record name, currently "queue"
|
|
27
|
+
head/tail half-open range containing allocated item positions
|
|
28
|
+
usedBytes sum of the queued items' logical byte sizes
|
|
29
|
+
|
|
30
|
+
The queue state record is absent while the queue is empty.
|
|
31
|
+
*/
|
|
32
|
+
|
|
10
33
|
function deferred () {
|
|
11
34
|
let resolve
|
|
12
35
|
let reject
|
|
@@ -251,6 +274,10 @@ export async function createQueue ({
|
|
|
251
274
|
const waiters = new Set()
|
|
252
275
|
let sessionMaxBytes = configuredMaxBytes
|
|
253
276
|
let revision = 0
|
|
277
|
+
let closed = false
|
|
278
|
+
let activeTransactions = 0
|
|
279
|
+
let closePromise = null
|
|
280
|
+
const closeWaiters = new Set()
|
|
254
281
|
|
|
255
282
|
function hasByteLimit () {
|
|
256
283
|
return Number.isFinite(sessionMaxBytes)
|
|
@@ -294,17 +321,27 @@ export async function createQueue ({
|
|
|
294
321
|
}
|
|
295
322
|
|
|
296
323
|
async function transaction (mode, work) {
|
|
297
|
-
|
|
298
|
-
|
|
324
|
+
if (closed) throw new Error('QUEUE_CLOSED')
|
|
325
|
+
activeTransactions++
|
|
299
326
|
try {
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
327
|
+
const tx = db.transaction([ITEMS_STORE, STATE_STORE], mode)
|
|
328
|
+
const done = transactionDone(tx)
|
|
329
|
+
try {
|
|
330
|
+
// Keep work limited to IndexedDB requests so the transaction stays active.
|
|
331
|
+
const value = await work(tx)
|
|
332
|
+
await done
|
|
333
|
+
return value
|
|
334
|
+
} catch (err) {
|
|
335
|
+
try { tx.abort() } catch {}
|
|
336
|
+
try { await done } catch {}
|
|
337
|
+
throw err
|
|
338
|
+
}
|
|
339
|
+
} finally {
|
|
340
|
+
activeTransactions--
|
|
341
|
+
if (!activeTransactions) {
|
|
342
|
+
for (const resolve of closeWaiters) resolve()
|
|
343
|
+
closeWaiters.clear()
|
|
344
|
+
}
|
|
308
345
|
}
|
|
309
346
|
}
|
|
310
347
|
|
|
@@ -748,6 +785,17 @@ export async function createQueue ({
|
|
|
748
785
|
for (const record of (await snapshotStoredItems()).reverse()) yield record.item
|
|
749
786
|
}
|
|
750
787
|
|
|
788
|
+
function close () {
|
|
789
|
+
if (closePromise) return closePromise
|
|
790
|
+
closed = true
|
|
791
|
+
wake()
|
|
792
|
+
closePromise = (activeTransactions
|
|
793
|
+
? new Promise(resolve => closeWaiters.add(resolve))
|
|
794
|
+
: Promise.resolve())
|
|
795
|
+
.then(() => { db.close() })
|
|
796
|
+
return closePromise
|
|
797
|
+
}
|
|
798
|
+
|
|
751
799
|
async function * storedItemsBy (indexName, query, { direction = 'next' } = {}) {
|
|
752
800
|
direction = validDirection(direction)
|
|
753
801
|
const records = await snapshot(async tx => {
|
|
@@ -788,6 +836,7 @@ export async function createQueue ({
|
|
|
788
836
|
getBy,
|
|
789
837
|
someBy,
|
|
790
838
|
removeBy,
|
|
791
|
-
storedItemsBy
|
|
839
|
+
storedItemsBy,
|
|
840
|
+
close
|
|
792
841
|
}
|
|
793
842
|
}
|
package/package.json
CHANGED
package/private-channel/index.js
CHANGED
|
@@ -566,8 +566,8 @@ async function decryptRouter ({ content, channelPubkey, channelSigner, channelRe
|
|
|
566
566
|
|
|
567
567
|
function readValueFromMap (map, key) {
|
|
568
568
|
if (!map || !key) return null
|
|
569
|
-
if (map instanceof Map) return map.get(key)
|
|
570
|
-
return map[key]
|
|
569
|
+
if (map instanceof Map) return map.get(key) ?? null
|
|
570
|
+
return map[key] ?? null
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
function privateChannelPubkeyList ({ privateChannelPubkey, privateChannelPubkeys }) {
|
|
@@ -669,22 +669,26 @@ function createProcessor ({
|
|
|
669
669
|
onContentKeyUsage,
|
|
670
670
|
onError,
|
|
671
671
|
receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS,
|
|
672
|
+
receivedChunkTtlMsByPubkey,
|
|
672
673
|
receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES,
|
|
673
|
-
|
|
674
|
+
receivedChunkIndexedDB = globalThis.indexedDB,
|
|
674
675
|
ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS,
|
|
675
676
|
ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES
|
|
676
677
|
}) {
|
|
677
678
|
const receivedChunks = createReceivedChunkStore({
|
|
678
679
|
ttlMs: receivedChunkTtlMs,
|
|
679
680
|
maxBytes: receivedChunkMaxBytes,
|
|
680
|
-
|
|
681
|
+
indexedDB: receivedChunkIndexedDB
|
|
681
682
|
})
|
|
682
683
|
const ignoredGroups = createTtlSet({
|
|
683
684
|
ttlMs: ignoredGroupTtlMs,
|
|
684
685
|
maxEntries: ignoredGroupMaxEntries
|
|
685
686
|
})
|
|
687
|
+
const storeReady = receivedChunks.ready()
|
|
688
|
+
storeReady.catch(() => {})
|
|
686
689
|
|
|
687
|
-
|
|
690
|
+
async function processOuterEvent (outer) {
|
|
691
|
+
await storeReady
|
|
688
692
|
let groupKey = ''
|
|
689
693
|
try {
|
|
690
694
|
const channelPubkey = outer.pubkey || await privateChannelSigner?.getPublicKey?.()
|
|
@@ -692,6 +696,7 @@ function createProcessor ({
|
|
|
692
696
|
const channelReaderSigner = readSignerFromMap(privateChannelReaderSignersByPubkey, channelPubkey) || privateChannelReaderSigner || channelSigner
|
|
693
697
|
const channelReaderPubkey = readValueFromMap(privateChannelReaderPubkeysByPubkey, channelPubkey) || privateChannelReaderPubkey || channelPubkey
|
|
694
698
|
const channelMode = readValueFromMap(modeByPubkey, channelPubkey) || mode
|
|
699
|
+
const channelReceivedChunkTtlMs = readValueFromMap(receivedChunkTtlMsByPubkey, channelPubkey) ?? receivedChunkTtlMs
|
|
695
700
|
if (!channelPubkey) throw new Error('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
|
|
696
701
|
|
|
697
702
|
const decrypted = await decryptRouter({
|
|
@@ -707,14 +712,15 @@ function createProcessor ({
|
|
|
707
712
|
groupKey = receivedChunks.groupKeyFor(channelPubkey, nymCarrierGroupId(carrier))
|
|
708
713
|
if (ignoredGroups.has(groupKey)) return
|
|
709
714
|
|
|
710
|
-
const meta = receivedChunks.put({
|
|
715
|
+
const meta = await receivedChunks.put({
|
|
711
716
|
channelPubkey,
|
|
712
717
|
routerPubkey: nymCarrierGroupId(carrier),
|
|
713
718
|
index,
|
|
714
719
|
total,
|
|
715
|
-
|
|
720
|
+
contentBytes: encoder.encode(JSON.stringify(carrier)),
|
|
721
|
+
ttlMs: channelReceivedChunkTtlMs
|
|
716
722
|
})
|
|
717
|
-
const status = receivedChunks.status(meta)
|
|
723
|
+
const status = await receivedChunks.status(meta)
|
|
718
724
|
onChunk?.({
|
|
719
725
|
outer,
|
|
720
726
|
nymCarrier: carrier,
|
|
@@ -726,7 +732,7 @@ function createProcessor ({
|
|
|
726
732
|
})
|
|
727
733
|
if (status.received < total) return
|
|
728
734
|
|
|
729
|
-
const carriers = receivedChunks.readChunkContents(groupKey).map(raw => JSON.parse(raw))
|
|
735
|
+
const carriers = (await receivedChunks.readChunkContents(groupKey)).map(raw => JSON.parse(raw))
|
|
730
736
|
const event = eventFromNymCarriers(carriers)
|
|
731
737
|
const shouldSeed = storesRecoverySeeds(channelMode)
|
|
732
738
|
if (shouldSeed) {
|
|
@@ -744,7 +750,7 @@ function createProcessor ({
|
|
|
744
750
|
carriers,
|
|
745
751
|
channelPubkey
|
|
746
752
|
})
|
|
747
|
-
receivedChunks.removeGroup(groupKey)
|
|
753
|
+
await receivedChunks.removeGroup(groupKey)
|
|
748
754
|
return
|
|
749
755
|
}
|
|
750
756
|
const router = decrypted
|
|
@@ -757,14 +763,15 @@ function createProcessor ({
|
|
|
757
763
|
|
|
758
764
|
const imkcPubkey = readImkcTag(router)
|
|
759
765
|
assertValidRouterImkcProof({ router, senderPubkey, imkcPubkey, imkcProof: readImkcProof(router) })
|
|
760
|
-
const meta = receivedChunks.put({
|
|
766
|
+
const meta = await receivedChunks.put({
|
|
761
767
|
channelPubkey,
|
|
762
768
|
routerPubkey: router.pubkey,
|
|
763
769
|
index,
|
|
764
770
|
total,
|
|
765
|
-
|
|
771
|
+
contentBytes: base64ToBytes(router.content),
|
|
772
|
+
ttlMs: channelReceivedChunkTtlMs
|
|
766
773
|
})
|
|
767
|
-
const status = receivedChunks.status(meta)
|
|
774
|
+
const status = await receivedChunks.status(meta)
|
|
768
775
|
onChunk?.({
|
|
769
776
|
outer,
|
|
770
777
|
router,
|
|
@@ -826,15 +833,15 @@ function createProcessor ({
|
|
|
826
833
|
if (event && !mustScanWholeBundle) {
|
|
827
834
|
await onEvent?.(event, outer, { router: joinedRouter(router), channelPubkey })
|
|
828
835
|
ignoredGroups.add(groupKey)
|
|
829
|
-
receivedChunks.removeGroup(groupKey)
|
|
836
|
+
await receivedChunks.removeGroup(groupKey)
|
|
830
837
|
return
|
|
831
838
|
}
|
|
832
839
|
|
|
833
840
|
if (!drained.complete) return
|
|
834
841
|
|
|
835
842
|
const receiverPubkeys = drained.meta?.receiverPubkeys || []
|
|
836
|
-
const content = shouldSeed ? receivedChunks.readEnvelopeBundleContent(groupKey) : ''
|
|
837
|
-
const jsonl = shouldSeed ? receivedChunks.readEnvelopeBundleText(groupKey) : ''
|
|
843
|
+
const content = shouldSeed ? await receivedChunks.readEnvelopeBundleContent(groupKey) : ''
|
|
844
|
+
const jsonl = shouldSeed ? await receivedChunks.readEnvelopeBundleText(groupKey) : ''
|
|
838
845
|
const completeRouter = joinedRouter(router, content)
|
|
839
846
|
|
|
840
847
|
emitSentContentKeyUsage({
|
|
@@ -848,15 +855,18 @@ function createProcessor ({
|
|
|
848
855
|
if (shouldSeed) await onSeedEvent?.({ recordType: 'routerRow_v1', outer, router: completeRouter, channelPubkey, jsonl, innerEventIdsByRowIndex })
|
|
849
856
|
if (event) await onEvent?.(event, outer, { router: completeRouter, channelPubkey, jsonl })
|
|
850
857
|
|
|
851
|
-
receivedChunks.removeGroup(groupKey)
|
|
858
|
+
await receivedChunks.removeGroup(groupKey)
|
|
852
859
|
} catch (err) {
|
|
853
860
|
if (shouldIgnoreGroupError(err) && groupKey) {
|
|
854
861
|
ignoredGroups.add(groupKey)
|
|
855
|
-
receivedChunks.removeGroup(groupKey)
|
|
862
|
+
await receivedChunks.removeGroup(groupKey).catch(() => {})
|
|
856
863
|
}
|
|
857
864
|
onError?.(err)
|
|
858
865
|
}
|
|
859
866
|
}
|
|
867
|
+
|
|
868
|
+
processOuterEvent.close = () => receivedChunks.close()
|
|
869
|
+
return processOuterEvent
|
|
860
870
|
}
|
|
861
871
|
|
|
862
872
|
function shouldIgnoreGroupError (err) {
|
|
@@ -875,13 +885,14 @@ function shouldIgnoreGroupError (err) {
|
|
|
875
885
|
'INVALID_RECIPIENT_ENVELOPE',
|
|
876
886
|
'INVALID_SIGNED_INNER_EVENT',
|
|
877
887
|
'MISSING_PAYLOAD_ENVELOPE',
|
|
888
|
+
'RECEIVED_CHUNK_GROUP_TOO_LARGE',
|
|
878
889
|
'MISMATCHED_NYM_CARRIER_CHUNKS',
|
|
879
890
|
'MISSING_NYM_CARRIER_CHUNK',
|
|
880
891
|
'MISSING_NYM_CARRIER_ID'
|
|
881
892
|
].includes(err?.message)
|
|
882
893
|
}
|
|
883
894
|
|
|
884
|
-
export async function fetch ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since, until, limit, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES,
|
|
895
|
+
export async function fetch ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since, until, limit, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES, receivedChunkIndexedDB = globalThis.indexedDB, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _getEvents = getEvents }) {
|
|
885
896
|
if (!relays?.length) throw new Error('NO_RELAYS')
|
|
886
897
|
const authors = privateChannelPubkeyList({ privateChannelPubkey, privateChannelPubkeys })
|
|
887
898
|
const filter = { kinds: [PRIVATE_BROADCAST_KIND] }
|
|
@@ -895,12 +906,16 @@ export async function fetch ({ receiverSigner, iykcSigner, privateChannelSigner
|
|
|
895
906
|
timeoutAfterFirstEose: null
|
|
896
907
|
})
|
|
897
908
|
events.sort((a, b) => a.created_at - b.created_at)
|
|
898
|
-
const processOuterEvent = createProcessor({ receiverSigner, iykcSigner, privateChannelSigner, privateChannelSignersByPubkey, privateChannelReaderSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, receiverPubkey, mode, modeByPubkey, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, receivedChunkTtlMs, receivedChunkMaxBytes,
|
|
899
|
-
|
|
900
|
-
|
|
909
|
+
const processOuterEvent = createProcessor({ receiverSigner, iykcSigner, privateChannelSigner, privateChannelSignersByPubkey, privateChannelReaderSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, receiverPubkey, mode, modeByPubkey, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, receivedChunkTtlMs, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes, receivedChunkIndexedDB, ignoredGroupTtlMs, ignoredGroupMaxEntries })
|
|
910
|
+
try {
|
|
911
|
+
for (const event of events) await processOuterEvent(event)
|
|
912
|
+
return events
|
|
913
|
+
} finally {
|
|
914
|
+
processOuterEvent.close()
|
|
915
|
+
}
|
|
901
916
|
}
|
|
902
917
|
|
|
903
|
-
export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since = nowSeconds() - 5, limit, liveOnly = false, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES,
|
|
918
|
+
export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since = nowSeconds() - 5, limit, liveOnly = false, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES, receivedChunkIndexedDB = globalThis.indexedDB, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _liveEventsGenerator = getLiveEventsGenerator, _eventsFeedGenerator = getEventsFeedGenerator }) {
|
|
904
919
|
if (!relays?.length) throw new Error('NO_RELAYS')
|
|
905
920
|
if (receiverSigner && !receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
|
|
906
921
|
if (!privateChannelReaderSigner && !privateChannelReaderSignersByPubkey && !privateChannelSigner && !privateChannelSignersByPubkey) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
|
|
@@ -909,17 +924,17 @@ export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner =
|
|
|
909
924
|
const filter = { kinds: [PRIVATE_BROADCAST_KIND], since }
|
|
910
925
|
if (authors.length) filter.authors = authors
|
|
911
926
|
if (limit != null) filter.limit = limit
|
|
912
|
-
const processOuterEvent = createProcessor({ receiverSigner, iykcSigner, privateChannelSigner, privateChannelSignersByPubkey, privateChannelReaderSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, receiverPubkey, mode, modeByPubkey, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, receivedChunkTtlMs, receivedChunkMaxBytes,
|
|
927
|
+
const processOuterEvent = createProcessor({ receiverSigner, iykcSigner, privateChannelSigner, privateChannelSignersByPubkey, privateChannelReaderSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, receiverPubkey, mode, modeByPubkey, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, receivedChunkTtlMs, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes, receivedChunkIndexedDB, ignoredGroupTtlMs, ignoredGroupMaxEntries })
|
|
913
928
|
const controller = new AbortController()
|
|
914
929
|
const events = liveOnly
|
|
915
930
|
? _liveEventsGenerator(filter, relays, {
|
|
916
|
-
|
|
917
|
-
|
|
931
|
+
signal: controller.signal
|
|
932
|
+
})
|
|
918
933
|
: _eventsFeedGenerator(filter, relays, {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
934
|
+
signal: controller.signal,
|
|
935
|
+
timeout: 5000,
|
|
936
|
+
timeoutAfterFirstEose: null
|
|
937
|
+
})
|
|
923
938
|
|
|
924
939
|
async function consumeEvents () {
|
|
925
940
|
try {
|
|
@@ -929,14 +944,17 @@ export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner =
|
|
|
929
944
|
}
|
|
930
945
|
} catch (error) {
|
|
931
946
|
if (!controller.signal.aborted && error?.message !== 'Aborted') onError?.(error)
|
|
947
|
+
} finally {
|
|
948
|
+
processOuterEvent.close()
|
|
932
949
|
}
|
|
933
950
|
}
|
|
934
951
|
|
|
935
|
-
consumeEvents()
|
|
952
|
+
const consumePromise = consumeEvents()
|
|
936
953
|
|
|
937
954
|
return {
|
|
938
955
|
close () {
|
|
939
956
|
controller.abort()
|
|
957
|
+
return consumePromise
|
|
940
958
|
}
|
|
941
959
|
}
|
|
942
960
|
}
|