libp2r2p 0.8.0 → 0.9.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 +83 -2
  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/i18n/index.js +15 -13
  14. package/idb/index.js +5 -3
  15. package/idb-queue/index.js +21 -20
  16. package/index.js +10 -0
  17. package/key/index.js +31 -18
  18. package/kind/index.js +244 -0
  19. package/nip04/index.js +47 -0
  20. package/nip05/index.js +61 -0
  21. package/nip19/index.js +176 -43
  22. package/nip44/helpers.js +95 -0
  23. package/nip44/index.js +14 -0
  24. package/nip44-v3/index.js +35 -16
  25. package/nip46/helpers/frame.js +6 -6
  26. package/nip46/helpers/url.js +8 -6
  27. package/nip46/services/bunker-signer.js +7 -6
  28. package/nip46/services/client.js +8 -7
  29. package/nip46/services/server-session.js +8 -7
  30. package/nip46/services/transport.js +9 -8
  31. package/nip96/index.js +285 -0
  32. package/nip98/index.js +56 -0
  33. package/nwt/index.js +241 -0
  34. package/package.json +22 -3
  35. package/private-channel/helpers/chunks.js +7 -6
  36. package/private-channel/helpers/event.js +5 -4
  37. package/private-channel/index.js +63 -61
  38. package/private-channel/services/received-chunks.js +4 -3
  39. package/private-message/index.js +32 -31
  40. package/private-messenger/index.js +23 -22
  41. package/private-messenger/recovery/index.js +15 -14
  42. package/private-messenger/services/channel-state.js +2 -1
  43. package/relay/helpers/hll.js +3 -1
  44. package/relay/services/query.js +3 -3
  45. package/relay/services/relay-connection.js +222 -121
  46. package/relay/services/relay-pool.js +13 -10
  47. package/temporary-storage/index.js +3 -1
  48. package/url/index.js +131 -0
  49. package/web-storage-queue/index.js +8 -6
@@ -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,