libp2r2p 0.0.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.
Files changed (63) hide show
  1. package/AGENTS.md +20 -0
  2. package/README.md +129 -0
  3. package/base16/index.js +14 -0
  4. package/base64/index.js +29 -0
  5. package/content-key/event/index.js +85 -0
  6. package/content-key/index.js +1 -0
  7. package/content-key/services/iykc-proof.js +129 -0
  8. package/double-dh/index.js +166 -0
  9. package/ecdh/index.js +8 -0
  10. package/idb/index.js +64 -0
  11. package/idb-queue/index.js +793 -0
  12. package/index.js +21 -0
  13. package/key/index.js +139 -0
  14. package/network/index.js +68 -0
  15. package/nip44-v3/index.js +175 -0
  16. package/nip46/constants/index.js +5 -0
  17. package/nip46/helpers/frame.js +51 -0
  18. package/nip46/helpers/url.js +96 -0
  19. package/nip46/index.js +5 -0
  20. package/nip46/services/bunker-signer.js +49 -0
  21. package/nip46/services/client.js +252 -0
  22. package/nip46/services/server-session.js +225 -0
  23. package/nip46/services/transport.js +265 -0
  24. package/package.json +49 -0
  25. package/private-channel/constants/index.js +5 -0
  26. package/private-channel/helpers/chunk-size.js +61 -0
  27. package/private-channel/helpers/chunks.js +282 -0
  28. package/private-channel/helpers/event.js +68 -0
  29. package/private-channel/index.js +942 -0
  30. package/private-channel/services/received-chunks.js +398 -0
  31. package/private-message/index.js +672 -0
  32. package/private-messenger/constants/index.js +1 -0
  33. package/private-messenger/index.js +1431 -0
  34. package/private-messenger/recovery/index.js +316 -0
  35. package/relay/constants/index.js +18 -0
  36. package/relay/helpers/hll.js +62 -0
  37. package/relay/helpers/publish.js +100 -0
  38. package/relay/helpers/routing.js +36 -0
  39. package/relay/helpers/timer.js +4 -0
  40. package/relay/index.js +4 -0
  41. package/relay/services/query.js +224 -0
  42. package/relay/services/relay-connection.js +156 -0
  43. package/relay/services/relay-pool.js +908 -0
  44. package/temporary-storage/index.js +85 -0
  45. package/tests/base16.test.js +19 -0
  46. package/tests/base64.test.js +18 -0
  47. package/tests/content-key-event.test.js +48 -0
  48. package/tests/fixtures/nip44v3-vectors.json +1 -0
  49. package/tests/helpers/test-signer.js +185 -0
  50. package/tests/idb-queue.test.js +153 -0
  51. package/tests/idb.test.js +91 -0
  52. package/tests/nip44-v3.test.js +73 -0
  53. package/tests/nip46.test.js +668 -0
  54. package/tests/private-channel.test.js +1899 -0
  55. package/tests/private-message.test.js +460 -0
  56. package/tests/private-messenger.test.js +1715 -0
  57. package/tests/queries.test.js +101 -0
  58. package/tests/queue-parity.test.js +105 -0
  59. package/tests/relay-hll.test.js +32 -0
  60. package/tests/relay-pool.test.js +2067 -0
  61. package/tests/temporary-storage.test.js +89 -0
  62. package/tests/web-storage-queue.test.js +480 -0
  63. package/web-storage-queue/index.js +652 -0
