libp2r2p 0.0.2 → 0.1.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.
@@ -1,501 +0,0 @@
1
- import { afterEach, test } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { finalizeEvent, generateSecretKey, getEventHash } from 'nostr-tools'
4
- import {
5
- ASK_KIND,
6
- REPLY_KIND,
7
- TELL_KIND,
8
- broadcastEvent,
9
- broadcastNymEvent,
10
- broadcastNymRumor,
11
- ask,
12
- broadcastRumor,
13
- parseRumorContent,
14
- reply,
15
- tell,
16
- unwatch,
17
- watch,
18
- yell
19
- } from '../private-message/index.js'
20
- import { keypairFromSeckey } from '../key/index.js'
21
-
22
- const data = new Map()
23
- globalThis.localStorage = {
24
- clear: () => data.clear(),
25
- getItem: key => data.has(String(key)) ? data.get(String(key)) : null,
26
- removeItem: key => { data.delete(String(key)) },
27
- setItem: (key, value) => { data.set(String(key), String(value)) }
28
- }
29
-
30
- afterEach(() => {
31
- unwatch()
32
- globalThis.localStorage.clear()
33
- })
34
-
35
- function signer (pubkey) {
36
- return {
37
- getPublicKey: () => pubkey,
38
- withSharedKey: () => ({})
39
- }
40
- }
41
-
42
- function pubkeyFixture (index) {
43
- return index.toString(16).padStart(64, '0')
44
- }
45
-
46
- function fakeSubscribeFactory () {
47
- const calls = []
48
- const closed = []
49
- const fakeSubscribe = options => {
50
- calls.push(options)
51
- return {
52
- close: () => closed.push(options)
53
- }
54
- }
55
- return { calls, closed, fakeSubscribe }
56
- }
57
-
58
- test('watch merges overlapping relay subscriptions by channel author', async () => {
59
- const { calls, closed, fakeSubscribe } = fakeSubscribeFactory()
60
- const channel1 = signer('channel1')
61
- const reader1 = signer('reader1')
62
- await watch({
63
- channels: ['channel1'],
64
- relays: ['wss://a.example'],
65
- receiverSigner: signer('receiver'),
66
- privateChannelSigner: channel1,
67
- privateChannelReaderSigner: reader1,
68
- privateChannelReaderPubkey: 'reader1',
69
- _subscribe: fakeSubscribe
70
- })
71
- await watch({
72
- channels: ['channel1'],
73
- relays: ['wss://a.example'],
74
- receiverSigner: signer('receiver'),
75
- privateChannelSigner: channel1,
76
- privateChannelReaderSigner: reader1,
77
- privateChannelReaderPubkey: 'reader1',
78
- _subscribe: fakeSubscribe
79
- })
80
- await watch({
81
- channels: ['channel2'],
82
- relays: ['wss://a.example', 'wss://b.example'],
83
- receiverSigner: signer('receiver'),
84
- privateChannelSigner: signer('channel2'),
85
- privateChannelReaderSigner: signer('reader2'),
86
- privateChannelReaderPubkey: 'reader2',
87
- mode: 'seeder',
88
- _subscribe: fakeSubscribe
89
- })
90
-
91
- assert.equal(calls.length, 3)
92
- assert.deepEqual(calls[0].privateChannelPubkeys, ['channel1'])
93
- assert.deepEqual(calls[0].relays, ['wss://a.example'])
94
- assert.equal(calls[0].limit, 0)
95
- assert.equal(calls[0].liveOnly, true)
96
- assert.deepEqual(calls[1].privateChannelPubkeys.sort(), ['channel1', 'channel2'])
97
- assert.deepEqual(calls[1].relays, ['wss://a.example'])
98
- assert.deepEqual(calls[1].modeByPubkey, { channel1: 'leecher', channel2: 'seeder' })
99
- assert.deepEqual(Object.fromEntries(Object.entries(calls[1].privateChannelReaderSignersByPubkey).map(([channel, nextSigner]) => [channel, nextSigner.getPublicKey()])), {
100
- channel1: 'reader1',
101
- channel2: 'reader2'
102
- })
103
- assert.deepEqual(calls[1].privateChannelReaderPubkeysByPubkey, {
104
- channel1: 'reader1',
105
- channel2: 'reader2'
106
- })
107
- assert.deepEqual(calls[2].privateChannelPubkeys, ['channel2'])
108
- assert.deepEqual(calls[2].relays, ['wss://b.example'])
109
-
110
- await new Promise(resolve => setTimeout(resolve, 550))
111
- assert.equal(closed.length, 1)
112
- assert.deepEqual(closed[0].privateChannelPubkeys, ['channel1'])
113
- })
114
-
115
- test('watch refresh keeps shared relay subscriptions and gracefully closes removed relays', async () => {
116
- const { calls, closed, fakeSubscribe } = fakeSubscribeFactory()
117
- await watch({
118
- channels: ['channel1'],
119
- relays: ['wss://a.example', 'wss://b.example'],
120
- receiverSigner: signer('receiver'),
121
- privateChannelSigner: signer('channel1'),
122
- _subscribe: fakeSubscribe
123
- })
124
- await watch({
125
- channels: ['channel1'],
126
- relays: ['wss://b.example', 'wss://c.example'],
127
- receiverSigner: signer('receiver'),
128
- privateChannelSigner: signer('channel1'),
129
- _subscribe: fakeSubscribe
130
- })
131
-
132
- assert.equal(calls.length, 3)
133
- assert.deepEqual(calls.map(call => call.relays[0]), ['wss://a.example', 'wss://b.example', 'wss://c.example'])
134
- assert.equal(closed.length, 0)
135
-
136
- await new Promise(resolve => setTimeout(resolve, 550))
137
- assert.equal(closed.length, 1)
138
- assert.deepEqual(closed[0].relays, ['wss://a.example'])
139
- })
140
-
141
- test('ask requires watching the sender private channel first', async () => {
142
- await assert.rejects(
143
- () => ask({
144
- senderSigner: signer('sender'),
145
- privateChannelSigner: signer('sender-channel'),
146
- receiverPubkey: 'receiver',
147
- relays: ['wss://relay.example'],
148
- message: { code: 'PING' },
149
- _publish: async () => []
150
- }),
151
- /PRIVATE_MESSAGE_NOT_WATCHING/
152
- )
153
- })
154
-
155
- test('ask from a reader-only channel requires a writer signer', async () => {
156
- const { fakeSubscribe } = fakeSubscribeFactory()
157
- await watch({
158
- channels: ['writer-channel'],
159
- relays: ['wss://relay.example'],
160
- receiverSigner: signer('sender'),
161
- privateChannelSigner: null,
162
- privateChannelReaderSigner: signer('reader'),
163
- _subscribe: fakeSubscribe
164
- })
165
-
166
- await assert.rejects(
167
- () => ask({
168
- senderSigner: signer('sender'),
169
- privateChannelSigner: null,
170
- receiverPubkey: 'receiver',
171
- relays: ['wss://relay.example'],
172
- message: { code: 'PING' },
173
- _publish: async () => []
174
- }),
175
- /PRIVATE_CHANNEL_WRITER_REQUIRED/
176
- )
177
- })
178
-
179
- test('watch dispatches content key usage callbacks', async () => {
180
- const { calls, fakeSubscribe } = fakeSubscribeFactory()
181
- const usages = []
182
- await watch({
183
- channels: ['channel1'],
184
- relays: ['wss://relay.example'],
185
- receiverSigner: signer('receiver'),
186
- privateChannelSigner: signer('channel1'),
187
- onContentKeyUsage: usage => usages.push(usage),
188
- _subscribe: fakeSubscribe
189
- })
190
-
191
- calls[0].onContentKeyUsage({
192
- channelPubkey: 'channel1',
193
- direction: 'sent',
194
- contentKeyPubkey: ''
195
- })
196
-
197
- assert.deepEqual(usages, [{ channelPubkey: 'channel1', direction: 'sent', contentKeyPubkey: '' }])
198
- })
199
-
200
- test('ask publishes an ask rumor and watch dispatches the reply with its question', async () => {
201
- const { calls, fakeSubscribe } = fakeSubscribeFactory()
202
- const replies = []
203
- let published = null
204
- const senderPubkey = pubkeyFixture(1)
205
- await watch({
206
- channels: ['sender-channel'],
207
- relays: ['wss://relay.example'],
208
- receiverSigner: signer(senderPubkey),
209
- privateChannelSigner: signer('sender-channel'),
210
- onReply: event => replies.push(event),
211
- _subscribe: fakeSubscribe
212
- })
213
- const result = await ask({
214
- senderSigner: signer(senderPubkey),
215
- privateChannelSigner: signer('sender-channel'),
216
- privateChannelReaderPubkey: 'reader-channel',
217
- receiverPubkey: 'receiver',
218
- relays: ['wss://relay.example'],
219
- temporaryStorageArea: globalThis.localStorage,
220
- message: { code: 'PING', payload: { ok: true } },
221
- _publish: async options => {
222
- published = options
223
- return [{ success: true }]
224
- }
225
- })
226
-
227
- assert.equal(published.event.kind, ASK_KIND)
228
- assert.equal(published.event.sig, undefined)
229
- assert.equal(published.event.id, undefined)
230
- assert.equal(published.event.pubkey, undefined)
231
- assert.equal(published.receiverTag, 'receiver')
232
- assert.deepEqual(published.receivers, ['receiver'])
233
- assert.equal(published.privateChannelReaderPubkey, 'reader-channel')
234
- assert.equal(published.temporaryStorageArea, globalThis.localStorage)
235
- assert.deepEqual(JSON.parse(published.event.content), { ok: true })
236
- assert.deepEqual(published.event.tags, [['r', 'receiver'], ['h', 'PING']])
237
- assert.equal(result.question.pubkey, senderPubkey)
238
- assert.equal(result.question.id, getEventHash({ ...published.event, pubkey: senderPubkey }))
239
- assert.deepEqual(result.delivery.reports, [{ success: true }])
240
- assert.equal(keypairFromSeckey(result.delivery.deletionSeckey).pubkey, published.deletionPubkey)
241
- assert.throws(() => getEventHash(published.event), /wrong or missing properties/)
242
-
243
- calls[0].onEvent({
244
- kind: REPLY_KIND,
245
- id: 'reply-id',
246
- pubkey: 'receiver',
247
- created_at: 1,
248
- tags: [['q', result.question.id]],
249
- content: 'pong'
250
- }, { created_at: 2 }, { channelPubkey: 'sender-channel' })
251
-
252
- assert.equal(replies.length, 1)
253
- assert.equal(replies[0].question, undefined)
254
- assert.equal(replies[0].questionId, result.question.id)
255
- assert.equal(replies[0].reply.pubkey, 'receiver')
256
- assert.deepEqual(replies[0].payload, { payload: 'pong' })
257
- })
258
-
259
- test('reply tell and yell publish recognizable private message rumors', async () => {
260
- const published = []
261
- const bobPubkey = pubkeyFixture(3)
262
- const _publish = async options => {
263
- published.push(options)
264
- return []
265
- }
266
- const question = {
267
- id: 'question-id',
268
- pubkey: 'alice',
269
- kind: ASK_KIND,
270
- created_at: 1,
271
- tags: [['r', 'bob']],
272
- content: '{}'
273
- }
274
-
275
- const replyResult = await reply({ senderSigner: signer(bobPubkey), question, relays: ['wss://relay.example'], payload: 'answer', _publish })
276
- const tellResult = await tell({ senderSigner: signer(bobPubkey), receiverPubkey: 'alice', relays: ['wss://relay.example'], payload: 'note', _publish })
277
- const yellResult = await yell({ senderSigner: signer(bobPubkey), receiverPubkeys: ['alice', 'carol'], relays: ['wss://relay.example'], payload: 'broadcast', _publish })
278
- const rawResult = await broadcastRumor({
279
- senderSigner: signer(bobPubkey),
280
- receiverPubkeys: ['alice', 'carol'],
281
- relays: ['wss://relay.example'],
282
- rumor: { kind: 9001, created_at: 22, tags: [['x', '1']], content: 'raw' },
283
- _publish
284
- })
285
- const signedEvent = finalizeEvent({ kind: 9002, created_at: 23, tags: [['x', '2']], content: 'signed' }, generateSecretKey())
286
- const signedResult = await broadcastEvent({
287
- senderSigner: signer(bobPubkey),
288
- receiverPubkeys: ['alice', 'carol'],
289
- relays: ['wss://relay.example'],
290
- event: signedEvent,
291
- _publish
292
- })
293
-
294
- assert.equal(published[0].event.kind, REPLY_KIND)
295
- assert.equal(published[0].event.id, undefined)
296
- assert.equal(published[0].event.pubkey, undefined)
297
- assert.equal(replyResult.reply.pubkey, bobPubkey)
298
- assert.equal(replyResult.reply.id, getEventHash({ ...published[0].event, pubkey: bobPubkey }))
299
- assert.deepEqual(published[0].event.tags, [['q', 'question-id'], ['r', 'alice']])
300
- assert.equal(published[0].receiverTag, 'alice')
301
- assert.equal(published[1].event.kind, TELL_KIND)
302
- assert.equal(tellResult.tell.id, getEventHash({ ...published[1].event, pubkey: bobPubkey }))
303
- assert.deepEqual(published[1].event.tags, [['r', 'alice']])
304
- assert.equal(published[1].receiverTag, 'alice')
305
- assert.equal(published[2].event.kind, TELL_KIND)
306
- assert.equal(yellResult.yell.id, getEventHash({ ...published[2].event, pubkey: bobPubkey }))
307
- assert.deepEqual(published[2].event.tags, [])
308
- assert.equal(published[2].receiverTag, '')
309
- assert.deepEqual(published[2].receivers, ['alice', 'carol'])
310
- assert.equal(published[3].event.kind, 9001)
311
- assert.equal(published[3].event.created_at, 22)
312
- assert.equal(published[3].event.content, 'raw')
313
- assert.equal(published[3].event.id, undefined)
314
- assert.equal(published[3].event.pubkey, undefined)
315
- assert.equal(published[3].event.sig, undefined)
316
- assert.deepEqual(published[3].event.tags, [['x', '1']])
317
- assert.equal(published[3].receiverTag, '')
318
- assert.deepEqual(published[3].receivers, ['alice', 'carol'])
319
- assert.equal(rawResult.rumor.pubkey, bobPubkey)
320
- assert.equal(rawResult.rumor.id, getEventHash({ ...published[3].event, pubkey: bobPubkey }))
321
- assert.deepEqual(published[4].event, signedEvent)
322
- assert.deepEqual(published[4].receivers, ['alice', 'carol'])
323
- assert.deepEqual(signedResult.event, signedEvent)
324
- })
325
-
326
- test('private message sends share one deletion capability and return delivery reports', async () => {
327
- const published = []
328
- const reports = [{ success: true, total: 1 }, { success: false, total: 1 }]
329
- const _publish = async options => {
330
- published.push(options)
331
- return reports
332
- }
333
-
334
- const generated = await tell({
335
- senderSigner: signer(pubkeyFixture(3)),
336
- receiverPubkey: 'alice',
337
- relays: ['wss://relay.example'],
338
- payload: 'managed',
339
- _publish
340
- })
341
-
342
- assert.deepEqual(generated.delivery.reports, reports)
343
- assert.equal(keypairFromSeckey(generated.delivery.deletionSeckey).pubkey, published[0].deletionPubkey)
344
- assert.equal('results' in generated, false)
345
- assert.equal('deletionPubkey' in generated, false)
346
- assert.equal('deletionSeckey' in generated, false)
347
-
348
- const supplied = await tell({
349
- senderSigner: signer(pubkeyFixture(3)),
350
- receiverPubkey: 'alice',
351
- relays: ['wss://relay.example'],
352
- payload: 'caller-managed',
353
- deletionPubkey: 'A'.repeat(64),
354
- _publish
355
- })
356
- assert.equal(published[1].deletionPubkey, 'a'.repeat(64))
357
- assert.deepEqual(supplied.delivery, { reports })
358
-
359
- const untagged = await tell({
360
- senderSigner: signer(pubkeyFixture(3)),
361
- receiverPubkey: 'alice',
362
- relays: ['wss://relay.example'],
363
- payload: 'unlinkable',
364
- autoDeletionCapability: false,
365
- _publish
366
- })
367
- assert.equal(published[2].deletionPubkey, undefined)
368
- assert.deepEqual(untagged.delivery, { reports })
369
-
370
- await assert.rejects(
371
- () => tell({
372
- senderSigner: signer(pubkeyFixture(3)),
373
- receiverPubkey: 'alice',
374
- relays: ['wss://relay.example'],
375
- payload: 'bad key',
376
- deletionPubkey: 'not-a-pubkey',
377
- _publish
378
- }),
379
- /INVALID_DELETION_PUBKEY/
380
- )
381
- await assert.rejects(
382
- () => tell({
383
- senderSigner: signer(pubkeyFixture(3)),
384
- receiverPubkey: 'alice',
385
- relays: ['wss://relay.example'],
386
- payload: 'bad secret',
387
- deletionSeckey: 'b'.repeat(64),
388
- _publish
389
- }),
390
- /DELETION_SECKEY_NOT_ACCEPTED/
391
- )
392
- await assert.rejects(
393
- () => tell({
394
- senderSigner: signer(pubkeyFixture(3)),
395
- receiverPubkey: 'alice',
396
- relays: ['wss://relay.example'],
397
- payload: 'bad policy',
398
- autoDeletionCapability: 'yes',
399
- _publish
400
- }),
401
- /AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED/
402
- )
403
- })
404
-
405
- test('broadcastRumor validates normalized unsigned rumors before publishing', async () => {
406
- let published = false
407
-
408
- await assert.rejects(
409
- () => broadcastRumor({
410
- senderSigner: signer(pubkeyFixture(4)),
411
- receiverPubkeys: ['alice'],
412
- relays: ['wss://relay.example'],
413
- rumor: { kind: 9001, created_at: 22, tags: ['not-a-tag'], content: 'raw' },
414
- _publish: async () => { published = true }
415
- }),
416
- /INVALID_RUMOR/
417
- )
418
-
419
- assert.equal(published, false)
420
- })
421
-
422
- test('broadcastEvent refuses unsigned or invalid signed events', async () => {
423
- const valid = finalizeEvent({ kind: 9002, created_at: 23, tags: [], content: 'signed' }, generateSecretKey())
424
- let published = false
425
-
426
- await assert.rejects(
427
- () => broadcastEvent({
428
- senderSigner: signer(pubkeyFixture(5)),
429
- receiverPubkeys: ['alice'],
430
- relays: ['wss://relay.example'],
431
- event: { ...valid, content: 'tampered' },
432
- _publish: async () => { published = true }
433
- }),
434
- /INVALID_SIGNED_EVENT/
435
- )
436
- await assert.rejects(
437
- () => broadcastEvent({
438
- senderSigner: signer(pubkeyFixture(5)),
439
- receiverPubkeys: ['alice'],
440
- relays: ['wss://relay.example'],
441
- event: { kind: 9002, created_at: 23, tags: [], content: 'unsigned' },
442
- _publish: async () => { published = true }
443
- }),
444
- /INVALID_SIGNED_EVENT/
445
- )
446
-
447
- assert.equal(published, false)
448
- })
449
-
450
- test('nym broadcasts publish through the nym channel path without receivers', async () => {
451
- const published = []
452
- const nymPubkey = pubkeyFixture(6)
453
- const channelPubkey = pubkeyFixture(7)
454
- const authorEvent = finalizeEvent({ kind: 9002, created_at: 30, tags: [], content: 'signed by another key' }, generateSecretKey())
455
- const reports = [{ success: true, total: 1 }]
456
- const _publish = async options => {
457
- published.push(options)
458
- return reports
459
- }
460
-
461
- const rumorResult = await broadcastNymRumor({
462
- nymSigner: signer(nymPubkey),
463
- privateChannelSigner: signer(channelPubkey),
464
- relays: ['wss://relay.example'],
465
- rumor: { kind: 9001, created_at: 29, tags: [['x', 'nym']], content: 'rumor' },
466
- _publish
467
- })
468
- const eventResult = await broadcastNymEvent({
469
- nymSigner: signer(nymPubkey),
470
- privateChannelSigner: signer(channelPubkey),
471
- relays: ['wss://relay.example'],
472
- event: authorEvent,
473
- _publish
474
- })
475
-
476
- assert.equal(published.length, 2)
477
- assert.equal(published[0].nymSigner.getPublicKey(), nymPubkey)
478
- assert.equal(published[0].privateChannelSigner.getPublicKey(), channelPubkey)
479
- assert.equal(published[0].event.pubkey, undefined)
480
- assert.equal(published[0].event.id, undefined)
481
- assert.equal(rumorResult.rumor.pubkey, nymPubkey)
482
- assert.equal(rumorResult.rumor.id, getEventHash({ ...published[0].event, pubkey: nymPubkey }))
483
- assert.deepEqual(rumorResult.delivery.reports, reports)
484
- assert.equal(keypairFromSeckey(rumorResult.delivery.deletionSeckey).pubkey, published[0].deletionPubkey)
485
- assert.deepEqual(published[1].event, authorEvent)
486
- assert.notEqual(published[1].event.pubkey, nymPubkey)
487
- assert.deepEqual(eventResult.event, authorEvent)
488
- assert.deepEqual(eventResult.delivery.reports, reports)
489
- assert.equal(keypairFromSeckey(eventResult.delivery.deletionSeckey).pubkey, published[1].deletionPubkey)
490
- })
491
-
492
- test('parseRumorContent only reads h tags for private message kinds', () => {
493
- assert.deepEqual(
494
- parseRumorContent({ kind: TELL_KIND, tags: [['h', 'NOTE']], content: 'hello' }),
495
- { payload: 'hello', code: 'NOTE' }
496
- )
497
- assert.deepEqual(
498
- parseRumorContent({ kind: 9001, content: JSON.stringify(['hello', 'NOTE']) }),
499
- ['hello', 'NOTE']
500
- )
501
- })