libp2r2p 0.8.0 → 0.10.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.
Files changed (49) hide show
  1. package/README.md +72 -45
  2. package/base16/index.js +6 -3
  3. package/base36/index.js +12 -8
  4. package/base62/index.js +15 -11
  5. package/base64/index.js +18 -2
  6. package/base93/index.js +19 -7
  7. package/content-key/event/index.js +40 -12
  8. package/double-dh/index.js +21 -6
  9. package/ecdh/index.js +12 -1
  10. package/error/index.js +32 -0
  11. package/event/helpers/serialize.js +31 -0
  12. package/event/index.js +116 -0
  13. package/idb/index.js +5 -3
  14. package/idb-queue/index.js +21 -20
  15. package/index.js +10 -1
  16. package/key/index.js +31 -18
  17. package/kind/index.js +244 -0
  18. package/nip04/index.js +47 -0
  19. package/nip05/index.js +61 -0
  20. package/nip19/index.js +176 -43
  21. package/nip44/helpers.js +95 -0
  22. package/nip44/index.js +14 -0
  23. package/nip44-v3/index.js +35 -16
  24. package/nip46/helpers/frame.js +6 -6
  25. package/nip46/helpers/url.js +8 -6
  26. package/nip46/services/bunker-signer.js +7 -6
  27. package/nip46/services/client.js +8 -7
  28. package/nip46/services/server-session.js +8 -7
  29. package/nip46/services/transport.js +9 -8
  30. package/nip96/index.js +285 -0
  31. package/nip98/index.js +56 -0
  32. package/nwt/index.js +241 -0
  33. package/package.json +22 -5
  34. package/private-channel/helpers/chunks.js +7 -6
  35. package/private-channel/helpers/event.js +5 -4
  36. package/private-channel/index.js +63 -61
  37. package/private-channel/services/received-chunks.js +4 -3
  38. package/private-message/index.js +32 -31
  39. package/private-messenger/index.js +23 -22
  40. package/private-messenger/recovery/index.js +15 -14
  41. package/private-messenger/services/channel-state.js +2 -1
  42. package/relay/helpers/hll.js +3 -1
  43. package/relay/services/query.js +3 -3
  44. package/relay/services/relay-connection.js +222 -121
  45. package/relay/services/relay-pool.js +13 -10
  46. package/temporary-storage/index.js +3 -1
  47. package/url/index.js +131 -0
  48. package/web-storage-queue/index.js +8 -6
  49. package/i18n/index.js +0 -235
@@ -1,4 +1,5 @@
1
- import { getEventHash, validateEvent, verifyEvent } from 'nostr-tools'
1
+ import { getEventHash, isSerializableEvent, isValidEvent } from '../event/index.js'
2
+ import { ValidationError } from '../error/index.js'
2
3
  import { generateKeypair } from '../key/index.js'
3
4
  import * as privateChannel from '../private-channel/index.js'
4
5
 
