libp2r2p 0.0.1 → 0.0.2
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 +68 -21
- package/package.json +1 -1
- package/private-channel/index.js +3 -3
- package/private-message/index.js +65 -39
- package/private-messenger/index.js +35 -11
- package/relay/helpers/publish.js +1 -2
- package/relay/services/relay-pool.js +2 -5
- package/tests/private-channel.test.js +19 -6
- package/tests/private-message.test.js +63 -22
- package/tests/private-messenger.test.js +44 -22
- package/tests/relay-pool.test.js +0 -4
package/README.md
CHANGED
|
@@ -44,40 +44,87 @@ await messenger.tell({
|
|
|
44
44
|
|
|
45
45
|
### Deleting Private Broadcasts
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
logical message.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
By default, each high-level private-message send creates one fresh deletion
|
|
48
|
+
keypair for its logical message. Every outer kind `3560` event produced for that
|
|
49
|
+
send, including router chunks, recipient subsets, and nym carriers, carries the
|
|
50
|
+
same public key in its `s` tag. The result always contains `delivery.reports`.
|
|
51
|
+
When libp2r2p generated the keypair, it also contains
|
|
52
|
+
`delivery.deletionSeckey`; the public key can be derived from that secret.
|
|
53
|
+
|
|
54
|
+
This shared `s` value deliberately makes the outer events for one logical send
|
|
55
|
+
linkable to relay operators and other observers. Disable automatic capabilities
|
|
56
|
+
when that tradeoff is not acceptable. The messenger-wide setting defaults to
|
|
57
|
+
`true`, and a channel setting takes precedence:
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
```js
|
|
60
|
+
const messenger = await createPrivateMessenger({
|
|
61
|
+
userSigner,
|
|
62
|
+
autoDeletionCapability: false,
|
|
63
|
+
channels: [{
|
|
64
|
+
signer: privateChannelSigner,
|
|
65
|
+
relays: ['wss://relay.example'],
|
|
66
|
+
autoDeletionCapability: true
|
|
67
|
+
}]
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With automatic capabilities disabled and no caller-supplied key, the outer
|
|
72
|
+
events have no `s` tag. They are not deliberately linkable through this
|
|
73
|
+
extension, but cannot later be deleted with it. A caller that already owns a
|
|
74
|
+
deletion key can supply its public key on an individual send; libp2r2p then
|
|
75
|
+
does not generate or return a key. Use a fresh caller-owned key for each
|
|
76
|
+
logical message unless cross-message linkability is intentional:
|
|
56
77
|
|
|
57
78
|
```js
|
|
58
79
|
import { generateKeypair } from 'libp2r2p/key'
|
|
59
80
|
|
|
60
81
|
const deletionKey = generateKeypair()
|
|
61
|
-
|
|
82
|
+
await messenger.tell({
|
|
62
83
|
receiverPubkey,
|
|
63
84
|
payload: { text: 'remove this later' },
|
|
64
85
|
deletionPubkey: deletionKey.pubkey
|
|
65
86
|
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { finalizeEvent } from 'nostr-tools'
|
|
91
|
+
import { keypairFromSeckey } from 'libp2r2p/key'
|
|
92
|
+
import { relayPool } from 'libp2r2p/relay'
|
|
93
|
+
|
|
94
|
+
const sent = await messenger.tell({
|
|
95
|
+
receiverPubkey,
|
|
96
|
+
payload: { text: 'remove this later' }
|
|
97
|
+
})
|
|
66
98
|
|
|
67
|
-
|
|
68
|
-
|
|
99
|
+
if (sent.delivery.deletionSeckey) {
|
|
100
|
+
const deletionKey = keypairFromSeckey(sent.delivery.deletionSeckey)
|
|
101
|
+
// Persist this secret with the application's copy of the logical message.
|
|
102
|
+
|
|
103
|
+
const { result: outerEvents } = await relayPool.getEvents({
|
|
104
|
+
kinds: [3560],
|
|
105
|
+
authors: [channelPubkey],
|
|
106
|
+
'#s': [deletionKey.pubkey]
|
|
107
|
+
}, relays)
|
|
108
|
+
for (let offset = 0; offset < outerEvents.length; offset += 100) {
|
|
109
|
+
const deletion = finalizeEvent({
|
|
110
|
+
kind: 5,
|
|
111
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
112
|
+
tags: [['k', '3560'], ...outerEvents.slice(offset, offset + 100).map(event => ['e', event.id])],
|
|
113
|
+
content: ''
|
|
114
|
+
}, deletionKey.secretKey)
|
|
115
|
+
await relayPool.sendEvent(deletion, relays)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
69
118
|
```
|
|
70
119
|
|
|
71
|
-
The `s` tag is public metadata
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
signed by the outer event's private-channel key MUST NOT delete a kind `3560`
|
|
80
|
-
event, whether or not that event carries an `s` tag.
|
|
120
|
+
The `s` tag is public metadata and a deletion capability, not the channel key
|
|
121
|
+
or sender identity. libp2r2p does not delete anything automatically.
|
|
122
|
+
|
|
123
|
+
Relay support for this capability is not universal. A relay that implements it
|
|
124
|
+
should accept only a kind `5` request signed by the matching `s` key with exactly
|
|
125
|
+
one `['k', '3560']` tag, explicit matching `e` targets, and no `a` tags. A
|
|
126
|
+
regular NIP-09 kind `5` request signed by the outer event's private-channel key
|
|
127
|
+
must not delete a kind `3560` event, whether or not that event has an `s` tag.
|
|
81
128
|
|
|
82
129
|
### Temporary Send Storage
|
|
83
130
|
|
package/package.json
CHANGED
package/private-channel/index.js
CHANGED
|
@@ -527,13 +527,13 @@ export async function publish ({ senderSigner, imkcSigner, privateChannelSigner
|
|
|
527
527
|
} finally {
|
|
528
528
|
cleanupEnvelopeRows(context.preparedRows)
|
|
529
529
|
}
|
|
530
|
-
return
|
|
530
|
+
return results
|
|
531
531
|
}
|
|
532
532
|
|
|
533
533
|
for await (const wrappedEvent of wrapEvents({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag, deletionPubkey: normalizedDeletionPubkey, event, expirationSeconds, temporaryStorageArea, _getIykcProofs })) {
|
|
534
534
|
results.push(await _publish(wrappedEvent, withRecoveryRelays(relays, recoveryRelays)))
|
|
535
535
|
}
|
|
536
|
-
return
|
|
536
|
+
return results
|
|
537
537
|
}
|
|
538
538
|
|
|
539
539
|
export async function publishNymEvent ({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey, event, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish = sendToRelays }) {
|
|
@@ -543,7 +543,7 @@ export async function publishNymEvent ({ nymSigner, privateChannelSigner, privat
|
|
|
543
543
|
for await (const wrappedEvent of wrapNymEvents({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: normalizedDeletionPubkey, event, expirationSeconds })) {
|
|
544
544
|
results.push(await _publish(wrappedEvent, publishRelays))
|
|
545
545
|
}
|
|
546
|
-
return
|
|
546
|
+
return results
|
|
547
547
|
}
|
|
548
548
|
|
|
549
549
|
function readSignerFromMap (signersByPubkey, pubkey) {
|
package/private-message/index.js
CHANGED
|
@@ -21,22 +21,32 @@ function uniq (values) {
|
|
|
21
21
|
return [...new Set((values || []).filter(Boolean))]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
if (deletionPubkey
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
return { deletionPubkey: deletionPubkey.toLowerCase() }
|
|
24
|
+
function normalizeDeletionPubkey (deletionPubkey) {
|
|
25
|
+
if (deletionPubkey === undefined) return undefined
|
|
26
|
+
if (typeof deletionPubkey !== 'string' || !HEX_PUBKEY.test(deletionPubkey)) {
|
|
27
|
+
throw new Error('INVALID_DELETION_PUBKEY')
|
|
30
28
|
}
|
|
29
|
+
return deletionPubkey.toLowerCase()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveDeletionCapability ({ deletionPubkey, deletionSeckey, autoDeletionCapability = true }) {
|
|
33
|
+
if (deletionSeckey !== undefined) throw new Error('DELETION_SECKEY_NOT_ACCEPTED')
|
|
34
|
+
if (typeof autoDeletionCapability !== 'boolean') throw new Error('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
|
|
35
|
+
|
|
36
|
+
const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
|
|
37
|
+
if (normalizedDeletionPubkey) return { deletionPubkey: normalizedDeletionPubkey }
|
|
38
|
+
if (!autoDeletionCapability) return {}
|
|
39
|
+
|
|
31
40
|
const { pubkey, seckey } = generateKeypair()
|
|
32
41
|
return { deletionPubkey: pubkey, deletionSeckey: seckey }
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
function
|
|
44
|
+
function withDelivery (result, reports, deletionSeckey) {
|
|
45
|
+
const delivery = { reports }
|
|
46
|
+
if (deletionSeckey !== undefined) delivery.deletionSeckey = deletionSeckey
|
|
36
47
|
return {
|
|
37
48
|
...result,
|
|
38
|
-
|
|
39
|
-
...(capability.deletionSeckey && { deletionSeckey: capability.deletionSeckey })
|
|
49
|
+
delivery
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -457,8 +467,10 @@ export async function ask ({
|
|
|
457
467
|
content,
|
|
458
468
|
expirationSeconds,
|
|
459
469
|
temporaryStorageArea,
|
|
460
|
-
deletionPubkey,
|
|
461
470
|
_getIykcProofs,
|
|
471
|
+
deletionPubkey,
|
|
472
|
+
deletionSeckey,
|
|
473
|
+
autoDeletionCapability = true,
|
|
462
474
|
_publish = privateChannel.publish
|
|
463
475
|
}) {
|
|
464
476
|
if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
|
|
@@ -474,10 +486,10 @@ export async function ask ({
|
|
|
474
486
|
message: message || { code, payload, error, content }
|
|
475
487
|
})
|
|
476
488
|
})
|
|
477
|
-
const
|
|
478
|
-
const
|
|
489
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
490
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers: [receiverPubkey], receiverTag: receiverPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
479
491
|
|
|
480
|
-
return
|
|
492
|
+
return withDelivery({ question }, reports, deletion.deletionSeckey)
|
|
481
493
|
}
|
|
482
494
|
|
|
483
495
|
export async function reply ({
|
|
@@ -497,8 +509,10 @@ export async function reply ({
|
|
|
497
509
|
content,
|
|
498
510
|
expirationSeconds,
|
|
499
511
|
temporaryStorageArea,
|
|
500
|
-
deletionPubkey,
|
|
501
512
|
_getIykcProofs,
|
|
513
|
+
deletionPubkey,
|
|
514
|
+
deletionSeckey,
|
|
515
|
+
autoDeletionCapability = true,
|
|
502
516
|
_publish = privateChannel.publish
|
|
503
517
|
}) {
|
|
504
518
|
if (!question?.id) throw new Error('QUESTION_REQUIRED')
|
|
@@ -511,9 +525,9 @@ export async function reply ({
|
|
|
511
525
|
message: message || { code, payload, error, content }
|
|
512
526
|
})
|
|
513
527
|
})
|
|
514
|
-
const
|
|
515
|
-
const
|
|
516
|
-
return
|
|
528
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
529
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers: [receiverPubkey], receiverTag: receiverPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
530
|
+
return withDelivery({ reply: event }, reports, deletion.deletionSeckey)
|
|
517
531
|
}
|
|
518
532
|
|
|
519
533
|
export async function tell ({
|
|
@@ -532,8 +546,10 @@ export async function tell ({
|
|
|
532
546
|
content,
|
|
533
547
|
expirationSeconds,
|
|
534
548
|
temporaryStorageArea,
|
|
535
|
-
deletionPubkey,
|
|
536
549
|
_getIykcProofs,
|
|
550
|
+
deletionPubkey,
|
|
551
|
+
deletionSeckey,
|
|
552
|
+
autoDeletionCapability = true,
|
|
537
553
|
_publish = privateChannel.publish
|
|
538
554
|
}) {
|
|
539
555
|
if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
|
|
@@ -545,9 +561,9 @@ export async function tell ({
|
|
|
545
561
|
message: message || { code, payload, error, content }
|
|
546
562
|
})
|
|
547
563
|
})
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
return
|
|
564
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
565
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers: [receiverPubkey], receiverTag: receiverPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
566
|
+
return withDelivery({ tell: event }, reports, deletion.deletionSeckey)
|
|
551
567
|
}
|
|
552
568
|
|
|
553
569
|
export async function yell ({
|
|
@@ -566,8 +582,10 @@ export async function yell ({
|
|
|
566
582
|
content,
|
|
567
583
|
expirationSeconds,
|
|
568
584
|
temporaryStorageArea,
|
|
569
|
-
deletionPubkey,
|
|
570
585
|
_getIykcProofs,
|
|
586
|
+
deletionPubkey,
|
|
587
|
+
deletionSeckey,
|
|
588
|
+
autoDeletionCapability = true,
|
|
571
589
|
_publish = privateChannel.publish
|
|
572
590
|
}) {
|
|
573
591
|
const receivers = uniq(receiverPubkeys)
|
|
@@ -580,9 +598,9 @@ export async function yell ({
|
|
|
580
598
|
message: message || { code, payload, error, content }
|
|
581
599
|
})
|
|
582
600
|
})
|
|
583
|
-
const
|
|
584
|
-
const
|
|
585
|
-
return
|
|
601
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
602
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag: '', deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
603
|
+
return withDelivery({ yell: event }, reports, deletion.deletionSeckey)
|
|
586
604
|
}
|
|
587
605
|
|
|
588
606
|
export async function broadcastRumor ({
|
|
@@ -597,16 +615,18 @@ export async function broadcastRumor ({
|
|
|
597
615
|
rumor,
|
|
598
616
|
expirationSeconds,
|
|
599
617
|
temporaryStorageArea,
|
|
600
|
-
deletionPubkey,
|
|
601
618
|
_getIykcProofs,
|
|
619
|
+
deletionPubkey,
|
|
620
|
+
deletionSeckey,
|
|
621
|
+
autoDeletionCapability = true,
|
|
602
622
|
_publish = privateChannel.publish
|
|
603
623
|
}) {
|
|
604
624
|
const receivers = uniq(receiverPubkeys)
|
|
605
625
|
if (!receivers.length) throw new Error('NO_RECEIVERS')
|
|
606
626
|
const { event, wireEvent } = await makeOutgoingRumor({ senderSigner, rumor })
|
|
607
|
-
const
|
|
608
|
-
const
|
|
609
|
-
return
|
|
627
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
628
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag: '', deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
629
|
+
return withDelivery({ rumor: event }, reports, deletion.deletionSeckey)
|
|
610
630
|
}
|
|
611
631
|
|
|
612
632
|
export async function broadcastEvent ({
|
|
@@ -621,16 +641,18 @@ export async function broadcastEvent ({
|
|
|
621
641
|
event,
|
|
622
642
|
expirationSeconds,
|
|
623
643
|
temporaryStorageArea,
|
|
624
|
-
deletionPubkey,
|
|
625
644
|
_getIykcProofs,
|
|
645
|
+
deletionPubkey,
|
|
646
|
+
deletionSeckey,
|
|
647
|
+
autoDeletionCapability = true,
|
|
626
648
|
_publish = privateChannel.publish
|
|
627
649
|
}) {
|
|
628
650
|
const receivers = uniq(receiverPubkeys)
|
|
629
651
|
if (!receivers.length) throw new Error('NO_RECEIVERS')
|
|
630
652
|
const wireEvent = assertValidSignedEvent({ ...event, tags: cloneTags(event?.tags) })
|
|
631
|
-
const
|
|
632
|
-
const
|
|
633
|
-
return
|
|
653
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
654
|
+
const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag: '', deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
|
|
655
|
+
return withDelivery({ event: wireEvent }, reports, deletion.deletionSeckey)
|
|
634
656
|
}
|
|
635
657
|
|
|
636
658
|
export async function broadcastNymRumor ({
|
|
@@ -643,13 +665,15 @@ export async function broadcastNymRumor ({
|
|
|
643
665
|
rumor,
|
|
644
666
|
expirationSeconds,
|
|
645
667
|
deletionPubkey,
|
|
668
|
+
deletionSeckey,
|
|
669
|
+
autoDeletionCapability = true,
|
|
646
670
|
_publish = privateChannel.publishNymEvent
|
|
647
671
|
}) {
|
|
648
672
|
if (!nymSigner?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
|
|
649
673
|
const { event, wireEvent } = await makeOutgoingRumor({ senderSigner: nymSigner, rumor })
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
return
|
|
674
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
675
|
+
const reports = await sendNymMessage({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish })
|
|
676
|
+
return withDelivery({ rumor: event }, reports, deletion.deletionSeckey)
|
|
653
677
|
}
|
|
654
678
|
|
|
655
679
|
export async function broadcastNymEvent ({
|
|
@@ -662,11 +686,13 @@ export async function broadcastNymEvent ({
|
|
|
662
686
|
event,
|
|
663
687
|
expirationSeconds,
|
|
664
688
|
deletionPubkey,
|
|
689
|
+
deletionSeckey,
|
|
690
|
+
autoDeletionCapability = true,
|
|
665
691
|
_publish = privateChannel.publishNymEvent
|
|
666
692
|
}) {
|
|
667
693
|
if (!nymSigner?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
|
|
668
694
|
const wireEvent = assertValidSignedEvent({ ...event, tags: cloneTags(event?.tags) })
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
return
|
|
695
|
+
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
696
|
+
const reports = await sendNymMessage({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish })
|
|
697
|
+
return withDelivery({ event: wireEvent }, reports, deletion.deletionSeckey)
|
|
672
698
|
}
|
|
@@ -112,6 +112,11 @@ function uniq (values) {
|
|
|
112
112
|
return [...new Set((values || []).filter(Boolean))]
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function normalizeAutoDeletionCapability (value) {
|
|
116
|
+
if (typeof value !== 'boolean') throw new Error('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
|
|
117
|
+
return value
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
function isPlainObject (value) {
|
|
116
121
|
return value && typeof value === 'object' && !Array.isArray(value)
|
|
117
122
|
}
|
|
@@ -140,6 +145,7 @@ export class PrivateMessenger {
|
|
|
140
145
|
messageQueueMaxBytes = DEFAULT_MESSAGE_QUEUE_MAX_BYTES,
|
|
141
146
|
seedQueueMaxBytes = DEFAULT_SEED_QUEUE_MAX_BYTES,
|
|
142
147
|
temporaryStorageArea = globalThis.sessionStorage,
|
|
148
|
+
autoDeletionCapability = true,
|
|
143
149
|
_indexedDB = globalThis.indexedDB,
|
|
144
150
|
useContentKeys = true,
|
|
145
151
|
onContentKeyChange,
|
|
@@ -165,6 +171,7 @@ export class PrivateMessenger {
|
|
|
165
171
|
this.messageQueueMaxBytes = messageQueueMaxBytes
|
|
166
172
|
this.seedQueueMaxBytes = seedQueueMaxBytes
|
|
167
173
|
this.temporaryStorageArea = temporaryStorageArea
|
|
174
|
+
this.autoDeletionCapability = normalizeAutoDeletionCapability(autoDeletionCapability)
|
|
168
175
|
this._indexedDB = _indexedDB
|
|
169
176
|
this.useContentKeys = useContentKeys
|
|
170
177
|
this.onContentKeyChange = onContentKeyChange
|
|
@@ -304,6 +311,9 @@ export class PrivateMessenger {
|
|
|
304
311
|
const mode = channel.mode || defaults.mode || 'leecher'
|
|
305
312
|
if (!signer && storesRecoverySeeds(mode)) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
|
|
306
313
|
const readerPubkey = channel.readerPubkey || channel.privateChannelReaderPubkey || await readerSigner?.getPublicKey?.() || pubkey
|
|
314
|
+
const autoDeletionCapability = channel.autoDeletionCapability === undefined
|
|
315
|
+
? undefined
|
|
316
|
+
: normalizeAutoDeletionCapability(channel.autoDeletionCapability)
|
|
307
317
|
out.push({
|
|
308
318
|
pubkey,
|
|
309
319
|
signer,
|
|
@@ -314,7 +324,8 @@ export class PrivateMessenger {
|
|
|
314
324
|
sendRelays: uniq(hasChannelSendRelays ? channel.sendRelays : []),
|
|
315
325
|
usesNip65WatchRelays: !hasChannelRelays && !hasDefaultRelays,
|
|
316
326
|
mode,
|
|
317
|
-
seeders: uniq(channel.seeders)
|
|
327
|
+
seeders: uniq(channel.seeders),
|
|
328
|
+
autoDeletionCapability
|
|
318
329
|
})
|
|
319
330
|
}
|
|
320
331
|
return out
|
|
@@ -807,12 +818,13 @@ export class PrivateMessenger {
|
|
|
807
818
|
...routing,
|
|
808
819
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
809
820
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
821
|
+
deletionPubkey,
|
|
822
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
810
823
|
message,
|
|
811
824
|
code,
|
|
812
825
|
payload,
|
|
813
826
|
error,
|
|
814
827
|
content,
|
|
815
|
-
deletionPubkey,
|
|
816
828
|
_getIykcProofs: this.contentKeyLookup()
|
|
817
829
|
})
|
|
818
830
|
}
|
|
@@ -832,12 +844,13 @@ export class PrivateMessenger {
|
|
|
832
844
|
...routing,
|
|
833
845
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
834
846
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
847
|
+
deletionPubkey,
|
|
848
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
835
849
|
message,
|
|
836
850
|
code,
|
|
837
851
|
payload,
|
|
838
852
|
error,
|
|
839
853
|
content,
|
|
840
|
-
deletionPubkey,
|
|
841
854
|
_getIykcProofs: this.contentKeyLookup()
|
|
842
855
|
})
|
|
843
856
|
}
|
|
@@ -855,12 +868,13 @@ export class PrivateMessenger {
|
|
|
855
868
|
...routing,
|
|
856
869
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
857
870
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
871
|
+
deletionPubkey,
|
|
872
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
858
873
|
message,
|
|
859
874
|
code,
|
|
860
875
|
payload,
|
|
861
876
|
error,
|
|
862
877
|
content,
|
|
863
|
-
deletionPubkey,
|
|
864
878
|
_getIykcProofs: this.contentKeyLookup()
|
|
865
879
|
})
|
|
866
880
|
}
|
|
@@ -878,12 +892,13 @@ export class PrivateMessenger {
|
|
|
878
892
|
...routing,
|
|
879
893
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
880
894
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
895
|
+
deletionPubkey,
|
|
896
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
881
897
|
message,
|
|
882
898
|
code,
|
|
883
899
|
payload,
|
|
884
900
|
error,
|
|
885
901
|
content,
|
|
886
|
-
deletionPubkey,
|
|
887
902
|
_getIykcProofs: this.contentKeyLookup()
|
|
888
903
|
})
|
|
889
904
|
}
|
|
@@ -901,8 +916,9 @@ export class PrivateMessenger {
|
|
|
901
916
|
...routing,
|
|
902
917
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
903
918
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
904
|
-
rumor,
|
|
905
919
|
deletionPubkey,
|
|
920
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
921
|
+
rumor,
|
|
906
922
|
_getIykcProofs: this.contentKeyLookup()
|
|
907
923
|
})
|
|
908
924
|
}
|
|
@@ -920,8 +936,9 @@ export class PrivateMessenger {
|
|
|
920
936
|
...routing,
|
|
921
937
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
922
938
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
923
|
-
event,
|
|
924
939
|
deletionPubkey,
|
|
940
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
941
|
+
event,
|
|
925
942
|
_getIykcProofs: this.contentKeyLookup()
|
|
926
943
|
})
|
|
927
944
|
}
|
|
@@ -937,8 +954,9 @@ export class PrivateMessenger {
|
|
|
937
954
|
privateChannelReaderPubkey: channel.readerPubkey,
|
|
938
955
|
...routing,
|
|
939
956
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
940
|
-
|
|
941
|
-
|
|
957
|
+
deletionPubkey,
|
|
958
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
959
|
+
rumor
|
|
942
960
|
})
|
|
943
961
|
}
|
|
944
962
|
|
|
@@ -953,8 +971,9 @@ export class PrivateMessenger {
|
|
|
953
971
|
privateChannelReaderPubkey: channel.readerPubkey,
|
|
954
972
|
...routing,
|
|
955
973
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
956
|
-
|
|
957
|
-
|
|
974
|
+
deletionPubkey,
|
|
975
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
976
|
+
event
|
|
958
977
|
})
|
|
959
978
|
}
|
|
960
979
|
|
|
@@ -972,6 +991,7 @@ export class PrivateMessenger {
|
|
|
972
991
|
...routing,
|
|
973
992
|
expirationSeconds: this.offlineRecoverySeconds,
|
|
974
993
|
temporaryStorageArea: this.temporaryStorageArea,
|
|
994
|
+
autoDeletionCapability: this.autoDeletionCapabilityFor(channel),
|
|
975
995
|
code: SEEDER_PRESENCE_CODE,
|
|
976
996
|
payload: {},
|
|
977
997
|
_getIykcProofs: this.contentKeyLookup()
|
|
@@ -1036,6 +1056,10 @@ export class PrivateMessenger {
|
|
|
1036
1056
|
return channel
|
|
1037
1057
|
}
|
|
1038
1058
|
|
|
1059
|
+
autoDeletionCapabilityFor (channel) {
|
|
1060
|
+
return channel.autoDeletionCapability ?? this.autoDeletionCapability
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1039
1063
|
requireNymSigner (channel, override) {
|
|
1040
1064
|
const signer = override || channel?.nymSigner || this.nymSigner
|
|
1041
1065
|
if (!signer?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
|
package/relay/helpers/publish.js
CHANGED
|
@@ -79,7 +79,7 @@ export function createPublishSettlements (promises, timeout, { onSettled } = {})
|
|
|
79
79
|
|
|
80
80
|
// Turns ordered relay settlements into a stable, caller-facing report. Failed
|
|
81
81
|
// relays retain their Error while optional successful URLs make acknowledgements visible.
|
|
82
|
-
export function publishSummary (settlements, relays, {
|
|
82
|
+
export function publishSummary (settlements, relays, { includeSucceededRelays = false } = {}) {
|
|
83
83
|
const succeededRelays = []
|
|
84
84
|
const errors = []
|
|
85
85
|
|
|
@@ -94,7 +94,6 @@ export function publishSummary (settlements, relays, { result, includeSucceededR
|
|
|
94
94
|
fulfilled: succeededRelays.length,
|
|
95
95
|
errors
|
|
96
96
|
}
|
|
97
|
-
if (result !== undefined) summary.result = result
|
|
98
97
|
if (includeSucceededRelays) summary.succeededRelays = succeededRelays
|
|
99
98
|
return summary
|
|
100
99
|
}
|
|
@@ -842,10 +842,9 @@ export class RelayPool {
|
|
|
842
842
|
const urls = normalizedRelayUrls(relays)
|
|
843
843
|
if (!urls.length) {
|
|
844
844
|
const promise = Promise.resolve(publishSummary([], urls, {
|
|
845
|
-
result: null,
|
|
846
845
|
includeSucceededRelays: true
|
|
847
846
|
}))
|
|
848
|
-
return {
|
|
847
|
+
return { total: 0, success: false, promise }
|
|
849
848
|
}
|
|
850
849
|
|
|
851
850
|
const eventToSend = event.meta ? { ...event } : event
|
|
@@ -862,11 +861,10 @@ export class RelayPool {
|
|
|
862
861
|
})
|
|
863
862
|
|
|
864
863
|
// Resolves after every relay settles (or reaches the operation timeout) as
|
|
865
|
-
// {
|
|
864
|
+
// { success, total, fulfilled, succeededRelays, errors },
|
|
866
865
|
// where errors contains { relay, reason } entries for failed relays.
|
|
867
866
|
const promise = settlement.promise
|
|
868
867
|
.then(settlements => publishSummary(settlements, urls, {
|
|
869
|
-
result: null,
|
|
870
868
|
includeSucceededRelays: true
|
|
871
869
|
}))
|
|
872
870
|
|
|
@@ -895,7 +893,6 @@ export class RelayPool {
|
|
|
895
893
|
if (!success) settlement.timeout()
|
|
896
894
|
|
|
897
895
|
return {
|
|
898
|
-
result: null,
|
|
899
896
|
total: urls.length,
|
|
900
897
|
success,
|
|
901
898
|
promise
|
|
@@ -445,7 +445,7 @@ test('wrapEvent creates private broadcast events under relay event size limit',
|
|
|
445
445
|
assert.equal(globalThis.sessionStorage.getItem(TEMPORARY_STORAGE_KEYS_KEY), null)
|
|
446
446
|
})
|
|
447
447
|
|
|
448
|
-
test('private broadcast wrappers
|
|
448
|
+
test('private broadcast wrappers share and normalize one deletion pubkey per logical message', async () => {
|
|
449
449
|
const alice = signer()
|
|
450
450
|
const bob = signer()
|
|
451
451
|
const bobPubkey = await bob.getPublicKey()
|
|
@@ -477,6 +477,7 @@ test('private broadcast wrappers add a normalized deletion pubkey when supplied'
|
|
|
477
477
|
/INVALID_DELETION_PUBKEY/
|
|
478
478
|
)
|
|
479
479
|
}
|
|
480
|
+
|
|
480
481
|
})
|
|
481
482
|
|
|
482
483
|
test('publish splits relay-targeted routers by receiver subset with separate router pubkeys', async () => {
|
|
@@ -500,7 +501,7 @@ test('publish splits relay-targeted routers by receiver subset with separate rou
|
|
|
500
501
|
}
|
|
501
502
|
}
|
|
502
503
|
|
|
503
|
-
await publish({
|
|
504
|
+
const reports = await publish({
|
|
504
505
|
senderSigner: alice,
|
|
505
506
|
receivers: [bobPubkey, carolPubkey, davePubkey],
|
|
506
507
|
relayToReceivers: new Map([
|
|
@@ -524,6 +525,7 @@ test('publish splits relay-targeted routers by receiver subset with separate rou
|
|
|
524
525
|
['s', 'd'.repeat(64)],
|
|
525
526
|
['s', 'd'.repeat(64)]
|
|
526
527
|
])
|
|
528
|
+
assert.deepEqual(reports, [{ success: true }, { success: true }])
|
|
527
529
|
assert.deepEqual(published[0].relays, ['wss://one.example', 'wss://two.example', 'wss://seed.example'])
|
|
528
530
|
assert.deepEqual(published[1].relays, ['wss://three.example', 'wss://seed.example', 'wss://one.example'])
|
|
529
531
|
|
|
@@ -626,7 +628,7 @@ test('publishNymEvent mirrors carrier chunks to recovery relays', async () => {
|
|
|
626
628
|
const nym = signer()
|
|
627
629
|
const published = []
|
|
628
630
|
|
|
629
|
-
await publishNymEvent({
|
|
631
|
+
const reports = await publishNymEvent({
|
|
630
632
|
nymSigner: nym,
|
|
631
633
|
privateChannelSigner: channel,
|
|
632
634
|
event: eventFixture('nym mirror'),
|
|
@@ -642,6 +644,7 @@ test('publishNymEvent mirrors carrier chunks to recovery relays', async () => {
|
|
|
642
644
|
assert.equal(published.length, 1)
|
|
643
645
|
assert.deepEqual(published[0].relays, ['wss://receiver.example', 'wss://seed.example'])
|
|
644
646
|
assert.deepEqual(published[0].outer.tags.find(tag => tag[0] === 's'), ['s', 'e'.repeat(64)])
|
|
647
|
+
assert.deepEqual(reports, [{ success: true }])
|
|
645
648
|
assert.equal(globalThis.sessionStorage.getItem(TEMPORARY_STORAGE_KEYS_KEY), null)
|
|
646
649
|
})
|
|
647
650
|
|
|
@@ -955,6 +958,7 @@ test('subscribe buffers out-of-order nym carrier chunks by nym pubkey and inner
|
|
|
955
958
|
const wrapped = await wrapNymEvent({
|
|
956
959
|
nymSigner: nym,
|
|
957
960
|
privateChannelSigner: channel,
|
|
961
|
+
deletionPubkey: 'e'.repeat(64),
|
|
958
962
|
event: original
|
|
959
963
|
})
|
|
960
964
|
const routedEvents = []
|
|
@@ -963,6 +967,7 @@ test('subscribe buffers out-of-order nym carrier chunks by nym pubkey and inner
|
|
|
963
967
|
|
|
964
968
|
assert.ok(wrapped.length > 1)
|
|
965
969
|
for (const event of wrapped) assert.ok(new TextEncoder().encode(JSON.stringify(event)).length <= MAX_EVENT_BYTES)
|
|
970
|
+
assert.deepEqual(new Set(wrapped.map(event => event.tags.find(tag => tag[0] === 's')?.[1])), new Set(['e'.repeat(64)]))
|
|
966
971
|
|
|
967
972
|
subscribe({
|
|
968
973
|
privateChannelSigner: channel,
|
|
@@ -1563,14 +1568,22 @@ test('wrapEvent chunks large jsonl without oversize events and unwraps reassembl
|
|
|
1563
1568
|
const imkcPubkey = await imkc.getPublicKey()
|
|
1564
1569
|
const original = eventFixture('x'.repeat(getJsonlChunkByteSize()))
|
|
1565
1570
|
NsecSigner.setContentSigners(alice, [imkc])
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1571
|
+
const wrapped = await wrapEvent({
|
|
1572
|
+
senderSigner: alice,
|
|
1573
|
+
imkcSigner: imkc,
|
|
1574
|
+
receivers: [bobPubkey],
|
|
1575
|
+
deletionPubkey: 'd'.repeat(64),
|
|
1576
|
+
event: original,
|
|
1577
|
+
_getIykcProofs: noContentKeys
|
|
1578
|
+
})
|
|
1568
1579
|
|
|
1569
1580
|
assert.ok(wrapped.length > 1)
|
|
1581
|
+
const deletionPubkeys = new Set()
|
|
1570
1582
|
for (const event of wrapped) {
|
|
1571
1583
|
assert.ok(new TextEncoder().encode(JSON.stringify(event)).length <= MAX_EVENT_BYTES)
|
|
1572
|
-
|
|
1584
|
+
deletionPubkeys.add(event.tags.find(tag => tag[0] === 's')?.[1])
|
|
1573
1585
|
}
|
|
1586
|
+
assert.deepEqual(deletionPubkeys, new Set(['d'.repeat(64)]))
|
|
1574
1587
|
|
|
1575
1588
|
const routers = []
|
|
1576
1589
|
for (const event of wrapped) {
|
|
@@ -146,7 +146,7 @@ test('ask requires watching the sender private channel first', async () => {
|
|
|
146
146
|
receiverPubkey: 'receiver',
|
|
147
147
|
relays: ['wss://relay.example'],
|
|
148
148
|
message: { code: 'PING' },
|
|
149
|
-
_publish: async () =>
|
|
149
|
+
_publish: async () => []
|
|
150
150
|
}),
|
|
151
151
|
/PRIVATE_MESSAGE_NOT_WATCHING/
|
|
152
152
|
)
|
|
@@ -170,7 +170,7 @@ test('ask from a reader-only channel requires a writer signer', async () => {
|
|
|
170
170
|
receiverPubkey: 'receiver',
|
|
171
171
|
relays: ['wss://relay.example'],
|
|
172
172
|
message: { code: 'PING' },
|
|
173
|
-
_publish: async () =>
|
|
173
|
+
_publish: async () => []
|
|
174
174
|
}),
|
|
175
175
|
/PRIVATE_CHANNEL_WRITER_REQUIRED/
|
|
176
176
|
)
|
|
@@ -220,7 +220,7 @@ test('ask publishes an ask rumor and watch dispatches the reply with its questio
|
|
|
220
220
|
message: { code: 'PING', payload: { ok: true } },
|
|
221
221
|
_publish: async options => {
|
|
222
222
|
published = options
|
|
223
|
-
return
|
|
223
|
+
return [{ success: true }]
|
|
224
224
|
}
|
|
225
225
|
})
|
|
226
226
|
|
|
@@ -236,6 +236,8 @@ test('ask publishes an ask rumor and watch dispatches the reply with its questio
|
|
|
236
236
|
assert.deepEqual(published.event.tags, [['r', 'receiver'], ['h', 'PING']])
|
|
237
237
|
assert.equal(result.question.pubkey, senderPubkey)
|
|
238
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)
|
|
239
241
|
assert.throws(() => getEventHash(published.event), /wrong or missing properties/)
|
|
240
242
|
|
|
241
243
|
calls[0].onEvent({
|
|
@@ -259,7 +261,7 @@ test('reply tell and yell publish recognizable private message rumors', async ()
|
|
|
259
261
|
const bobPubkey = pubkeyFixture(3)
|
|
260
262
|
const _publish = async options => {
|
|
261
263
|
published.push(options)
|
|
262
|
-
return
|
|
264
|
+
return []
|
|
263
265
|
}
|
|
264
266
|
const question = {
|
|
265
267
|
id: 'question-id',
|
|
@@ -321,37 +323,49 @@ test('reply tell and yell publish recognizable private message rumors', async ()
|
|
|
321
323
|
assert.deepEqual(signedResult.event, signedEvent)
|
|
322
324
|
})
|
|
323
325
|
|
|
324
|
-
test('private message sends
|
|
326
|
+
test('private message sends share one deletion capability and return delivery reports', async () => {
|
|
325
327
|
const published = []
|
|
328
|
+
const reports = [{ success: true, total: 1 }, { success: false, total: 1 }]
|
|
326
329
|
const _publish = async options => {
|
|
327
330
|
published.push(options)
|
|
328
|
-
return
|
|
331
|
+
return reports
|
|
329
332
|
}
|
|
330
|
-
const suppliedPubkey = pubkeyFixture(99).toUpperCase()
|
|
331
333
|
|
|
332
|
-
const
|
|
334
|
+
const generated = await tell({
|
|
333
335
|
senderSigner: signer(pubkeyFixture(3)),
|
|
334
336
|
receiverPubkey: 'alice',
|
|
335
337
|
relays: ['wss://relay.example'],
|
|
336
|
-
payload: '
|
|
337
|
-
deletionPubkey: suppliedPubkey,
|
|
338
|
+
payload: 'managed',
|
|
338
339
|
_publish
|
|
339
340
|
})
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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',
|
|
343
351
|
relays: ['wss://relay.example'],
|
|
344
|
-
|
|
352
|
+
payload: 'caller-managed',
|
|
353
|
+
deletionPubkey: 'A'.repeat(64),
|
|
345
354
|
_publish
|
|
346
355
|
})
|
|
356
|
+
assert.equal(published[1].deletionPubkey, 'a'.repeat(64))
|
|
357
|
+
assert.deepEqual(supplied.delivery, { reports })
|
|
347
358
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 })
|
|
355
369
|
|
|
356
370
|
await assert.rejects(
|
|
357
371
|
() => tell({
|
|
@@ -364,6 +378,28 @@ test('private message sends return generated deletion keys and forward supplied
|
|
|
364
378
|
}),
|
|
365
379
|
/INVALID_DELETION_PUBKEY/
|
|
366
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
|
+
)
|
|
367
403
|
})
|
|
368
404
|
|
|
369
405
|
test('broadcastRumor validates normalized unsigned rumors before publishing', async () => {
|
|
@@ -416,9 +452,10 @@ test('nym broadcasts publish through the nym channel path without receivers', as
|
|
|
416
452
|
const nymPubkey = pubkeyFixture(6)
|
|
417
453
|
const channelPubkey = pubkeyFixture(7)
|
|
418
454
|
const authorEvent = finalizeEvent({ kind: 9002, created_at: 30, tags: [], content: 'signed by another key' }, generateSecretKey())
|
|
455
|
+
const reports = [{ success: true, total: 1 }]
|
|
419
456
|
const _publish = async options => {
|
|
420
457
|
published.push(options)
|
|
421
|
-
return
|
|
458
|
+
return reports
|
|
422
459
|
}
|
|
423
460
|
|
|
424
461
|
const rumorResult = await broadcastNymRumor({
|
|
@@ -443,9 +480,13 @@ test('nym broadcasts publish through the nym channel path without receivers', as
|
|
|
443
480
|
assert.equal(published[0].event.id, undefined)
|
|
444
481
|
assert.equal(rumorResult.rumor.pubkey, nymPubkey)
|
|
445
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)
|
|
446
485
|
assert.deepEqual(published[1].event, authorEvent)
|
|
447
486
|
assert.notEqual(published[1].event.pubkey, nymPubkey)
|
|
448
487
|
assert.deepEqual(eventResult.event, authorEvent)
|
|
488
|
+
assert.deepEqual(eventResult.delivery.reports, reports)
|
|
489
|
+
assert.equal(keypairFromSeckey(eventResult.delivery.deletionSeckey).pubkey, published[1].deletionPubkey)
|
|
449
490
|
})
|
|
450
491
|
|
|
451
492
|
test('parseRumorContent only reads h tags for private message kinds', () => {
|
|
@@ -76,35 +76,35 @@ function fakePrivateMessage () {
|
|
|
76
76
|
},
|
|
77
77
|
ask: async options => {
|
|
78
78
|
sent.push({ method: 'ask', options })
|
|
79
|
-
return { question: { id: 'question-id', kind: ASK_KIND, pubkey: 'user' },
|
|
79
|
+
return { question: { id: 'question-id', kind: ASK_KIND, pubkey: 'user' }, delivery: { reports: [] } }
|
|
80
80
|
},
|
|
81
81
|
reply: async options => {
|
|
82
82
|
sent.push({ method: 'reply', options })
|
|
83
|
-
return { reply: { id: 'reply-id', kind: REPLY_KIND },
|
|
83
|
+
return { reply: { id: 'reply-id', kind: REPLY_KIND }, delivery: { reports: [] } }
|
|
84
84
|
},
|
|
85
85
|
tell: async options => {
|
|
86
86
|
sent.push({ method: 'tell', options })
|
|
87
|
-
return { tell: { id: 'tell-id', kind: TELL_KIND },
|
|
87
|
+
return { tell: { id: 'tell-id', kind: TELL_KIND }, delivery: { reports: [] } }
|
|
88
88
|
},
|
|
89
89
|
yell: async options => {
|
|
90
90
|
sent.push({ method: 'yell', options })
|
|
91
|
-
return { yell: { id: 'yell-id', kind: TELL_KIND },
|
|
91
|
+
return { yell: { id: 'yell-id', kind: TELL_KIND }, delivery: { reports: [] } }
|
|
92
92
|
},
|
|
93
93
|
broadcastRumor: async options => {
|
|
94
94
|
sent.push({ method: 'broadcastRumor', options })
|
|
95
|
-
return { rumor: { id: 'raw-id', kind: 9001 },
|
|
95
|
+
return { rumor: { id: 'raw-id', kind: 9001 }, delivery: { reports: [] } }
|
|
96
96
|
},
|
|
97
97
|
broadcastEvent: async options => {
|
|
98
98
|
sent.push({ method: 'broadcastEvent', options })
|
|
99
|
-
return { event: options.event,
|
|
99
|
+
return { event: options.event, delivery: { reports: [] } }
|
|
100
100
|
},
|
|
101
101
|
broadcastNymRumor: async options => {
|
|
102
102
|
sent.push({ method: 'broadcastNymRumor', options })
|
|
103
|
-
return { rumor: { id: 'nym-raw-id', kind: 9003, pubkey: options.nymSigner.getPublicKey() },
|
|
103
|
+
return { rumor: { id: 'nym-raw-id', kind: 9003, pubkey: options.nymSigner.getPublicKey() }, delivery: { reports: [] } }
|
|
104
104
|
},
|
|
105
105
|
broadcastNymEvent: async options => {
|
|
106
106
|
sent.push({ method: 'broadcastNymEvent', options })
|
|
107
|
-
return { event: options.event,
|
|
107
|
+
return { event: options.event, delivery: { reports: [] } }
|
|
108
108
|
},
|
|
109
109
|
unwatch: channels => stopped.push(channels),
|
|
110
110
|
clearChannelState: channel => cleared.push(channel)
|
|
@@ -516,35 +516,55 @@ test('private messenger delegates send helpers with scoped signers and relays',
|
|
|
516
516
|
assert.deepEqual(sent.options.relays, ['wss://relay.example'])
|
|
517
517
|
assert.equal(sent.options.expirationSeconds, 7 * 24 * 60 * 60)
|
|
518
518
|
assert.equal(sent.options.temporaryStorageArea, globalThis.localStorage)
|
|
519
|
+
assert.equal(sent.options.autoDeletionCapability, true)
|
|
519
520
|
}
|
|
520
521
|
assert.equal(pm.sent[5].options.event.id, 'signed-id')
|
|
521
522
|
assert.equal(pm.sent[6].options.nymSigner.getPublicKey(), 'global-nym')
|
|
522
523
|
assert.equal(pm.sent[6].options.privateChannelSigner.getPublicKey(), 'channel')
|
|
523
524
|
assert.deepEqual(pm.sent[6].options.relays, ['wss://relay.example'])
|
|
524
525
|
assert.equal(pm.sent[6].options.expirationSeconds, 7 * 24 * 60 * 60)
|
|
526
|
+
assert.equal(pm.sent[6].options.autoDeletionCapability, true)
|
|
525
527
|
assert.equal(pm.sent[7].options.nymSigner.getPublicKey(), 'global-nym')
|
|
526
528
|
assert.equal(pm.sent[7].options.event.pubkey, 'author')
|
|
529
|
+
assert.equal(pm.sent[7].options.autoDeletionCapability, true)
|
|
527
530
|
})
|
|
528
531
|
|
|
529
|
-
test('private messenger
|
|
532
|
+
test('private messenger configures automatic deletion capabilities and forwards caller pubkeys', async () => {
|
|
530
533
|
const pm = fakePrivateMessage()
|
|
531
|
-
const messenger = await new PrivateMessenger({ _privateMessage: pm }).init({
|
|
534
|
+
const messenger = await new PrivateMessenger({ _privateMessage: pm, autoDeletionCapability: true }).init({
|
|
532
535
|
userSigner: signer('user'),
|
|
533
|
-
channels: [
|
|
536
|
+
channels: [
|
|
537
|
+
{ pubkey: 'inherited', signer: signer('inherited'), relays: ['wss://relay.example'] },
|
|
538
|
+
{ pubkey: 'disabled', signer: signer('disabled'), relays: ['wss://relay.example'], autoDeletionCapability: false }
|
|
539
|
+
]
|
|
534
540
|
})
|
|
535
|
-
const regularDeletionPubkey = 'a'.repeat(64)
|
|
536
|
-
const nymDeletionPubkey = 'b'.repeat(64)
|
|
537
541
|
|
|
538
|
-
await messenger.tell({
|
|
539
|
-
await messenger.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
542
|
+
await messenger.tell({ channelPubkey: 'inherited', receiverPubkey: 'alice', payload: 'default' })
|
|
543
|
+
await messenger.tell({ channelPubkey: 'disabled', receiverPubkey: 'alice', payload: 'disabled' })
|
|
544
|
+
await messenger.tell({ channelPubkey: 'disabled', receiverPubkey: 'alice', payload: 'caller-managed', deletionPubkey: 'a'.repeat(64) })
|
|
545
|
+
|
|
546
|
+
assert.equal(pm.sent[0].options.autoDeletionCapability, true)
|
|
547
|
+
assert.equal(pm.sent[1].options.autoDeletionCapability, false)
|
|
548
|
+
assert.equal(pm.sent[2].options.autoDeletionCapability, false)
|
|
549
|
+
assert.equal(pm.sent[2].options.deletionPubkey, 'a'.repeat(64))
|
|
550
|
+
|
|
551
|
+
const enabledPm = fakePrivateMessage()
|
|
552
|
+
const enabledMessenger = await new PrivateMessenger({ _privateMessage: enabledPm, autoDeletionCapability: false }).init({
|
|
553
|
+
userSigner: signer('other-user'),
|
|
554
|
+
channels: [{ pubkey: 'enabled', signer: signer('enabled'), relays: ['wss://relay.example'], autoDeletionCapability: true }]
|
|
544
555
|
})
|
|
556
|
+
await enabledMessenger.tell({ receiverPubkey: 'alice', payload: 'enabled' })
|
|
557
|
+
assert.equal(enabledPm.sent[0].options.autoDeletionCapability, true)
|
|
545
558
|
|
|
546
|
-
|
|
547
|
-
|
|
559
|
+
await messenger.tell({
|
|
560
|
+
channelPubkey: 'inherited',
|
|
561
|
+
receiverPubkey: 'alice',
|
|
562
|
+
payload: 'ignored fields',
|
|
563
|
+
deletionSeckey: 'b'.repeat(64),
|
|
564
|
+
autoDeletionCapability: false
|
|
565
|
+
})
|
|
566
|
+
assert.equal(pm.sent[3].options.deletionPubkey, undefined)
|
|
567
|
+
assert.equal(pm.sent[3].options.autoDeletionCapability, true)
|
|
548
568
|
})
|
|
549
569
|
|
|
550
570
|
test('private messenger update accepts only same-user replacement signers', async () => {
|
|
@@ -1105,18 +1125,20 @@ test('seeder channels publish presence immediately and on interval', async () =>
|
|
|
1105
1125
|
_clearInterval: timer => cleared.push(timer)
|
|
1106
1126
|
}).init({
|
|
1107
1127
|
userSigner: signer('user'),
|
|
1108
|
-
channels: [{ pubkey: 'channel', signer: signer('channel'), relays: ['wss://relay.example'], mode: 'seeder', seeders: ['alice'] }]
|
|
1128
|
+
channels: [{ pubkey: 'channel', signer: signer('channel'), relays: ['wss://relay.example'], mode: 'seeder', seeders: ['alice'], autoDeletionCapability: false }]
|
|
1109
1129
|
})
|
|
1110
1130
|
|
|
1111
1131
|
assert.equal(pm.sent[0].method, 'yell')
|
|
1112
1132
|
assert.equal(pm.sent[0].options.code, SEEDER_PRESENCE_CODE)
|
|
1113
1133
|
assert.deepEqual(pm.sent[0].options.receiverPubkeys, ['alice', 'user'])
|
|
1134
|
+
assert.equal(pm.sent[0].options.autoDeletionCapability, false)
|
|
1114
1135
|
assert.equal(intervals[0].ms, 10 * 60 * 1000)
|
|
1115
1136
|
|
|
1116
1137
|
await intervals[0].fn()
|
|
1117
1138
|
|
|
1118
1139
|
assert.equal(pm.sent[1].method, 'yell')
|
|
1119
1140
|
assert.equal(pm.sent[1].options.code, SEEDER_PRESENCE_CODE)
|
|
1141
|
+
assert.equal(pm.sent[1].options.autoDeletionCapability, false)
|
|
1120
1142
|
|
|
1121
1143
|
messenger.close()
|
|
1122
1144
|
assert.deepEqual(cleared, intervals)
|
package/tests/relay-pool.test.js
CHANGED
|
@@ -1661,7 +1661,6 @@ describe('RelayPool.sendEvent', () => {
|
|
|
1661
1661
|
onRelayResult: result => relayResults.push(result)
|
|
1662
1662
|
})
|
|
1663
1663
|
|
|
1664
|
-
assert.equal(early.result, null)
|
|
1665
1664
|
assert.equal(early.total, 2)
|
|
1666
1665
|
assert.equal(early.success, true)
|
|
1667
1666
|
assert.deepEqual(relayResults, [{
|
|
@@ -1672,7 +1671,6 @@ describe('RelayPool.sendEvent', () => {
|
|
|
1672
1671
|
|
|
1673
1672
|
delayed.reject(new Error('relay failed'))
|
|
1674
1673
|
const full = await early.promise
|
|
1675
|
-
assert.equal(full.result, null)
|
|
1676
1674
|
assert.equal(full.success, true)
|
|
1677
1675
|
assert.equal(full.total, 2)
|
|
1678
1676
|
assert.equal(full.fulfilled, 1)
|
|
@@ -2050,7 +2048,6 @@ describe('RelayPool.sendEvent', () => {
|
|
|
2050
2048
|
const early = await nostr.sendEvent({ id: 'ev1' }, [])
|
|
2051
2049
|
const full = await early.promise
|
|
2052
2050
|
assert.deepEqual(early, {
|
|
2053
|
-
result: null,
|
|
2054
2051
|
total: 0,
|
|
2055
2052
|
success: false,
|
|
2056
2053
|
promise: early.promise
|
|
@@ -2060,7 +2057,6 @@ describe('RelayPool.sendEvent', () => {
|
|
|
2060
2057
|
total: 0,
|
|
2061
2058
|
fulfilled: 0,
|
|
2062
2059
|
errors: [],
|
|
2063
|
-
result: null,
|
|
2064
2060
|
succeededRelays: []
|
|
2065
2061
|
})
|
|
2066
2062
|
})
|