@@ -0,0 +1,942 @@
1
+ import { generateSecretKey, getPublicKey, finalizeEvent, getEventHash, validateEvent, verifyEvent } from 'nostr-tools'
2
+ import { bytesToBase64, base64ToBytes } from '../base64/index.js'
3
+ import { hexToBytes } from '../base16/index.js'
4
+ import { makeContentKeyEventForPubkey, parseContentKeyEvent, verifyContentKeyProof, verifyIykcProof } from '../content-key/event/index.js'
5
+ import { getIykcProofs } from '../content-key/index.js'
6
+ import * as nip44v3 from '../nip44-v3/index.js'
7
+ import { relayPool } from '../relay/index.js'
8
+ import { JSONL_CHUNK_BYTES, NYM_CARRIER_CHUNK_CHARS } from './helpers/chunk-size.js'
9
+ import {
10
+ cleanupChunks,
11
+ cleanupEnvelopeRows,
12
+ decodeChunkLines,
13
+ prepareEnvelopeRows,
14
+ preparedRowIndexesForReceivers,
15
+ readChunkContent,
16
+ receiverPubkeys,
17
+ receiverPubkeysWithoutContentKeys,
18
+ writeChunksFromPreparedRows
19
+ } from './helpers/chunks.js'
20
+ import { EXPIRATION_SECONDS, MAX_EVENT_BYTES, NYM_CARRIER_KIND, PRIVATE_BROADCAST_KIND, ROUTER_KIND } from './constants/index.js'
21
+ import { eventByteLength, hasImkcTag, makeNymCarrierEvent, makeRouterEvent, nowSeconds, readChunkTag, readIdTag, readImkcProof, readImkcTag, readReceiverTag, readSenderTag } from './helpers/event.js'
22
+ import { createReceivedChunkStore, DEFAULT_RECEIVED_CHUNK_MAX_BYTES, DEFAULT_RECEIVED_CHUNK_TTL_MS } from './services/received-chunks.js'
23
+
24
+ export { EXPIRATION_SECONDS, MAX_EVENT_BYTES, NYM_CARRIER_KIND, PRIVATE_BROADCAST_KIND, ROUTER_KIND } from './constants/index.js'
25
+
26
+ const DEFAULT_IGNORED_GROUP_TTL_MS = 30 * 60 * 1000
27
+ const DEFAULT_IGNORED_GROUP_MAX_ENTRIES = 5000
28
+ const HEX_SECKEY = /^[0-9a-f]{64}$/i
29
+ const HEX_PUBKEY = /^[0-9a-f]{64}$/i
30
+ const encoder = new TextEncoder()
31
+ const decoder = new TextDecoder()
32
+ const NIP44_V3_SCOPE = ''
33
+
34
+ const sendToRelays = (...args) => relayPool.sendEvent(...args)
35
+ const getEvents = (...args) => relayPool.getEvents(...args)
36
+ const getLiveEventsGenerator = (...args) => relayPool.getLiveEventsGenerator(...args)
37
+ const getEventsFeedGenerator = (...args) => relayPool.getEventsFeedGenerator(...args)
38
+
39
+ function uniq (values) {
40
+ return [...new Set((values || []).filter(Boolean))]
41
+ }
42
+
43
+ function normalizeDeletionPubkey (deletionPubkey) {
44
+ if (deletionPubkey === undefined) return undefined
45
+ if (typeof deletionPubkey !== 'string' || !HEX_PUBKEY.test(deletionPubkey)) {
46
+ throw new Error('INVALID_DELETION_PUBKEY')
47
+ }
48
+ return deletionPubkey.toLowerCase()
49
+ }
50
+
51
+ function privateBroadcastTags ({ deletionPubkey, createdAt, expirationSeconds }) {
52
+ return [
53
+ ...(deletionPubkey ? [['s', deletionPubkey]] : []),
54
+ ['expiration', String(createdAt + expirationSeconds)]
55
+ ]
56
+ }
57
+
58
+ export function getJsonlChunkByteSize () {
59
+ return JSONL_CHUNK_BYTES
60
+ }
61
+
62
+ export function getNymCarrierChunkSize () {
63
+ return NYM_CARRIER_CHUNK_CHARS
64
+ }
65
+
66
+ function textToBase64 (text) {
67
+ return bytesToBase64(encoder.encode(text))
68
+ }
69
+
70
+ function base64ToText (b64) {
71
+ return decoder.decode(base64ToBytes(b64))
72
+ }
73
+
74
+ async function nip44v3EncryptText (signer, peerPubkey, kind, plaintext) {
75
+ return signer.nip44v3Encrypt(peerPubkey, kind, NIP44_V3_SCOPE, textToBase64(plaintext))
76
+ }
77
+
78
+ async function nip44v3DecryptText (signer, peerPubkey, kind, ciphertext) {
79
+ return base64ToText(await signer.nip44v3Decrypt(peerPubkey, kind, NIP44_V3_SCOPE, ciphertext))
80
+ }
81
+
82
+ function storesRecoverySeeds (mode) {
83
+ return mode === 'seeder' || mode === 'watchtower'
84
+ }
85
+
86
+ async function makeImkcProof ({ senderSigner, senderPubkey, imkcPubkey }) {
87
+ const event = await makeContentKeyEventForPubkey({ userSigner: senderSigner, contentPubkey: imkcPubkey })
88
+ const parsed = parseContentKeyEvent(event)
89
+ if (
90
+ event.pubkey !== senderPubkey ||
91
+ parsed?.iykcPubkey !== imkcPubkey ||
92
+ !verifyContentKeyProof({ ownerPubkey: senderPubkey, contentPubkey: imkcPubkey, proof: parsed?.iykcProof })
93
+ ) throw new Error('INVALID_IMKC_PROOF')
94
+ return parsed.iykcProof
95
+ }
96
+
97
+ async function prepareRoutedMessage ({ senderSigner, imkcSigner, privateChannelSigner = senderSigner, privateChannelReaderPubkey, receivers, event, temporaryStorageArea, _getIykcProofs = getIykcProofs }) {
98
+ if (!senderSigner?.getPublicKey) throw new Error('SENDER_SIGNER_REQUIRED')
99
+ if (!senderSigner?.nip44EncryptDoubleDH && !senderSigner?.nip44v3Encrypt) throw new Error('SIGNER_NIP44V3_ENCRYPT_UNSUPPORTED')
100
+ if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new Error('PRIVATE_CHANNEL_SIGNER_REQUIRED')
101
+ if (!Array.isArray(receivers) || !receivers.length) throw new Error('NO_RECEIVERS')
102
+
103
+ const senderPubkey = await senderSigner.getPublicKey()
104
+ const useDoubleDh = typeof senderSigner.nip44EncryptDoubleDH === 'function'
105
+ const channelPubkey = await privateChannelSigner.getPublicKey()
106
+ const channelReaderPubkey = privateChannelReaderPubkey || channelPubkey
107
+ const receiverContentKeys = useDoubleDh ? await _getIykcProofs(receiverPubkeysWithoutContentKeys(receivers)) : {}
108
+ const preparedRows = await prepareEnvelopeRows({
109
+ senderSigner,
110
+ imkcSigner: useDoubleDh ? imkcSigner : null,
111
+ receivers,
112
+ receiverContentKeys,
113
+ event,
114
+ rowScope: channelPubkey,
115
+ temporaryStorageArea
116
+ })
117
+ const imkcPubkey = preparedRows.ownContentPubkey || ''
118
+ const imkcProof = imkcPubkey ? await makeImkcProof({ senderSigner, senderPubkey, imkcPubkey }) : ''
119
+ return {
120
+ senderPubkey,
121
+ channelPubkey,
122
+ channelReaderPubkey,
123
+ preparedRows,
124
+ imkcPubkey,
125
+ imkcProof
126
+ }
127
+ }
128
+
129
+ async function * wrapPreparedEvents ({ privateChannelSigner, receivers, receiverTag, deletionPubkey, expirationSeconds = EXPIRATION_SECONDS, context }) {
130
+ const routerSeckey = generateSecretKey()
131
+ const routerPubkey = getPublicKey(routerSeckey)
132
+ const receiverPubkeyList = receiverPubkeys(receivers)
133
+ const routerReceiverTag = receiverTag ?? (receiverPubkeyList.length === 1 ? receiverPubkeyList[0] : '')
134
+ const rowIndexes = preparedRowIndexesForReceivers(context.preparedRows, receivers)
135
+ const {
136
+ id,
137
+ total
138
+ } = writeChunksFromPreparedRows(context.preparedRows, rowIndexes)
139
+ const temporaryStorage = context.preparedRows.temporaryStorage
140
+
141
+ try {
142
+ for (let index = 0; index < total; index++) {
143
+ const content = readChunkContent(id, index, temporaryStorage)
144
+ const router = finalizeEvent(makeRouterEvent({
145
+ pubkey: routerPubkey,
146
+ senderPubkey: context.senderPubkey,
147
+ imkcPubkey: context.imkcPubkey,
148
+ imkcProof: context.imkcProof,
149
+ receiverPubkey: routerReceiverTag,
150
+ chunkIndex: index,
151
+ chunkTotal: total,
152
+ content
153
+ }), routerSeckey)
154
+ const createdAt = nowSeconds()
155
+ const outer = await privateChannelSigner.signEvent({
156
+ kind: PRIVATE_BROADCAST_KIND,
157
+ created_at: createdAt,
158
+ tags: privateBroadcastTags({ deletionPubkey, createdAt, expirationSeconds }),
159
+ content: await nip44v3EncryptText(privateChannelSigner, context.channelReaderPubkey, PRIVATE_BROADCAST_KIND, JSON.stringify(router))
160
+ })
161
+ if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new Error('EVENT_TOO_LARGE')
162
+ yield outer
163
+ }
164
+ } finally {
165
+ cleanupChunks(id, total, temporaryStorage)
166
+ }
167
+ }
168
+
169
+ // Streaming version of wrapEvent
170
+ export async function * wrapEvents ({ senderSigner, imkcSigner, privateChannelSigner = senderSigner, privateChannelReaderPubkey, receivers, receiverTag, deletionPubkey, event, expirationSeconds = EXPIRATION_SECONDS, temporaryStorageArea, _getIykcProofs = getIykcProofs }) {
171
+ const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
172
+ const context = await prepareRoutedMessage({
173
+ senderSigner,
174
+ imkcSigner,
175
+ privateChannelSigner,
176
+ privateChannelReaderPubkey,
177
+ receivers,
178
+ event,
179
+ temporaryStorageArea,
180
+ _getIykcProofs
181
+ })
182
+ try {
183
+ yield * wrapPreparedEvents({ privateChannelSigner, receivers, receiverTag, deletionPubkey: normalizedDeletionPubkey, expirationSeconds, context })
184
+ } finally {
185
+ cleanupEnvelopeRows(context.preparedRows)
186
+ }
187
+ }
188
+
189
+ export async function wrapEvent (options) {
190
+ const events = []
191
+ for await (const event of wrapEvents(options)) events.push(event)
192
+ return events
193
+ }
194
+
195
+ export async function * wrapNymEvents ({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey, event, expirationSeconds = EXPIRATION_SECONDS }) {
196
+ if (!nymSigner?.getPublicKey || !nymSigner?.signEvent) throw new Error('NYM_SIGNER_REQUIRED')
197
+ if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new Error('PRIVATE_CHANNEL_SIGNER_REQUIRED')
198
+ const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
199
+
200
+ const nymPubkey = await nymSigner.getPublicKey()
201
+ const channelPubkey = await privateChannelSigner.getPublicKey()
202
+ const channelReaderPubkey = privateChannelReaderPubkey || channelPubkey
203
+ const wireEvent = isSignedEvent(event)
204
+ ? assertValidSignedInnerEvent(event)
205
+ : wireNymRumor({ ...event, created_at: event?.created_at !== undefined ? event.created_at : nowSeconds() })
206
+ const innerEvent = isSignedEvent(wireEvent) ? wireEvent : normalizeNymRumor(wireEvent, nymPubkey)
207
+ const encoded = bytesToBase64(encoder.encode(JSON.stringify(wireEvent)))
208
+ const total = Math.max(1, Math.ceil(encoded.length / NYM_CARRIER_CHUNK_CHARS))
209
+ const carrierCreatedAt = nowSeconds()
210
+
211
+ for (let index = 0; index < total; index++) {
212
+ const carrier = assertValidNymCarrierEvent(await nymSigner.signEvent(makeNymCarrierEvent({
213
+ innerId: innerEvent.id,
214
+ chunkIndex: index,
215
+ chunkTotal: total,
216
+ content: encoded.slice(index * NYM_CARRIER_CHUNK_CHARS, (index + 1) * NYM_CARRIER_CHUNK_CHARS),
217
+ createdAt: carrierCreatedAt
218
+ })))
219
+ const createdAt = nowSeconds()
220
+ const outer = await privateChannelSigner.signEvent({
221
+ kind: PRIVATE_BROADCAST_KIND,
222
+ created_at: createdAt,
223
+ tags: privateBroadcastTags({ deletionPubkey: normalizedDeletionPubkey, createdAt, expirationSeconds }),
224
+ content: await nip44v3EncryptText(privateChannelSigner, channelReaderPubkey, PRIVATE_BROADCAST_KIND, JSON.stringify(carrier))
225
+ })
226
+ if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new Error('EVENT_TOO_LARGE')
227
+ yield outer
228
+ }
229
+ }
230
+
231
+ export async function wrapNymEvent (options) {
232
+ const events = []
233
+ for await (const event of wrapNymEvents(options)) events.push(event)
234
+ return events
235
+ }
236
+
237
+ function joinedRouter (router, content = '') {
238
+ return {
239
+ ...router,
240
+ content,
241
+ tags: router.tags.filter(t => t[0] !== 'c').concat([['c', '0', '1']])
242
+ }
243
+ }
244
+
245
+ function parsePayloadEnvelope (line, index = 0) {
246
+ const record = JSON.parse(line)
247
+ if (!Array.isArray(record) || record.length !== 1 || typeof record[0] !== 'string') throw new Error('INVALID_PAYLOAD_ENVELOPE')
248
+ return { index, type: 'payload', ciphertext: record[0] }
249
+ }
250
+
251
+ function parseRecipientEnvelope (line, index = 0) {
252
+ const record = JSON.parse(line)
253
+ if (!Array.isArray(record) || (record.length !== 2 && record.length !== 4)) throw new Error('INVALID_RECIPIENT_ENVELOPE')
254
+ const [receiverPubkey, ciphertext, iykcPubkey = '', iykcProof = ''] = record
255
+ return { index, receiverPubkey, ciphertext, iykcPubkey, iykcProof }
256
+ }
257
+
258
+ function isSignedEvent (event) {
259
+ return Object.prototype.hasOwnProperty.call(event || {}, 'sig')
260
+ }
261
+
262
+ function assertValidSignedInnerEvent (event) {
263
+ if (!validateEvent(event) || event.id !== getEventHash(event) || !verifyEvent(event)) {
264
+ throw new Error('INVALID_SIGNED_INNER_EVENT')
265
+ }
266
+ return event
267
+ }
268
+
269
+ function normalizeNymRumor (event, pubkey) {
270
+ const normalized = { ...event, pubkey }
271
+ if (!validateEvent(normalized)) throw new Error('INVALID_NYM_RUMOR')
272
+ return { ...normalized, id: getEventHash(normalized) }
273
+ }
274
+
275
+ function wireNymRumor (event = {}) {
276
+ return {
277
+ kind: event.kind,
278
+ tags: event.tags,
279
+ content: event.content,
280
+ created_at: event.created_at
281
+ }
282
+ }
283
+
284
+ function assertValidNymCarrierEvent (carrier) {
285
+ if (!validateEvent(carrier) || carrier.id !== getEventHash(carrier) || !verifyEvent(carrier)) {
286
+ throw new Error('INVALID_NYM_CARRIER')
287
+ }
288
+ if (carrier.kind !== NYM_CARRIER_KIND) throw new Error('INVALID_NYM_CARRIER_KIND')
289
+ if (!readIdTag(carrier)) throw new Error('MISSING_NYM_CARRIER_ID')
290
+ readChunkTag(carrier)
291
+ return carrier
292
+ }
293
+
294
+ function nymCarrierGroupId (carrier) {
295
+ return `nym:${carrier.pubkey}:${readIdTag(carrier)}:${readChunkTag(carrier).total}`
296
+ }
297
+
298
+ function validateNymCarriers (carriers) {
299
+ if (!Array.isArray(carriers) || !carriers.length) throw new Error('NYM_CARRIERS_REQUIRED')
300
+ const chunks = []
301
+ let nymPubkey = ''
302
+ let innerId = ''
303
+ let total = 0
304
+
305
+ for (const carrier of carriers) {
306
+ assertValidNymCarrierEvent(carrier)
307
+ const nextInnerId = readIdTag(carrier)
308
+ const { index, total: nextTotal } = readChunkTag(carrier)
309
+ if (!nymPubkey) {
310
+ nymPubkey = carrier.pubkey
311
+ innerId = nextInnerId
312
+ total = nextTotal
313
+ }
314
+ if (carrier.pubkey !== nymPubkey || nextInnerId !== innerId || nextTotal !== total) {
315
+ throw new Error('MISMATCHED_NYM_CARRIER_CHUNKS')
316
+ }
317
+ if (chunks[index] !== undefined) throw new Error('DUPLICATE_NYM_CARRIER_CHUNK')
318
+ chunks[index] = carrier.content
319
+ }
320
+
321
+ if (chunks.length !== total) throw new Error('MISSING_NYM_CARRIER_CHUNK')
322
+ for (let index = 0; index < total; index++) {
323
+ if (chunks[index] == null) throw new Error('MISSING_NYM_CARRIER_CHUNK')
324
+ }
325
+ return { nymPubkey, innerId, content: chunks.join('') }
326
+ }
327
+
328
+ export function eventFromNymCarriers (carriers) {
329
+ const { nymPubkey, innerId, content } = validateNymCarriers(carriers)
330
+ let parsed
331
+ try {
332
+ parsed = JSON.parse(decoder.decode(base64ToBytes(content)))
333
+ } catch {
334
+ throw new Error('INVALID_NYM_CARRIER_PAYLOAD')
335
+ }
336
+
337
+ if (isSignedEvent(parsed)) {
338
+ const event = assertValidSignedInnerEvent(parsed)
339
+ if (event.id !== innerId) throw new Error('INVALID_NYM_CARRIER_INNER_ID')
340
+ return event
341
+ }
342
+
343
+ const event = normalizeNymRumor(parsed, nymPubkey)
344
+ if (event.id !== innerId) throw new Error('INVALID_NYM_CARRIER_INNER_ID')
345
+ return event
346
+ }
347
+
348
+ function assertValidEnvelopeIykcProof (envelope) {
349
+ if (!envelope.iykcPubkey) return
350
+ if (!verifyIykcProof({
351
+ receiverPubkey: envelope.receiverPubkey,
352
+ iykcPubkey: envelope.iykcPubkey,
353
+ iykcProof: envelope.iykcProof
354
+ })) throw new Error('INVALID_IYKC_PROOF')
355
+ }
356
+
357
+ function assertValidRouterImkcProof ({ router, senderPubkey, imkcPubkey, imkcProof }) {
358
+ if (!hasImkcTag(router)) return
359
+ if (!verifyContentKeyProof({
360
+ ownerPubkey: senderPubkey,
361
+ contentPubkey: imkcPubkey,
362
+ proof: imkcProof
363
+ })) throw new Error('INVALID_IMKC_PROOF')
364
+ }
365
+
366
+ function eventFromPayload ({ payloadCiphertext, messageSeckey, senderPubkey }) {
367
+ if (!HEX_SECKEY.test(messageSeckey || '')) throw new Error('INVALID_MESSAGE_SECKEY')
368
+ const messageSecretKey = hexToBytes(messageSeckey)
369
+ const messagePubkey = getPublicKey(messageSecretKey)
370
+ const decrypted = JSON.parse(nip44v3.decrypt(messageSecretKey, messagePubkey, ROUTER_KIND, NIP44_V3_SCOPE, payloadCiphertext))
371
+ if (isSignedEvent(decrypted)) return assertValidSignedInnerEvent(decrypted)
372
+ const normalized = { ...decrypted, pubkey: senderPubkey }
373
+ return { ...normalized, id: getEventHash(normalized) }
374
+ }
375
+
376
+ async function unwrapRecipientEnvelope ({ payloadCiphertext, envelope, receiverSigner, receiverPubkey, senderPubkey, imkcPubkey, rowScope = '' }) {
377
+ if (receiverPubkey && envelope.receiverPubkey !== receiverPubkey) return null
378
+ let messageSeckey
379
+ if (envelope.iykcPubkey || imkcPubkey) {
380
+ if (!receiverSigner?.nip44DecryptDoubleDH) throw new Error('RECEIVER_DOUBLE_DH_UNSUPPORTED')
381
+ if (envelope.iykcPubkey) {
382
+ assertValidEnvelopeIykcProof(envelope)
383
+ }
384
+ messageSeckey = base64ToText(await receiverSigner.nip44DecryptDoubleDH(
385
+ senderPubkey,
386
+ ROUTER_KIND,
387
+ rowScope,
388
+ envelope.ciphertext,
389
+ imkcPubkey,
390
+ envelope.iykcPubkey || ''
391
+ ))
392
+ } else {
393
+ if (!receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
394
+ messageSeckey = base64ToText(await receiverSigner.nip44v3Decrypt(senderPubkey, ROUTER_KIND, rowScope, envelope.ciphertext))
395
+ }
396
+ return eventFromPayload({ payloadCiphertext, messageSeckey, senderPubkey })
397
+ }
398
+
399
+ export async function unwrapEvent ({ receiverSigner, privateChannelSigner = receiverSigner, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderPubkey, event, receiverPubkey }) {
400
+ if (!event || event.kind !== PRIVATE_BROADCAST_KIND) return null
401
+ if (!receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
402
+ const channelReaderSigner = privateChannelReaderSigner || privateChannelSigner
403
+ if (!channelReaderSigner?.nip44v3Decrypt) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
404
+
405
+ const channelPubkey = event.pubkey || await privateChannelSigner?.getPublicKey?.()
406
+ if (!channelPubkey) throw new Error('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
407
+ const router = await decryptRouter({
408
+ content: event.content,
409
+ channelPubkey,
410
+ channelSigner: privateChannelSigner,
411
+ channelReaderSigner,
412
+ channelReaderPubkey: privateChannelReaderPubkey
413
+ })
414
+ if (router.kind !== ROUTER_KIND) throw new Error('INVALID_ROUTER_KIND')
415
+ if (receiverPubkey && readReceiverTag(router) && readReceiverTag(router) !== receiverPubkey) return null
416
+
417
+ const senderPubkey = readSenderTag(router)
418
+ const imkcPubkey = readImkcTag(router)
419
+ assertValidRouterImkcProof({ router, senderPubkey, imkcPubkey, imkcProof: readImkcProof(router) })
420
+ const lines = decodeChunkLines(router.content)
421
+ if (!lines.length) throw new Error('MISSING_PAYLOAD_ENVELOPE')
422
+ const payload = parsePayloadEnvelope(lines[0], 0)
423
+ for (let index = 1; index < lines.length; index++) {
424
+ const event = await unwrapRecipientEnvelope({
425
+ payloadCiphertext: payload.ciphertext,
426
+ envelope: parseRecipientEnvelope(lines[index], index),
427
+ receiverSigner,
428
+ receiverPubkey,
429
+ senderPubkey,
430
+ imkcPubkey,
431
+ rowScope: channelPubkey
432
+ })
433
+ if (event) return event
434
+ }
435
+ return null
436
+ }
437
+
438
+ function receiverPubkeyFor (receiver) {
439
+ return receiverPubkeys([receiver])[0] || ''
440
+ }
441
+
442
+ function relayReceiverEntries (relayToReceivers) {
443
+ if (!relayToReceivers) return []
444
+ if (relayToReceivers instanceof Map) return [...relayToReceivers.entries()]
445
+ if (typeof relayToReceivers === 'object') return Object.entries(relayToReceivers)
446
+ throw new Error('INVALID_RELAY_RECEIVERS')
447
+ }
448
+
449
+ function groupedRelayReceivers ({ relayToReceivers, receivers }) {
450
+ const entries = relayReceiverEntries(relayToReceivers)
451
+ if (!entries.length) return null
452
+
453
+ const receiverByPubkey = new Map()
454
+ const orderedPubkeys = []
455
+ for (const receiver of receivers || []) {
456
+ const pubkey = receiverPubkeyFor(receiver)
457
+ if (!pubkey || receiverByPubkey.has(pubkey)) continue
458
+ receiverByPubkey.set(pubkey, receiver)
459
+ orderedPubkeys.push(pubkey)
460
+ }
461
+
462
+ const wanted = new Set(orderedPubkeys)
463
+ const covered = new Set()
464
+ const groupsByKey = new Map()
465
+ for (const [relay, value] of entries) {
466
+ if (!relay) continue
467
+ const pubkeys = uniq(Array.isArray(value) ? value : [value])
468
+ if (!pubkeys.length) continue
469
+ for (const pubkey of pubkeys) {
470
+ if (!wanted.has(pubkey)) throw new Error('RELAY_RECEIVER_NOT_REQUESTED')
471
+ covered.add(pubkey)
472
+ }
473
+ const key = [...pubkeys].sort().join(',')
474
+ if (!groupsByKey.has(key)) {
475
+ const set = new Set(pubkeys)
476
+ groupsByKey.set(key, {
477
+ pubkeys: set,
478
+ relays: []
479
+ })
480
+ }
481
+ groupsByKey.get(key).relays.push(relay)
482
+ }
483
+
484
+ if (!groupsByKey.size) throw new Error('NO_RELAYS')
485
+ for (const pubkey of orderedPubkeys) {
486
+ if (!covered.has(pubkey)) throw new Error('RELAY_RECEIVER_MISSING')
487
+ }
488
+
489
+ return [...groupsByKey.values()].map(group => ({
490
+ relays: uniq(group.relays),
491
+ receivers: orderedPubkeys
492
+ .filter(pubkey => group.pubkeys.has(pubkey))
493
+ .map(pubkey => receiverByPubkey.get(pubkey))
494
+ }))
495
+ }
496
+
497
+ function relaysFromRelayReceivers (relayToReceivers) {
498
+ return uniq(relayReceiverEntries(relayToReceivers).map(([relay]) => relay))
499
+ }
500
+
501
+ function withRecoveryRelays (relays, recoveryRelays) {
502
+ return uniq([...(relays || []), ...(recoveryRelays || [])])
503
+ }
504
+
505
+ export async function publish ({ senderSigner, imkcSigner, privateChannelSigner = senderSigner, privateChannelReaderPubkey, receivers, receiverTag, deletionPubkey, event, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs = getIykcProofs, _publish = sendToRelays }) {
506
+ const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
507
+ const results = []
508
+ // Relays are grouped only when they have the exact same recipient pubkey set
509
+ const groups = groupedRelayReceivers({ relayToReceivers, receivers })
510
+ if (groups) {
511
+ const context = await prepareRoutedMessage({
512
+ senderSigner,
513
+ imkcSigner,
514
+ privateChannelSigner,
515
+ privateChannelReaderPubkey,
516
+ receivers,
517
+ event,
518
+ temporaryStorageArea,
519
+ _getIykcProofs
520
+ })
521
+ try {
522
+ for (const group of groups) {
523
+ for await (const wrappedEvent of wrapPreparedEvents({ privateChannelSigner, receivers: group.receivers, receiverTag, deletionPubkey: normalizedDeletionPubkey, expirationSeconds, context })) {
524
+ results.push(await _publish(wrappedEvent, withRecoveryRelays(group.relays, recoveryRelays)))
525
+ }
526
+ }
527
+ } finally {
528
+ cleanupEnvelopeRows(context.preparedRows)
529
+ }
530
+ return { results }
531
+ }
532
+
533
+ for await (const wrappedEvent of wrapEvents({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag, deletionPubkey: normalizedDeletionPubkey, event, expirationSeconds, temporaryStorageArea, _getIykcProofs })) {
534
+ results.push(await _publish(wrappedEvent, withRecoveryRelays(relays, recoveryRelays)))
535
+ }
536
+ return { results }
537
+ }
538
+
539
+ export async function publishNymEvent ({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey, event, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish = sendToRelays }) {
540
+ const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
541
+ const results = []
542
+ const publishRelays = withRecoveryRelays(relayToReceivers ? relaysFromRelayReceivers(relayToReceivers) : relays, recoveryRelays)
543
+ for await (const wrappedEvent of wrapNymEvents({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: normalizedDeletionPubkey, event, expirationSeconds })) {
544
+ results.push(await _publish(wrappedEvent, publishRelays))
545
+ }
546
+ return { results }
547
+ }
548
+
549
+ function readSignerFromMap (signersByPubkey, pubkey) {
550
+ if (!signersByPubkey || !pubkey) return null
551
+ if (signersByPubkey instanceof Map) return signersByPubkey.get(pubkey) || null
552
+ return signersByPubkey[pubkey] || null
553
+ }
554
+
555
+ async function decryptRouter ({ content, channelPubkey, channelSigner, channelReaderSigner, channelReaderPubkey }) {
556
+ const signer = channelReaderSigner || channelSigner
557
+ if (!signer?.nip44v3Decrypt) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
558
+
559
+ const readerPubkey = channelReaderPubkey || channelPubkey
560
+ const signerPubkey = await signer.getPublicKey?.()
561
+ // Writer-side reads use writer secret + reader pubkey; reader-side reads use reader secret + channel pubkey.
562
+ const isWriterSide = readerPubkey !== channelPubkey && (signer === channelSigner || signerPubkey === channelPubkey)
563
+ const peerPubkey = isWriterSide ? readerPubkey : channelPubkey
564
+ return JSON.parse(await nip44v3DecryptText(signer, peerPubkey, PRIVATE_BROADCAST_KIND, content))
565
+ }
566
+
567
+ function readValueFromMap (map, key) {
568
+ if (!map || !key) return null
569
+ if (map instanceof Map) return map.get(key) || null
570
+ return map[key] || null
571
+ }
572
+
573
+ function privateChannelPubkeyList ({ privateChannelPubkey, privateChannelPubkeys }) {
574
+ return [...new Set([
575
+ ...(privateChannelPubkeys || []),
576
+ ...(privateChannelPubkey ? [privateChannelPubkey] : [])
577
+ ].filter(Boolean))]
578
+ }
579
+
580
+ function createTtlSet ({ ttlMs, maxEntries }) {
581
+ const entries = new Map()
582
+
583
+ function prune (now = Date.now()) {
584
+ if (Number.isFinite(ttlMs)) {
585
+ for (const [key, expiresAt] of entries) {
586
+ if (expiresAt > now) break
587
+ entries.delete(key)
588
+ }
589
+ }
590
+ if (Number.isFinite(maxEntries)) {
591
+ while (entries.size > maxEntries) entries.delete(entries.keys().next().value)
592
+ }
593
+ }
594
+
595
+ return {
596
+ add (key) {
597
+ if (!key || maxEntries <= 0 || ttlMs <= 0) return
598
+ const now = Date.now()
599
+ const expiresAt = Number.isFinite(ttlMs) ? now + ttlMs : Infinity
600
+ if (entries.has(key)) entries.delete(key)
601
+ entries.set(key, expiresAt)
602
+ prune(now)
603
+ },
604
+ has (key) {
605
+ prune()
606
+ return entries.has(key)
607
+ }
608
+ }
609
+ }
610
+
611
+ function contentKeyUsageBase ({ outer, router, channelPubkey, receiverPubkeys = [] }) {
612
+ const senderPubkey = readSenderTag(router)
613
+ const routerReceiverPubkey = readReceiverTag(router)
614
+ return {
615
+ outer,
616
+ router,
617
+ channelPubkey,
618
+ senderPubkey,
619
+ routerReceiverPubkey,
620
+ receiverPubkeys,
621
+ isBroadcast: !routerReceiverPubkey
622
+ }
623
+ }
624
+
625
+ function emitSentContentKeyUsage ({ outer, router, channelPubkey, receiverPubkey, receiverPubkeys, onContentKeyUsage }) {
626
+ if (!receiverPubkey || !onContentKeyUsage) return
627
+
628
+ const base = contentKeyUsageBase({ outer, router, channelPubkey, receiverPubkeys })
629
+ if (base.senderPubkey !== receiverPubkey) return
630
+ onContentKeyUsage({
631
+ ...base,
632
+ direction: 'sent',
633
+ keyRole: 'sender',
634
+ receiverPubkey: base.routerReceiverPubkey || '',
635
+ contentKeyPubkey: readImkcTag(router),
636
+ contentKeyProof: readImkcProof(router)
637
+ })
638
+ }
639
+
640
+ function emitReceivedContentKeyUsage ({ outer, router, channelPubkey, receiverPubkey, receiverPubkeys, envelope, onContentKeyUsage }) {
641
+ if (!receiverPubkey || !onContentKeyUsage || envelope.receiverPubkey !== receiverPubkey) return
642
+
643
+ onContentKeyUsage({
644
+ ...contentKeyUsageBase({ outer, router, channelPubkey, receiverPubkeys }),
645
+ direction: 'received',
646
+ keyRole: 'receiver',
647
+ receiverPubkey: envelope.receiverPubkey,
648
+ contentKeyPubkey: envelope.iykcPubkey,
649
+ contentKeyProof: envelope.iykcProof,
650
+ rowIndex: envelope.index
651
+ })
652
+ }
653
+
654
+ function createProcessor ({
655
+ receiverSigner,
656
+ privateChannelSigner,
657
+ privateChannelSignersByPubkey,
658
+ privateChannelReaderSigner = privateChannelSigner,
659
+ privateChannelReaderSignersByPubkey,
660
+ privateChannelReaderPubkey,
661
+ privateChannelReaderPubkeysByPubkey,
662
+ receiverPubkey,
663
+ mode = 'leecher',
664
+ modeByPubkey,
665
+ onChunk,
666
+ onEvent,
667
+ onNymEvent,
668
+ onSeedEvent,
669
+ onContentKeyUsage,
670
+ onError,
671
+ receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS,
672
+ receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES,
673
+ receivedChunkStorageArea,
674
+ ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS,
675
+ ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES
676
+ }) {
677
+ const receivedChunks = createReceivedChunkStore({
678
+ ttlMs: receivedChunkTtlMs,
679
+ maxBytes: receivedChunkMaxBytes,
680
+ storageArea: receivedChunkStorageArea
681
+ })
682
+ const ignoredGroups = createTtlSet({
683
+ ttlMs: ignoredGroupTtlMs,
684
+ maxEntries: ignoredGroupMaxEntries
685
+ })
686
+
687
+ return async function processOuterEvent (outer) {
688
+ let groupKey = ''
689
+ try {
690
+ const channelPubkey = outer.pubkey || await privateChannelSigner?.getPublicKey?.()
691
+ const channelSigner = readSignerFromMap(privateChannelSignersByPubkey, channelPubkey) || privateChannelSigner
692
+ const channelReaderSigner = readSignerFromMap(privateChannelReaderSignersByPubkey, channelPubkey) || privateChannelReaderSigner || channelSigner
693
+ const channelReaderPubkey = readValueFromMap(privateChannelReaderPubkeysByPubkey, channelPubkey) || privateChannelReaderPubkey || channelPubkey
694
+ const channelMode = readValueFromMap(modeByPubkey, channelPubkey) || mode
695
+ if (!channelPubkey) throw new Error('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
696
+
697
+ const decrypted = await decryptRouter({
698
+ content: outer.content,
699
+ channelPubkey,
700
+ channelSigner,
701
+ channelReaderSigner,
702
+ channelReaderPubkey
703
+ })
704
+ if (decrypted.kind === NYM_CARRIER_KIND) {
705
+ const carrier = assertValidNymCarrierEvent(decrypted)
706
+ const { index, total } = readChunkTag(carrier)
707
+ groupKey = receivedChunks.groupKeyFor(channelPubkey, nymCarrierGroupId(carrier))
708
+ if (ignoredGroups.has(groupKey)) return
709
+
710
+ const meta = receivedChunks.put({
711
+ channelPubkey,
712
+ routerPubkey: nymCarrierGroupId(carrier),
713
+ index,
714
+ total,
715
+ content: JSON.stringify(carrier)
716
+ })
717
+ const status = receivedChunks.status(meta)
718
+ onChunk?.({
719
+ outer,
720
+ nymCarrier: carrier,
721
+ channelPubkey,
722
+ index,
723
+ total,
724
+ received: status.received,
725
+ missing: status.missing
726
+ })
727
+ if (status.received < total) return
728
+
729
+ const carriers = receivedChunks.readChunkContents(groupKey).map(raw => JSON.parse(raw))
730
+ const event = eventFromNymCarriers(carriers)
731
+ const shouldSeed = storesRecoverySeeds(channelMode)
732
+ if (shouldSeed) {
733
+ await onSeedEvent?.({
734
+ recordType: 'nymCarrier_v1',
735
+ outer,
736
+ carriers,
737
+ carrier: carriers[0],
738
+ channelPubkey,
739
+ event
740
+ })
741
+ }
742
+ await onNymEvent?.(event, outer, {
743
+ carrier: carriers[0],
744
+ carriers,
745
+ channelPubkey
746
+ })
747
+ receivedChunks.removeGroup(groupKey)
748
+ return
749
+ }
750
+ const router = decrypted
751
+ if (router.kind !== ROUTER_KIND) return
752
+ const senderPubkey = readSenderTag(router)
753
+ if (receiverPubkey && readReceiverTag(router) && readReceiverTag(router) !== receiverPubkey && senderPubkey !== receiverPubkey) return
754
+ const { index, total } = readChunkTag(router)
755
+ groupKey = receivedChunks.groupKeyFor(channelPubkey, router.pubkey)
756
+ if (ignoredGroups.has(groupKey)) return
757
+
758
+ const imkcPubkey = readImkcTag(router)
759
+ assertValidRouterImkcProof({ router, senderPubkey, imkcPubkey, imkcProof: readImkcProof(router) })
760
+ const meta = receivedChunks.put({
761
+ channelPubkey,
762
+ routerPubkey: router.pubkey,
763
+ index,
764
+ total,
765
+ content: router.content
766
+ })
767
+ const status = receivedChunks.status(meta)
768
+ onChunk?.({
769
+ outer,
770
+ router,
771
+ channelPubkey,
772
+ index,
773
+ total,
774
+ received: status.received,
775
+ missing: status.missing
776
+ })
777
+
778
+ const shouldSeed = storesRecoverySeeds(channelMode)
779
+ const sentByReceiver = receiverPubkey && senderPubkey === receiverPubkey
780
+ // Recovery seeders and own-sent messages need the full recipient list;
781
+ // regular leechers can stop as soon as their envelope is decrypted.
782
+ const mustScanWholeBundle = shouldSeed || sentByReceiver
783
+ let event = null
784
+ const innerEventIdsByRowIndex = {}
785
+
786
+ const drained = await receivedChunks.drainAvailable(groupKey, {
787
+ onLine: async (line, rowIndex, groupMeta, helpers) => {
788
+ if (rowIndex === 0) {
789
+ helpers.rememberPayloadCiphertext(groupMeta, parsePayloadEnvelope(line, rowIndex).ciphertext)
790
+ return
791
+ }
792
+ if (!groupMeta.payloadCiphertext) throw new Error('MISSING_PAYLOAD_ENVELOPE')
793
+ const envelope = parseRecipientEnvelope(line, rowIndex)
794
+ helpers.rememberReceiverPubkey(groupMeta, envelope.receiverPubkey)
795
+
796
+ if (receiverPubkey && envelope.receiverPubkey === receiverPubkey) {
797
+ assertValidEnvelopeIykcProof(envelope)
798
+ emitReceivedContentKeyUsage({
799
+ outer,
800
+ router,
801
+ channelPubkey,
802
+ receiverPubkey,
803
+ receiverPubkeys: groupMeta.receiverPubkeys,
804
+ envelope,
805
+ onContentKeyUsage
806
+ })
807
+ if (receiverSigner && !event) {
808
+ event = await unwrapRecipientEnvelope({
809
+ payloadCiphertext: groupMeta.payloadCiphertext,
810
+ envelope,
811
+ receiverSigner,
812
+ receiverPubkey,
813
+ senderPubkey,
814
+ imkcPubkey,
815
+ rowScope: channelPubkey
816
+ })
817
+ if (event) {
818
+ innerEventIdsByRowIndex[rowIndex] = event.id
819
+ if (!mustScanWholeBundle) return { stop: true }
820
+ }
821
+ }
822
+ }
823
+ }
824
+ })
825
+
826
+ if (event && !mustScanWholeBundle) {
827
+ await onEvent?.(event, outer, { router: joinedRouter(router), channelPubkey })
828
+ ignoredGroups.add(groupKey)
829
+ receivedChunks.removeGroup(groupKey)
830
+ return
831
+ }
832
+
833
+ if (!drained.complete) return
834
+
835
+ const receiverPubkeys = drained.meta?.receiverPubkeys || []
836
+ const content = shouldSeed ? receivedChunks.readEnvelopeBundleContent(groupKey) : ''
837
+ const jsonl = shouldSeed ? receivedChunks.readEnvelopeBundleText(groupKey) : ''
838
+ const completeRouter = joinedRouter(router, content)
839
+
840
+ emitSentContentKeyUsage({
841
+ outer,
842
+ router: completeRouter,
843
+ channelPubkey,
844
+ receiverPubkey,
845
+ receiverPubkeys,
846
+ onContentKeyUsage
847
+ })
848
+ if (shouldSeed) await onSeedEvent?.({ recordType: 'routerRow_v1', outer, router: completeRouter, channelPubkey, jsonl, innerEventIdsByRowIndex })
849
+ if (event) await onEvent?.(event, outer, { router: completeRouter, channelPubkey, jsonl })
850
+
851
+ receivedChunks.removeGroup(groupKey)
852
+ } catch (err) {
853
+ if (shouldIgnoreGroupError(err) && groupKey) {
854
+ ignoredGroups.add(groupKey)
855
+ receivedChunks.removeGroup(groupKey)
856
+ }
857
+ onError?.(err)
858
+ }
859
+ }
860
+ }
861
+
862
+ function shouldIgnoreGroupError (err) {
863
+ return [
864
+ 'DUPLICATE_NYM_CARRIER_CHUNK',
865
+ 'INVALID_IYKC_PROOF',
866
+ 'INVALID_IMKC_PROOF',
867
+ 'INVALID_MESSAGE_SECKEY',
868
+ 'INVALID_NYM_CARRIER',
869
+ 'INVALID_NYM_CARRIER_ID',
870
+ 'INVALID_NYM_CARRIER_INNER_ID',
871
+ 'INVALID_NYM_CARRIER_KIND',
872
+ 'INVALID_NYM_CARRIER_PAYLOAD',
873
+ 'INVALID_NYM_RUMOR',
874
+ 'INVALID_PAYLOAD_ENVELOPE',
875
+ 'INVALID_RECIPIENT_ENVELOPE',
876
+ 'INVALID_SIGNED_INNER_EVENT',
877
+ 'MISSING_PAYLOAD_ENVELOPE',
878
+ 'MISMATCHED_NYM_CARRIER_CHUNKS',
879
+ 'MISSING_NYM_CARRIER_CHUNK',
880
+ 'MISSING_NYM_CARRIER_ID'
881
+ ].includes(err?.message)
882
+ }
883
+
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, receivedChunkStorageArea, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _getEvents = getEvents }) {
885
+ if (!relays?.length) throw new Error('NO_RELAYS')
886
+ const authors = privateChannelPubkeyList({ privateChannelPubkey, privateChannelPubkeys })
887
+ const filter = { kinds: [PRIVATE_BROADCAST_KIND] }
888
+ if (authors.length) filter.authors = authors
889
+ if (since != null) filter.since = since
890
+ if (until != null) filter.until = until
891
+ if (limit != null) filter.limit = limit
892
+
893
+ const { result: events } = await _getEvents(filter, relays, {
894
+ timeout: 5000,
895
+ timeoutAfterFirstEose: null
896
+ })
897
+ 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, receivedChunkStorageArea, ignoredGroupTtlMs, ignoredGroupMaxEntries })
899
+ for (const event of events) await processOuterEvent(event)
900
+ return events
901
+ }
902
+
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, receivedChunkStorageArea, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _liveEventsGenerator = getLiveEventsGenerator, _eventsFeedGenerator = getEventsFeedGenerator }) {
904
+ if (!relays?.length) throw new Error('NO_RELAYS')
905
+ if (receiverSigner && !receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
906
+ if (!privateChannelReaderSigner && !privateChannelReaderSignersByPubkey && !privateChannelSigner && !privateChannelSignersByPubkey) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
907
+
908
+ const authors = privateChannelPubkeyList({ privateChannelPubkey, privateChannelPubkeys })
909
+ const filter = { kinds: [PRIVATE_BROADCAST_KIND], since }
910
+ if (authors.length) filter.authors = authors
911
+ 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, receivedChunkStorageArea, ignoredGroupTtlMs, ignoredGroupMaxEntries })
913
+ const controller = new AbortController()
914
+ const events = liveOnly
915
+ ? _liveEventsGenerator(filter, relays, {
916
+ signal: controller.signal
917
+ })
918
+ : _eventsFeedGenerator(filter, relays, {
919
+ signal: controller.signal,
920
+ timeout: 5000,
921
+ timeoutAfterFirstEose: null
922
+ })
923
+
924
+ async function consumeEvents () {
925
+ try {
926
+ for await (const outer of events) {
927
+ if (controller.signal.aborted) continue
928
+ await processOuterEvent(outer)
929
+ }
930
+ } catch (error) {
931
+ if (!controller.signal.aborted && error?.message !== 'Aborted') onError?.(error)
932
+ }
933
+ }
934
+
935
+ consumeEvents()
936
+
937
+ return {
938
+ close () {
939
+ controller.abort()
940
+ }
941
+ }
942
+ }