@@ -25,14 +26,14 @@ function uniq (values) {
25
26
  function normalizeDeletionPubkey (deletionPubkey) {
26
27
  if (deletionPubkey === undefined) return undefined
27
28
  if (typeof deletionPubkey !== 'string' || !HEX_PUBKEY.test(deletionPubkey)) {
28
- throw new Error('INVALID_DELETION_PUBKEY')
29
+ throw new ValidationError('INVALID_DELETION_PUBKEY')
29
30
  }
30
31
  return deletionPubkey.toLowerCase()
31
32
  }
32
33
 
33
34
  function resolveDeletionCapability ({ deletionPubkey, deletionSeckey, autoDeletionCapability = true }) {
34
- if (deletionSeckey !== undefined) throw new Error('DELETION_SECKEY_NOT_ACCEPTED')
35
- if (typeof autoDeletionCapability !== 'boolean') throw new Error('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
35
+ if (deletionSeckey !== undefined) throw new ValidationError('DELETION_SECKEY_NOT_ACCEPTED')
36
+ if (typeof autoDeletionCapability !== 'boolean') throw new ValidationError('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
36
37
 
37
38
  const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
38
39
  if (normalizedDeletionPubkey) return { deletionPubkey: normalizedDeletionPubkey }
@@ -51,7 +52,7 @@ function withDelivery (result, reports, deletionSeckey) {
51
52
  }
52
53
  }
53
54
 
54
- function setEquals (a, b) {
55
+ function areSetsEqual (a, b) {
55
56
  if (a.size !== b.size) return false
56
57
  for (const value of a) if (!b.has(value)) return false
57
58
  return true
@@ -117,7 +118,7 @@ function cloneTags (tags) {
117
118
  }
118
119
 
119
120
  async function makeOutgoingRumor ({ senderSigner, rumor }) {
120
- if (!senderSigner?.getPublicKey) throw new Error('SENDER_SIGNER_REQUIRED')
121
+ if (!senderSigner?.getPublicKey) throw new ValidationError('SENDER_SIGNER_REQUIRED')
121
122
  const senderPubkey = await senderSigner.getPublicKey()
122
123
  // This is what gets sent. Id and pubkey are added later by recipient.
123
124
  const wireEvent = {
@@ -134,13 +135,13 @@ async function makeOutgoingRumor ({ senderSigner, rumor }) {
134
135
 
135
136
  function normalizeRumor (event, pubkey) {
136
137
  const normalized = { ...event, pubkey }
137
- if (!validateEvent(normalized)) throw new Error('INVALID_RUMOR')
138
+ if (!isSerializableEvent(normalized)) throw new ValidationError('INVALID_RUMOR')
138
139
  return { ...normalized, id: getEventHash(normalized) }
139
140
  }
140
141
 
141
142
  function assertValidSignedEvent (event) {
142
- if (!validateEvent(event) || event.id !== getEventHash(event) || !verifyEvent(event)) {
143
- throw new Error('INVALID_SIGNED_EVENT')
143
+ if (!isValidEvent(event)) {
144
+ throw new ValidationError('INVALID_SIGNED_EVENT')
144
145
  }
145
146
  return event
146
147
  }
@@ -150,7 +151,7 @@ function readTag (event, name) {
150
151
  }
151
152
 
152
153
  async function ownPrivateChannelPubkey (signer) {
153
- if (!signer?.getPublicKey) throw new Error('PRIVATE_CHANNEL_SIGNER_REQUIRED')
154
+ if (!signer?.getPublicKey) throw new ValidationError('PRIVATE_CHANNEL_SIGNER_REQUIRED')
154
155
  return signer.getPublicKey()
155
156
  }
156
157
 
@@ -268,8 +269,8 @@ function watchRevisionsForChannels (channels) {
268
269
  return Object.fromEntries(channels.map(channel => [channel, watchesByChannel.get(channel)?.revision || 0]))
269
270
  }
270
271
 
271
- function subscriptionMatches (current, channels) {
272
- if (!current || !setEquals(current.channels, channels)) return false
272
+ function doesSubscriptionMatch (current, channels) {
273
+ if (!current || !areSetsEqual(current.channels, channels)) return false
273
274
  for (const channel of channels) {
274
275
  if (current.revisions?.[channel] !== watchesByChannel.get(channel)?.revision) return false
275
276
  }
@@ -302,7 +303,7 @@ function rebuildSubscriptions ({ _subscribe = privateChannel.subscribe, graceful
302
303
 
303
304
  for (const [relay, current] of subsByRelay) {
304
305
  const nextChannels = desired.get(relay)
305
- if (nextChannels && subscriptionMatches(current, nextChannels)) continue
306
+ if (nextChannels && doesSubscriptionMatch(current, nextChannels)) continue
306
307
  if (!nextChannels) {
307
308
  const close = closeSubscription(current.sub, gracefulClose)
308
309
  if (close) closing.push(close)
@@ -312,7 +313,7 @@ function rebuildSubscriptions ({ _subscribe = privateChannel.subscribe, graceful
312
313
 
313
314
  for (const [relay, channels] of desired) {
314
315
  const current = subsByRelay.get(relay)
315
- if (subscriptionMatches(current, channels)) continue
316
+ if (doesSubscriptionMatch(current, channels)) continue
316
317
 
317
318
  const channelList = [...channels]
318
319
  const firstWatch = watchesByChannel.get(channelList[0])
@@ -394,7 +395,7 @@ export async function watch ({
394
395
  since = nowSeconds(),
395
396
  _subscribe = privateChannel.subscribe
396
397
  }) {
397
- if (!relays?.length) throw new Error('NO_RELAYS')
398
+ if (!relays?.length) throw new ValidationError('NO_RELAYS')
398
399
  const channelList = uniq(channels?.length ? channels : [await ownPrivateChannelPubkey(privateChannelSigner)])
399
400
  const ownPubkey = receiverPubkey || await receiverSigner?.getPublicKey?.()
400
401
  const callbacks = { onAsk, onReply, onTell, onYell, onNym, onMessage, onSeed, onChunk, onContentKeyUsage, onError }
@@ -419,7 +420,7 @@ export async function watch ({
419
420
  since
420
421
  }
421
422
  const current = watchesByChannel.get(channel)
422
- const sameSettings = Boolean(
423
+ const areSettingsEqual = Boolean(
423
424
  current &&
424
425
  current.receiverSigner === next.receiverSigner &&
425
426
  current.iykcSigner === next.iykcSigner &&
@@ -434,8 +435,8 @@ export async function watch ({
434
435
  current.ignoredGroupTtlMs === next.ignoredGroupTtlMs &&
435
436
  current.ignoredGroupMaxEntries === next.ignoredGroupMaxEntries
436
437
  )
437
- next.revision = sameSettings ? current.revision : nextWatchRevision++
438
- if (sameSettings && setEquals(new Set(current.relays), new Set(next.relays))) {
438
+ next.revision = areSettingsEqual ? current.revision : nextWatchRevision++
439
+ if (areSettingsEqual && areSetsEqual(new Set(current.relays), new Set(next.relays))) {
439
440
  current.callbacks = callbacks
440
441
  continue
441
442
  }
@@ -475,7 +476,7 @@ async function sendPrivateMessage ({
475
476
  _getIykcProofs,
476
477
  _publish = privateChannel.publish
477
478
  }) {
478
- if (!privateChannelSigner?.getPublicKey) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
479
+ if (!privateChannelSigner?.getPublicKey) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
479
480
  return _publish({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag, deletionPubkey, event, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs })
480
481
  }
481
482
 
@@ -491,8 +492,8 @@ async function sendNymMessage ({
491
492
  deletionPubkey,
492
493
  _publish = privateChannel.publishNymEvent
493
494
  }) {
494
- if (!nymSigner?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
495
- if (!privateChannelSigner?.getPublicKey) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
495
+ if (!nymSigner?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
496
+ if (!privateChannelSigner?.getPublicKey) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
496
497
  return _publish({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey, event, relays, relayToReceivers, recoveryRelays, expirationSeconds })
497
498
  }
498
499
 
@@ -518,8 +519,8 @@ export async function ask ({
518
519
  autoDeletionCapability = true,
519
520
  _publish = privateChannel.publish
520
521
  }) {
521
- if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
522
- if (!privateChannelSigner?.getPublicKey) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
522
+ if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
523
+ if (!privateChannelSigner?.getPublicKey) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
523
524
  const privateChannelPubkey = await ownPrivateChannelPubkey(privateChannelSigner)
524
525
  assertWatching(privateChannelPubkey)
525
526
 
@@ -560,8 +561,8 @@ export async function reply ({
560
561
  autoDeletionCapability = true,
561
562
  _publish = privateChannel.publish
562
563
  }) {
563
- if (!question?.id) throw new Error('QUESTION_REQUIRED')
564
- if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
564
+ if (!question?.id) throw new ValidationError('QUESTION_REQUIRED')
565
+ if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
565
566
  const { event, wireEvent } = await makeOutgoingRumor({
566
567
  senderSigner,
567
568
  rumor: makeMessageRumor({
@@ -597,7 +598,7 @@ export async function tell ({
597
598
  autoDeletionCapability = true,
598
599
  _publish = privateChannel.publish
599
600
  }) {
600
- if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
601
+ if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
601
602
  const { event, wireEvent } = await makeOutgoingRumor({
602
603
  senderSigner,
603
604
  rumor: makeMessageRumor({
@@ -634,7 +635,7 @@ export async function yell ({
634
635
  _publish = privateChannel.publish
635
636
  }) {
636
637
  const receivers = uniq(receiverPubkeys)
637
- if (!receivers.length) throw new Error('NO_RECEIVERS')
638
+ if (!receivers.length) throw new ValidationError('NO_RECEIVERS')
638
639
  const { event, wireEvent } = await makeOutgoingRumor({
639
640
  senderSigner,
640
641
  rumor: makeMessageRumor({
@@ -667,7 +668,7 @@ export async function broadcastRumor ({
667
668
  _publish = privateChannel.publish
668
669
  }) {
669
670
  const receivers = uniq(receiverPubkeys)
670
- if (!receivers.length) throw new Error('NO_RECEIVERS')
671
+ if (!receivers.length) throw new ValidationError('NO_RECEIVERS')
671
672
  const { event, wireEvent } = await makeOutgoingRumor({ senderSigner, rumor })
672
673
  const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
673
674
  const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag: '', deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
@@ -693,7 +694,7 @@ export async function broadcastEvent ({
693
694
  _publish = privateChannel.publish
694
695
  }) {
695
696
  const receivers = uniq(receiverPubkeys)
696
- if (!receivers.length) throw new Error('NO_RECEIVERS')
697
+ if (!receivers.length) throw new ValidationError('NO_RECEIVERS')
697
698
  const wireEvent = assertValidSignedEvent({ ...event, tags: cloneTags(event?.tags) })
698
699
  const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
699
700
  const reports = await sendPrivateMessage({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receivers, receiverTag: '', deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, temporaryStorageArea, _getIykcProofs, _publish })
@@ -714,7 +715,7 @@ export async function broadcastNymRumor ({
714
715
  autoDeletionCapability = true,
715
716
  _publish = privateChannel.publishNymEvent
716
717
  }) {
717
- if (!nymSigner?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
718
+ if (!nymSigner?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
718
719
  const { event, wireEvent } = await makeOutgoingRumor({ senderSigner: nymSigner, rumor })
719
720
  const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
720
721
  const reports = await sendNymMessage({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish })
@@ -735,7 +736,7 @@ export async function broadcastNymEvent ({
735
736
  autoDeletionCapability = true,
736
737
  _publish = privateChannel.publishNymEvent
737
738
  }) {
738
- if (!nymSigner?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
739
+ if (!nymSigner?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
739
740
  const wireEvent = assertValidSignedEvent({ ...event, tags: cloneTags(event?.tags) })
740
741
  const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
741
742
  const reports = await sendNymMessage({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish })
@@ -41,6 +41,7 @@
41
41
 
42
42
  import * as privateMessage from '../private-message/index.js'
43
43
  import { bytesToBase64 } from '../base64/index.js'
44
+ import { ValidationError } from '../error/index.js'
44
45
  import { getRelaysByPubkey, pickRelaysForPubkeys, subscribeRelayListUpdates } from '../relay/index.js'
45
46
  import * as privateChannel from '../private-channel/index.js'
46
47
  import { DEFAULT_RECEIVED_CHUNK_TTL_MS } from '../private-channel/services/received-chunks.js'
@@ -126,21 +127,21 @@ function nowSeconds () {
126
127
 
127
128
  function normalizeOfflineRecoverySeconds (value) {
128
129
  if (!Number.isSafeInteger(value) || value < 0 || value > MAX_OFFLINE_RECOVERY_SECONDS) {
129
- throw new Error('INVALID_OFFLINE_RECOVERY_SECONDS')
130
+ throw new ValidationError('INVALID_OFFLINE_RECOVERY_SECONDS')
130
131
  }
131
132
  return value
132
133
  }
133
134
 
134
135
  function normalizeStaleChannelSeconds (value) {
135
136
  if (!Number.isSafeInteger(value) || value < 0 || value > MAX_OFFLINE_RECOVERY_SECONDS) {
136
- throw new Error('INVALID_STALE_CHANNEL_SECONDS')
137
+ throw new ValidationError('INVALID_STALE_CHANNEL_SECONDS')
137
138
  }
138
139
  return value
139
140
  }
140
141
 
141
142
  function normalizeIdentityStorageRetentionSeconds (value) {
142
143
  if (!Number.isSafeInteger(value) || value < 0 || value > MAX_OFFLINE_RECOVERY_SECONDS) {
143
- throw new Error('INVALID_IDENTITY_STORAGE_RETENTION_SECONDS')
144
+ throw new ValidationError('INVALID_IDENTITY_STORAGE_RETENTION_SECONDS')
144
145
  }
145
146
  return value
146
147
  }
@@ -150,7 +151,7 @@ function uniq (values) {
150
151
  }
151
152
 
152
153
  function normalizeAutoDeletionCapability (value) {
153
- if (typeof value !== 'boolean') throw new Error('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
154
+ if (typeof value !== 'boolean') throw new ValidationError('AUTO_DELETION_CAPABILITY_BOOLEAN_REQUIRED')
154
155
  return value
155
156
  }
156
157
 
@@ -162,11 +163,11 @@ function parseJson (raw, fallback) {
162
163
  try { return JSON.parse(raw || '') } catch { return fallback }
163
164
  }
164
165
 
165
- function sameStateValue (left, right) {
166
+ function areStateValuesEqual (left, right) {
166
167
  return JSON.stringify(left) === JSON.stringify(right)
167
168
  }
168
169
 
169
- function storesRecoverySeeds (mode) {
170
+ function doesModeStoreRecoverySeeds (mode) {
170
171
  return mode === 'seeder' || mode === 'watchtower'
171
172
  }
172
173
 
@@ -283,7 +284,7 @@ export class PrivateMessenger {
283
284
  }
284
285
 
285
286
  async init ({ userSigner, contentKeySigner, nymSigner, channels = [], relays = [], mode = 'leecher' }) {
286
- if (!userSigner?.getPublicKey) throw new Error('USER_SIGNER_REQUIRED')
287
+ if (!userSigner?.getPublicKey) throw new ValidationError('USER_SIGNER_REQUIRED')
287
288
  this.assertOpen()
288
289
  if (this.initSettledPromise) throw new Error('PRIVATE_MESSENGER_INIT_IN_PROGRESS')
289
290
  if (this.initialized) throw new Error('PRIVATE_MESSENGER_ALREADY_INITIALIZED')
@@ -600,8 +601,8 @@ export class PrivateMessenger {
600
601
  : this.identityStorageRetentionSeconds
601
602
  if (userSigner) {
602
603
  const userPubkey = await userSigner.getPublicKey?.()
603
- if (!userPubkey) throw new Error('USER_SIGNER_REQUIRED')
604
- if (this.userPubkey && userPubkey !== this.userPubkey) throw new Error('USER_SIGNER_MISMATCH')
604
+ if (!userPubkey) throw new ValidationError('USER_SIGNER_REQUIRED')
605
+ if (this.userPubkey && userPubkey !== this.userPubkey) throw new ValidationError('USER_SIGNER_MISMATCH')
605
606
  this.userSigner = userSigner
606
607
  }
607
608
  this.contentKeySigner = contentKeySigner || null
@@ -666,10 +667,10 @@ export class PrivateMessenger {
666
667
  const hasChannelSendRelays = Boolean(channel.sendRelays?.length)
667
668
  const hasDefaultRelays = Boolean(defaults.relays?.length)
668
669
  const pubkey = channel.pubkey || await signer?.getPublicKey?.()
669
- if (!pubkey) throw new Error('CHANNEL_PUBKEY_REQUIRED')
670
- if (!signer && !readerSigner) throw new Error('CHANNEL_SIGNER_REQUIRED')
670
+ if (!pubkey) throw new ValidationError('CHANNEL_PUBKEY_REQUIRED')
671
+ if (!signer && !readerSigner) throw new ValidationError('CHANNEL_SIGNER_REQUIRED')
671
672
  const mode = channel.mode || defaults.mode || 'leecher'
672
- if (!signer && storesRecoverySeeds(mode)) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
673
+ if (!signer && doesModeStoreRecoverySeeds(mode)) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
673
674
  const readerPubkey = channel.readerPubkey || channel.privateChannelReaderPubkey || await readerSigner?.getPublicKey?.() || pubkey
674
675
  const autoDeletionCapability = channel.autoDeletionCapability === undefined
675
676
  ? undefined
@@ -733,7 +734,7 @@ export class PrivateMessenger {
733
734
  if (channel.sendRelays.length) return { relays: channel.sendRelays, recoveryRelays }
734
735
  if (channel.relays.length) return { relays: channel.relays, recoveryRelays }
735
736
  const derived = await this.readRelayToReceivers(receiverPubkeys)
736
- if (!relayMapRelays(derived).length) throw new Error('NO_RELAYS')
737
+ if (!relayMapRelays(derived).length) throw new ValidationError('NO_RELAYS')
737
738
  return { relayToReceivers: derived, recoveryRelays }
738
739
  }
739
740
 
@@ -748,7 +749,7 @@ export class PrivateMessenger {
748
749
  channels: isPlainObject(state?.channels) ? structuredClone(state.channels) : {}
749
750
  }
750
751
  const changed = Object.fromEntries(Object.entries(next.channels)
751
- .filter(([pubkey, value]) => !sameStateValue(previous[pubkey], value)))
752
+ .filter(([pubkey, value]) => !areStateValuesEqual(previous[pubkey], value)))
752
753
  const removed = Object.keys(previous).filter(pubkey => !Object.hasOwn(next.channels, pubkey))
753
754
  this.state = next
754
755
  if (!Object.keys(changed).length && !removed.length) return this.stateWriteTail
@@ -993,7 +994,7 @@ export class PrivateMessenger {
993
994
  const channelPubkeys = uniq(channels)
994
995
  for (const pubkey of channelPubkeys) {
995
996
  const channel = this.channels.get(pubkey)
996
- if (!channel) throw new Error('UNKNOWN_CHANNEL')
997
+ if (!channel) throw new ValidationError('UNKNOWN_CHANNEL')
997
998
  const watchRelays = await this.resolveWatchRelays(channel)
998
999
  this.assertOpen()
999
1000
  const stop = await this._privateMessage.watch({
@@ -1107,7 +1108,7 @@ export class PrivateMessenger {
1107
1108
 
1108
1109
  async handleAsk (channelPubkey, message) {
1109
1110
  this.trackSeederActivity(channelPubkey, message)
1110
- if (storesRecoverySeeds(this.channels.get(channelPubkey)?.mode) && messageCode(message) === MISSING_MESSAGES_ASK_CODE) {
1111
+ if (doesModeStoreRecoverySeeds(this.channels.get(channelPubkey)?.mode) && messageCode(message) === MISSING_MESSAGES_ASK_CODE) {
1111
1112
  await this.replyWithStoredSeeds(channelPubkey, message)
1112
1113
  return
1113
1114
  }
@@ -1461,10 +1462,10 @@ export class PrivateMessenger {
1461
1462
  async reconcilePresencePublishers () {
1462
1463
  const starts = []
1463
1464
  for (const pubkey of [...this.presenceTimers.keys()]) {
1464
- if (!storesRecoverySeeds(this.channels.get(pubkey)?.mode) || !this.offlineRecoverySecondsFor(pubkey)) this.stopPresencePublisher(pubkey)
1465
+ if (!doesModeStoreRecoverySeeds(this.channels.get(pubkey)?.mode) || !this.offlineRecoverySecondsFor(pubkey)) this.stopPresencePublisher(pubkey)
1465
1466
  }
1466
1467
  for (const [pubkey, channel] of this.channels) {
1467
- if (storesRecoverySeeds(channel.mode) && this.offlineRecoverySecondsFor(channel)) starts.push(this.startPresencePublisher(pubkey))
1468
+ if (doesModeStoreRecoverySeeds(channel.mode) && this.offlineRecoverySecondsFor(channel)) starts.push(this.startPresencePublisher(pubkey))
1468
1469
  else this.stopPresencePublisher(pubkey)
1469
1470
  }
1470
1471
  await Promise.all(starts)
@@ -1484,13 +1485,13 @@ export class PrivateMessenger {
1484
1485
 
1485
1486
  requireChannel (pubkey) {
1486
1487
  const channel = this.channels.get(pubkey)
1487
- if (!channel) throw new Error('UNKNOWN_CHANNEL')
1488
+ if (!channel) throw new ValidationError('UNKNOWN_CHANNEL')
1488
1489
  return channel
1489
1490
  }
1490
1491
 
1491
1492
  requireWritableChannel (pubkey) {
1492
1493
  const channel = this.requireChannel(pubkey)
1493
- if (!channel.signer) throw new Error('PRIVATE_CHANNEL_WRITER_REQUIRED')
1494
+ if (!channel.signer) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
1494
1495
  return channel
1495
1496
  }
1496
1497
 
@@ -1560,7 +1561,7 @@ export class PrivateMessenger {
1560
1561
 
1561
1562
  requireNymSigner (channel, override) {
1562
1563
  const signer = override || channel?.nymSigner || this.nymSigner
1563
- if (!signer?.getPublicKey) throw new Error('NYM_SIGNER_REQUIRED')
1564
+ if (!signer?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
1564
1565
  return signer
1565
1566
  }
1566
1567
 
@@ -1704,7 +1705,7 @@ export class PrivateMessenger {
1704
1705
 
1705
1706
  const routerRecord = record?.recordType === ROUTER_SEED_RECORD_TYPE ? record.router : null
1706
1707
  if (!isPrivateChannelRouter(routerRecord)) return null
1707
- if (!this._privateChannel.unwrapEvent) throw new Error('PRIVATE_CHANNEL_UNWRAP_UNSUPPORTED')
1708
+ if (!this._privateChannel.unwrapEvent) throw new ValidationError('PRIVATE_CHANNEL_UNWRAP_UNSUPPORTED')
1708
1709
 
1709
1710
  const channel = this.requireChannel(channelPubkey)
1710
1711
  const router = {
@@ -1,5 +1,6 @@
1
- import { getEventHash } from 'nostr-tools'
1
+ import { getEventHash } from '../../event/index.js'
2
2
  import { bytesToBase64, base64ToBytes } from '../../base64/index.js'
3
+ import { ValidationError } from '../../error/index.js'
3
4
  import { ASK_KIND, parseRumorContent } from '../../private-message/index.js'
4
5
 
5
6
  export const SEEDER_PRESENCE_CODE = 'seederPresence_8mj8'
@@ -37,7 +38,7 @@ function encodeJsonlRows (...rows) {
37
38
  return bytesToBase64(encoder.encode(rows.map(row => String(row).endsWith('\n') ? row : `${row}\n`).join('')))
38
39
  }
39
40
 
40
- function eventInRange (event, since, until) {
41
+ function isEventInRange (event, since, until) {
41
42
  if (!Number.isFinite(event?.created_at)) return true
42
43
  if (since != null && event.created_at < since) return false
43
44
  if (until != null && event.created_at > until) return false
@@ -159,17 +160,17 @@ function routerWithSingleRow (router, payloadRow, row) {
159
160
  }
160
161
  }
161
162
 
162
- function seedRowInRange (seed, since, until) {
163
+ function isSeedRowInRange (seed, since, until) {
163
164
  const firstSeenAt = seed.firstSeenAt ?? seed.router?.created_at
164
165
  const lastSeenAt = seed.lastSeenAt ?? seed.router?.created_at
165
- if (!Number.isFinite(firstSeenAt) || !Number.isFinite(lastSeenAt)) return eventInRange(seed.router, since, until)
166
+ if (!Number.isFinite(firstSeenAt) || !Number.isFinite(lastSeenAt)) return isEventInRange(seed.router, since, until)
166
167
  if (since != null && lastSeenAt < since) return false
167
168
  if (until != null && firstSeenAt > until) return false
168
169
  return true
169
170
  }
170
171
 
171
172
  function compactRoutersFromSeed (seed, { receiverPubkey, since, until }) {
172
- if (!seed?.payloadRow || !seed?.row || !seed?.router || !seedRowInRange(seed, since, until)) return []
173
+ if (!seed?.payloadRow || !seed?.row || !seed?.router || !isSeedRowInRange(seed, since, until)) return []
173
174
  if (receiverPubkey && seed.receiverPubkey !== receiverPubkey) return []
174
175
  return [{
175
176
  recordType: ROUTER_SEED_RECORD_TYPE,
@@ -185,7 +186,7 @@ function compactNymCarriersFromSeed (seed, { since, until }) {
185
186
  // eslint-disable-next-line camelcase
186
187
  const created_at = nymCarrierRecordTime(seed)
187
188
  // eslint-disable-next-line camelcase
188
- if (!seed?.carriers?.length || !eventInRange({ created_at }, since, until)) return []
189
+ if (!seed?.carriers?.length || !isEventInRange({ created_at }, since, until)) return []
189
190
  return [{
190
191
  recordType: NYM_CARRIER_SEED_RECORD_TYPE,
191
192
  carriers: compactSeedNymCarriers(seed.carriers)
@@ -226,10 +227,10 @@ export function createEventReplyPacker ({
226
227
  recordsFromInput = eventRecordFromInput,
227
228
  sendEmptyReply = false
228
229
  }) {
229
- if (!messenger?.reply) throw new Error('MESSENGER_REQUIRED')
230
- if (!question?.id) throw new Error('QUESTION_REQUIRED')
231
- if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
232
- if (!Number.isSafeInteger(eventsPerChunk) || eventsPerChunk < 1) throw new Error('INVALID_EVENTS_PER_CHUNK')
230
+ if (!messenger?.reply) throw new ValidationError('MESSENGER_REQUIRED')
231
+ if (!question?.id) throw new ValidationError('QUESTION_REQUIRED')
232
+ if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
233
+ if (!Number.isSafeInteger(eventsPerChunk) || eventsPerChunk < 1) throw new ValidationError('INVALID_EVENTS_PER_CHUNK')
233
234
 
234
235
  let chunk = ''
235
236
  let chunkEvents = 0
@@ -298,10 +299,10 @@ export function createMissingMessageReplyPacker ({
298
299
  eventsPerChunk = DEFAULT_EVENTS_PER_CHUNK,
299
300
  sendEmptyReply = false
300
301
  }) {
301
- if (!messenger?.reply) throw new Error('MESSENGER_REQUIRED')
302
- if (!question?.id) throw new Error('QUESTION_REQUIRED')
303
- if (!receiverPubkey) throw new Error('RECEIVER_PUBKEY_REQUIRED')
304
- if (!Number.isSafeInteger(eventsPerChunk) || eventsPerChunk < 1) throw new Error('INVALID_EVENTS_PER_CHUNK')
302
+ if (!messenger?.reply) throw new ValidationError('MESSENGER_REQUIRED')
303
+ if (!question?.id) throw new ValidationError('QUESTION_REQUIRED')
304
+ if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
305
+ if (!Number.isSafeInteger(eventsPerChunk) || eventsPerChunk < 1) throw new ValidationError('INVALID_EVENTS_PER_CHUNK')
305
306
 
306
307
  const range = backfillRequestRange(question, since, until)
307
308
  return createEventReplyPacker({
@@ -1,4 +1,5 @@
1
1
  import { run } from '../../idb/index.js'
2
+ import { ValidationError } from '../../error/index.js'
2
3
 
3
4
  const DATABASE_VERSION = 1
4
5
  const CHANNELS_STORE = 'channels'
@@ -80,7 +81,7 @@ function cloneChannels (channels) {
80
81
  }
81
82
 
82
83
  export async function createChannelStateStore ({ prefix, indexedDB = globalThis.indexedDB } = {}) {
83
- if (!prefix) throw new Error('PRIVATE_MESSENGER_STATE_PREFIX_REQUIRED')
84
+ if (!prefix) throw new ValidationError('PRIVATE_MESSENGER_STATE_PREFIX_REQUIRED')
84
85
  const db = await openDatabase(indexedDB, `${prefix}:state:idb`)
85
86
  let closed = false
86
87
  let activeTransactions = 0
@@ -1,9 +1,11 @@
1
+ import { ValidationError } from '../../error/index.js'
2
+
1
3
  const REGISTER_COUNT = 256
2
4
  const HLL_HEX_LENGTH = REGISTER_COUNT * 2
3
5
 
4
6
  function assertRegisters (registers) {
5
7
  if (!(registers instanceof Uint8Array) || registers.length !== REGISTER_COUNT) {
6
- throw new Error('INVALID_HLL_REGISTERS')
8
+ throw new ValidationError('INVALID_HLL_REGISTERS')
7
9
  }
8
10
  }
9
11
 
@@ -52,7 +52,7 @@ function relayListCreatedAt (event) {
52
52
  return Number.isFinite(event?.created_at) ? event.created_at : 0
53
53
  }
54
54
 
55
- function relaySetsEqual (a, b) {
55
+ function areRelaySetsEqual (a, b) {
56
56
  const left = new Set(a || [])
57
57
  const right = new Set(b || [])
58
58
  if (left.size !== right.size) return false
@@ -61,8 +61,8 @@ function relaySetsEqual (a, b) {
61
61
  }
62
62
 
63
63
  function relaySetChanges (previous, next) {
64
- const read = !relaySetsEqual(previous?.read, next?.read)
65
- const write = !relaySetsEqual(previous?.write, next?.write)
64
+ const read = !areRelaySetsEqual(previous?.read, next?.read)
65
+ const write = !areRelaySetsEqual(previous?.write, next?.write)
66
66
  return {
67
67
  read,
68
68
  write,