@helia/ipns 7.2.2 → 7.2.3-2f88fc8

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/src/dnslink.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { CodeError, type Logger } from '@libp2p/interface'
1
+ import { } from '@libp2p/interface'
2
2
  import { peerIdFromString } from '@libp2p/peer-id'
3
3
  import { RecordType } from '@multiformats/dns'
4
4
  import { CID } from 'multiformats/cid'
5
+ import { DNSLinkNotFoundError } from './errors.js'
5
6
  import type { ResolveDNSLinkOptions } from './index.js'
7
+ import type { Logger } from '@libp2p/interface'
6
8
  import type { Answer, DNS } from '@multiformats/dns'
7
9
 
8
10
  const MAX_RECURSIVE_DEPTH = 32
@@ -26,7 +28,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
26
28
  })
27
29
 
28
30
  // sort the TXT records to ensure deterministic processing
29
- const txtRecords = txtRecordsResponse.Answer
31
+ const txtRecords = (txtRecordsResponse?.Answer ?? [])
30
32
  .sort((a, b) => a.data.localeCompare(b.data))
31
33
 
32
34
  log('found %d TXT records for %s', txtRecords.length, domain)
@@ -98,7 +100,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
98
100
  })
99
101
 
100
102
  // sort the CNAME records to ensure deterministic processing
101
- const cnameRecords = cnameRecordsResponse.Answer
103
+ const cnameRecords = (cnameRecordsResponse?.Answer ?? [])
102
104
  .sort((a, b) => a.data.localeCompare(b.data))
103
105
 
104
106
  log('found %d CNAME records for %s', cnameRecords.length, domain)
@@ -111,7 +113,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
111
113
  }
112
114
  }
113
115
 
114
- throw new CodeError(`No DNSLink records found for domain: ${domain}`, 'ERR_DNSLINK_NOT_FOUND')
116
+ throw new DNSLinkNotFoundError(`No DNSLink records found for domain: ${domain}`)
115
117
  }
116
118
 
117
119
  async function recursiveResolveDomain (domain: string, depth: number, dns: DNS, log: Logger, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResult> {
@@ -130,7 +132,7 @@ async function recursiveResolveDomain (domain: string, depth: number, dns: DNS,
130
132
  return await recursiveResolveDnslink(domain, depth, dns, log, options)
131
133
  } catch (err: any) {
132
134
  // If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error
133
- if (err.code !== 'ENOTFOUND' && err.code !== 'ERR_DNSLINK_NOT_FOUND' && err.code !== 'ENODATA') {
135
+ if (err.code !== 'ENOTFOUND' && err.code !== 'ENODATA' && err.name !== 'DNSLinkNotFoundError' && err.name !== 'NotFoundError') {
134
136
  throw err
135
137
  }
136
138
 
@@ -144,7 +146,7 @@ async function recursiveResolveDomain (domain: string, depth: number, dns: DNS,
144
146
  }
145
147
 
146
148
  // If this throws then we propagate the error
147
- return await recursiveResolveDnslink(domain, depth, dns, log, options)
149
+ return recursiveResolveDnslink(domain, depth, dns, log, options)
148
150
  }
149
151
  }
150
152
 
package/src/errors.ts ADDED
@@ -0,0 +1,53 @@
1
+ export class DNSLinkNotFoundError extends Error {
2
+ static name = 'DNSLinkNotFoundError'
3
+
4
+ constructor (message = 'DNSLink not found') {
5
+ super(message)
6
+ this.name = 'DNSLinkNotFoundError'
7
+ }
8
+ }
9
+
10
+ export class RecordsFailedValidationError extends Error {
11
+ static name = 'RecordsFailedValidationError'
12
+
13
+ constructor (message = 'Records failed validation') {
14
+ super(message)
15
+ this.name = 'RecordsFailedValidationError'
16
+ }
17
+ }
18
+
19
+ export class UnsupportedMultibasePrefixError extends Error {
20
+ static name = 'UnsupportedMultibasePrefixError'
21
+
22
+ constructor (message = 'Unsupported multibase prefix') {
23
+ super(message)
24
+ this.name = 'UnsupportedMultibasePrefixError'
25
+ }
26
+ }
27
+
28
+ export class UnsupportedMultihashCodecError extends Error {
29
+ static name = 'UnsupportedMultihashCodecError'
30
+
31
+ constructor (message = 'Unsupported multihash codec') {
32
+ super(message)
33
+ this.name = 'UnsupportedMultihashCodecError'
34
+ }
35
+ }
36
+
37
+ export class InvalidValueError extends Error {
38
+ static name = 'InvalidValueError'
39
+
40
+ constructor (message = 'Invalid value') {
41
+ super(message)
42
+ this.name = 'InvalidValueError'
43
+ }
44
+ }
45
+
46
+ export class InvalidTopicError extends Error {
47
+ static name = 'InvalidTopicError'
48
+
49
+ constructor (message = 'Invalid topic') {
50
+ super(message)
51
+ this.name = 'InvalidTopicError'
52
+ }
53
+ }
package/src/index.ts CHANGED
@@ -11,23 +11,23 @@
11
11
  * import { createHelia } from 'helia'
12
12
  * import { ipns } from '@helia/ipns'
13
13
  * import { unixfs } from '@helia/unixfs'
14
+ * import { generateKeyPair } from '@libp2p/crypto/keys'
14
15
  *
15
16
  * const helia = await createHelia()
16
17
  * const name = ipns(helia)
17
18
  *
18
- * // create a public key to publish as an IPNS name
19
- * const keyInfo = await helia.libp2p.services.keychain.createKey('my-key')
20
- * const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
19
+ * // create a keypair to publish an IPNS name
20
+ * const privateKey = await generateKeyPair('Ed25519')
21
21
  *
22
22
  * // store some data to publish
23
23
  * const fs = unixfs(helia)
24
- * const cid = await fs.add(Uint8Array.from([0, 1, 2, 3, 4]))
24
+ * const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
25
25
  *
26
26
  * // publish the name
27
- * await name.publish(peerId, cid)
27
+ * await name.publish(privateKey, cid)
28
28
  *
29
29
  * // resolve the name
30
- * const result = name.resolve(peerId)
30
+ * const result = await name.resolve(privateKey.publicKey)
31
31
  *
32
32
  * console.info(result.cid, result.path)
33
33
  * ```
@@ -41,30 +41,29 @@
41
41
  * import { createHelia } from 'helia'
42
42
  * import { ipns } from '@helia/ipns'
43
43
  * import { unixfs } from '@helia/unixfs'
44
+ * import { generateKeyPair } from '@libp2p/crypto/keys'
44
45
  *
45
46
  * const helia = await createHelia()
46
47
  * const name = ipns(helia)
47
48
  *
48
- * // create a public key to publish as an IPNS name
49
- * const keyInfo = await helia.libp2p.services.keychain.createKey('my-key')
50
- * const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
49
+ * // create a keypair to publish an IPNS name
50
+ * const privateKey = await generateKeyPair('Ed25519')
51
51
  *
52
52
  * // store some data to publish
53
53
  * const fs = unixfs(helia)
54
- * const cid = await fs.add(Uint8Array.from([0, 1, 2, 3, 4]))
54
+ * const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
55
55
  *
56
56
  * // publish the name
57
- * await name.publish(peerId, cid)
57
+ * await name.publish(privateKey, cid)
58
58
  *
59
- * // create another public key to re-publish the original record
60
- * const recursiveKeyInfo = await helia.libp2p.services.keychain.createKey('my-recursive-key')
61
- * const recursivePeerId = await helia.libp2p.services.keychain.exportPeerId(recursiveKeyInfo.name)
59
+ * // create another keypair to re-publish the original record
60
+ * const recursivePrivateKey = await generateKeyPair('Ed25519')
62
61
  *
63
62
  * // publish the recursive name
64
- * await name.publish(recursivePeerId, peerId)
63
+ * await name.publish(recursivePrivateKey, privateKey.publicKey)
65
64
  *
66
65
  * // resolve the name recursively - it resolves until a CID is found
67
- * const result = name.resolve(recursivePeerId)
66
+ * const result = await name.resolve(recursivePrivateKey.publicKey)
68
67
  * console.info(result.cid.toString() === cid.toString()) // true
69
68
  * ```
70
69
  *
@@ -76,27 +75,27 @@
76
75
  * import { createHelia } from 'helia'
77
76
  * import { ipns } from '@helia/ipns'
78
77
  * import { unixfs } from '@helia/unixfs'
78
+ * import { generateKeyPair } from '@libp2p/crypto/keys'
79
79
  *
80
80
  * const helia = await createHelia()
81
81
  * const name = ipns(helia)
82
82
  *
83
- * // create a public key to publish as an IPNS name
84
- * const keyInfo = await helia.libp2p.services.keychain.createKey('my-key')
85
- * const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
83
+ * // create a keypair to publish an IPNS name
84
+ * const privateKey = await generateKeyPair('Ed25519')
86
85
  *
87
86
  * // store some data to publish
88
87
  * const fs = unixfs(helia)
89
- * const fileCid = await fs.add(Uint8Array.from([0, 1, 2, 3, 4]))
88
+ * const fileCid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
90
89
  *
91
90
  * // store the file in a directory
92
- * const dirCid = await fs.mkdir()
91
+ * const dirCid = await fs.addDirectory()
93
92
  * const finalDirCid = await fs.cp(fileCid, dirCid, '/foo.txt')
94
93
  *
95
94
  * // publish the name
96
- * await name.publish(peerId, `/ipfs/${finalDirCid}/foo.txt)
95
+ * await name.publish(privateKey, `/ipfs/${finalDirCid}/foo.txt`)
97
96
  *
98
97
  * // resolve the name
99
- * const result = name.resolve(peerId)
98
+ * const result = await name.resolve(privateKey.publicKey)
100
99
  *
101
100
  * console.info(result.cid, result.path) // QmFoo.. 'foo.txt'
102
101
  * ```
@@ -123,11 +122,14 @@
123
122
  * import { pubsub } from '@helia/ipns/routing'
124
123
  * import { unixfs } from '@helia/unixfs'
125
124
  * import { gossipsub } from '@chainsafe/libp2p-gossipsub'
125
+ * import { generateKeyPair } from '@libp2p/crypto/keys'
126
+ * import type { Libp2p, PubSub } from '@libp2p/interface'
127
+ * import type { DefaultLibp2pServices } from 'helia'
126
128
  *
127
129
  * const libp2pOptions = libp2pDefaults()
128
130
  * libp2pOptions.services.pubsub = gossipsub()
129
131
  *
130
- * const helia = await createHelia({
132
+ * const helia = await createHelia<Libp2p<DefaultLibp2pServices & { pubsub: PubSub }>>({
131
133
  * libp2p: libp2pOptions
132
134
  * })
133
135
  * const name = ipns(helia, {
@@ -136,44 +138,50 @@
136
138
  * ]
137
139
  * })
138
140
  *
139
- * // create a public key to publish as an IPNS name
140
- * const keyInfo = await helia.libp2p.services.keychain.createKey('my-key')
141
- * const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
141
+ * // create a keypair to publish an IPNS name
142
+ * const privateKey = await generateKeyPair('Ed25519')
142
143
  *
143
144
  * // store some data to publish
144
145
  * const fs = unixfs(helia)
145
- * const cid = await fs.add(Uint8Array.from([0, 1, 2, 3, 4]))
146
+ * const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
146
147
  *
147
148
  * // publish the name
148
- * await name.publish(peerId, cid)
149
+ * await name.publish(privateKey, cid)
149
150
  *
150
151
  * // resolve the name
151
- * const { cid, path } = name.resolve(peerId)
152
+ * const result = await name.resolve(privateKey.publicKey)
152
153
  * ```
153
154
  *
154
155
  * @example Using custom DNS over HTTPS resolvers
155
156
  *
156
- * With default {@link DNSResolver} resolvers:
157
+ * To use custom resolvers, configure Helia's `dns` option:
157
158
  *
158
159
  * ```TypeScript
159
160
  * import { createHelia } from 'helia'
160
161
  * import { ipns } from '@helia/ipns'
161
- * import { unixfs } from '@helia/unixfs'
162
- * import { dnsOverHttps } from '@helia/ipns/dns-resolvers'
163
- *
164
- * const helia = await createHelia()
165
- * const name = ipns(helia, {
166
- * resolvers: [
167
- * dnsOverHttps('https://private-dns-server.me/dns-query'),
162
+ * import { dns } from '@multiformats/dns'
163
+ * import { dnsOverHttps } from '@multiformats/dns/resolvers'
164
+ * import { helia } from '@helia/ipns/routing'
165
+ *
166
+ * const node = await createHelia({
167
+ * dns: dns({
168
+ * resolvers: {
169
+ * '.': dnsOverHttps('https://private-dns-server.me/dns-query')
170
+ * }
171
+ * })
172
+ * })
173
+ * const name = ipns(node, {
174
+ * routers: [
175
+ * helia(node.routing)
168
176
  * ]
169
177
  * })
170
178
  *
171
- * const { cid, path } = name.resolveDns('some-domain-with-dnslink-entry.com')
179
+ * const result = name.resolveDNSLink('some-domain-with-dnslink-entry.com')
172
180
  * ```
173
181
  *
174
182
  * @example Resolving a domain with a dnslink entry
175
183
  *
176
- * Calling `resolveDns` with the `@helia/ipns` instance:
184
+ * Calling `resolveDNSLink` with the `@helia/ipns` instance:
177
185
  *
178
186
  * ```TypeScript
179
187
  * // resolve a CID from a TXT record in a DNS zone file, using the default
@@ -185,10 +193,16 @@
185
193
  * // ;; ANSWER SECTION:
186
194
  * // _dnslink.website.ipfs.io. 60 IN TXT "dnslink=/ipfs/QmWebsite"
187
195
  *
188
- * const { cid, path } = name.resolveDns('ipfs.io')
196
+ * import { createHelia } from 'helia'
197
+ * import { ipns } from '@helia/ipns'
198
+ *
199
+ * const node = await createHelia()
200
+ * const name = ipns(node)
189
201
  *
190
- * console.info(cid)
191
- * // QmWebsite
202
+ * const { answer } = await name.resolveDNSLink('ipfs.io')
203
+ *
204
+ * console.info(answer)
205
+ * // { data: '/ipfs/QmWebsite' }
192
206
  * ```
193
207
  *
194
208
  * @example Using DNS-Over-HTTPS
@@ -200,14 +214,21 @@
200
214
  * If this is a concern, use the DNS-JSON-Over-HTTPS resolver instead.
201
215
  *
202
216
  * ```TypeScript
203
- * // use DNS-Over-HTTPS
204
- * import { dnsOverHttps } from '@helia/ipns/dns-resolvers'
205
- *
206
- * const { cid, path } = name.resolveDns('ipfs.io', {
207
- * resolvers: [
208
- * dnsOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
209
- * ]
217
+ * import { createHelia } from 'helia'
218
+ * import { ipns } from '@helia/ipns'
219
+ * import { dns } from '@multiformats/dns'
220
+ * import { dnsOverHttps } from '@multiformats/dns/resolvers'
221
+ *
222
+ * const node = await createHelia({
223
+ * dns: dns({
224
+ * resolvers: {
225
+ * '.': dnsOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
226
+ * }
227
+ * })
210
228
  * })
229
+ * const name = ipns(node)
230
+ *
231
+ * const result = await name.resolveDNSLink('ipfs.io')
211
232
  * ```
212
233
  *
213
234
  * @example Using DNS-JSON-Over-HTTPS
@@ -216,34 +237,46 @@
216
237
  * result in a smaller browser bundle due to the response being plain JSON.
217
238
  *
218
239
  * ```TypeScript
219
- * // use DNS-JSON-Over-HTTPS
220
- * import { dnsJsonOverHttps } from '@helia/ipns/dns-resolvers'
221
- *
222
- * const { cid, path } = name.resolveDns('ipfs.io', {
223
- * resolvers: [
224
- * dnsJsonOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
225
- * ]
240
+ * import { createHelia } from 'helia'
241
+ * import { ipns } from '@helia/ipns'
242
+ * import { dns } from '@multiformats/dns'
243
+ * import { dnsJsonOverHttps } from '@multiformats/dns/resolvers'
244
+ *
245
+ * const node = await createHelia({
246
+ * dns: dns({
247
+ * resolvers: {
248
+ * '.': dnsJsonOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
249
+ * }
250
+ * })
226
251
  * })
252
+ * const name = ipns(node)
253
+ *
254
+ * const result = await name.resolveDNSLink('ipfs.io')
227
255
  * ```
228
256
  */
229
257
 
230
- import { CodeError } from '@libp2p/interface'
258
+ import { NotFoundError, isPublicKey } from '@libp2p/interface'
231
259
  import { logger } from '@libp2p/logger'
232
- import { peerIdFromString } from '@libp2p/peer-id'
233
- import { create, marshal, peerIdToRoutingKey, unmarshal } from 'ipns'
260
+ import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord, type IPNSRecord } from 'ipns'
234
261
  import { ipnsSelector } from 'ipns/selector'
235
262
  import { ipnsValidator } from 'ipns/validator'
263
+ import { base36 } from 'multiformats/bases/base36'
264
+ import { base58btc } from 'multiformats/bases/base58'
236
265
  import { CID } from 'multiformats/cid'
266
+ import * as Digest from 'multiformats/hashes/digest'
237
267
  import { CustomProgressEvent } from 'progress-events'
238
268
  import { resolveDNSLink } from './dnslink.js'
269
+ import { InvalidValueError, RecordsFailedValidationError, UnsupportedMultibasePrefixError, UnsupportedMultihashCodecError } from './errors.js'
239
270
  import { helia } from './routing/helia.js'
240
271
  import { localStore, type LocalStore } from './routing/local-store.js'
272
+ import { isCodec, IDENTITY_CODEC, SHA2_256_CODEC } from './utils.js'
241
273
  import type { IPNSRouting, IPNSRoutingEvents } from './routing/index.js'
242
274
  import type { Routing } from '@helia/interface'
243
- import type { AbortOptions, ComponentLogger, Logger, PeerId } from '@libp2p/interface'
275
+ import type { AbortOptions, ComponentLogger, Logger, PrivateKey, PublicKey } from '@libp2p/interface'
244
276
  import type { Answer, DNS, ResolveDnsProgressEvents } from '@multiformats/dns'
245
277
  import type { Datastore } from 'interface-datastore'
246
- import type { IPNSRecord } from 'ipns'
278
+ import type { MultibaseDecoder } from 'multiformats/bases/interface'
279
+ import type { MultihashDigest } from 'multiformats/hashes/interface'
247
280
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
248
281
 
249
282
  const log = logger('helia:ipns')
@@ -375,13 +408,13 @@ export interface IPNS {
375
408
  *
376
409
  * If the value is a PeerId, a recursive IPNS record will be created.
377
410
  */
378
- publish(key: PeerId, value: CID | PeerId | string, options?: PublishOptions): Promise<IPNSRecord>
411
+ publish(key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options?: PublishOptions): Promise<IPNSRecord>
379
412
 
380
413
  /**
381
414
  * Accepts a public key formatted as a libp2p PeerID and resolves the IPNS record
382
415
  * corresponding to that public key until a value is found
383
416
  */
384
- resolve(key: PeerId, options?: ResolveOptions): Promise<IPNSResolveResult>
417
+ resolve(key: PublicKey | MultihashDigest<0x00 | 0x12>, options?: ResolveOptions): Promise<IPNSResolveResult>
385
418
 
386
419
  /**
387
420
  * Resolve a CID from a dns-link style IPNS record
@@ -403,6 +436,11 @@ export interface IPNSComponents {
403
436
  logger: ComponentLogger
404
437
  }
405
438
 
439
+ const bases: Record<string, MultibaseDecoder<string>> = {
440
+ [base36.prefix]: base36,
441
+ [base58btc.prefix]: base58btc
442
+ }
443
+
406
444
  class DefaultIPNS implements IPNS {
407
445
  private readonly routers: IPNSRouting[]
408
446
  private readonly localStore: LocalStore
@@ -420,21 +458,21 @@ class DefaultIPNS implements IPNS {
420
458
  this.log = components.logger.forComponent('helia:ipns')
421
459
  }
422
460
 
423
- async publish (key: PeerId, value: CID | PeerId | string, options: PublishOptions = {}): Promise<IPNSRecord> {
461
+ async publish (key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options: PublishOptions = {}): Promise<IPNSRecord> {
424
462
  try {
425
463
  let sequenceNumber = 1n
426
- const routingKey = peerIdToRoutingKey(key)
464
+ const routingKey = multihashToIPNSRoutingKey(key.publicKey.toMultihash())
427
465
 
428
466
  if (await this.localStore.has(routingKey, options)) {
429
467
  // if we have published under this key before, increment the sequence number
430
468
  const { record } = await this.localStore.get(routingKey, options)
431
- const existingRecord = unmarshal(record)
469
+ const existingRecord = unmarshalIPNSRecord(record)
432
470
  sequenceNumber = existingRecord.sequence + 1n
433
471
  }
434
472
 
435
473
  // create record
436
- const record = await create(key, value, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS, options)
437
- const marshaledRecord = marshal(record)
474
+ const record = await createIPNSRecord(key, value, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS, options)
475
+ const marshaledRecord = marshalIPNSRecord(record)
438
476
 
439
477
  await this.localStore.put(routingKey, marshaledRecord, options)
440
478
 
@@ -450,8 +488,9 @@ class DefaultIPNS implements IPNS {
450
488
  }
451
489
  }
452
490
 
453
- async resolve (key: PeerId, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
454
- const routingKey = peerIdToRoutingKey(key)
491
+ async resolve (key: PublicKey | MultihashDigest<0x00 | 0x12>, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
492
+ const digest = isPublicKey(key) ? key.toMultihash() : key
493
+ const routingKey = multihashToIPNSRoutingKey(digest)
455
494
  const record = await this.#findIpnsRecord(routingKey, options)
456
495
 
457
496
  return {
@@ -511,7 +550,31 @@ class DefaultIPNS implements IPNS {
511
550
  const scheme = parts[1]
512
551
 
513
552
  if (scheme === 'ipns') {
514
- const { cid } = await this.resolve(peerIdFromString(parts[2]), options)
553
+ const str = parts[2]
554
+ const prefix = str.substring(0, 1)
555
+ let buf: Uint8Array | undefined
556
+
557
+ if (prefix === '1' || prefix === 'Q') {
558
+ buf = base58btc.decode(`z${str}`)
559
+ } else if (bases[prefix] != null) {
560
+ buf = bases[prefix].decode(str)
561
+ } else {
562
+ throw new UnsupportedMultibasePrefixError(`Unsupported multibase prefix "${prefix}"`)
563
+ }
564
+
565
+ let digest: MultihashDigest<number>
566
+
567
+ try {
568
+ digest = Digest.decode(buf)
569
+ } catch {
570
+ digest = CID.decode(buf).multihash
571
+ }
572
+
573
+ if (!isCodec(digest, IDENTITY_CODEC) && !isCodec(digest, SHA2_256_CODEC)) {
574
+ throw new UnsupportedMultihashCodecError(`Unsupported multihash codec "${digest.code}"`)
575
+ }
576
+
577
+ const { cid } = await this.resolve(digest, options)
515
578
  const path = parts.slice(3).join('/')
516
579
  return {
517
580
  cid,
@@ -530,7 +593,7 @@ class DefaultIPNS implements IPNS {
530
593
  }
531
594
 
532
595
  log.error('invalid ipfs path %s', ipfsPath)
533
- throw new Error('Invalid value')
596
+ throw new InvalidValueError('Invalid value')
534
597
  }
535
598
 
536
599
  async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
@@ -553,7 +616,7 @@ class DefaultIPNS implements IPNS {
553
616
  this.log('record was valid')
554
617
 
555
618
  // check the TTL
556
- const ipnsRecord = unmarshal(record)
619
+ const ipnsRecord = unmarshalIPNSRecord(record)
557
620
 
558
621
  // IPNS TTL is in nanoseconds, convert to milliseconds, default to one
559
622
  // hour
@@ -588,7 +651,7 @@ class DefaultIPNS implements IPNS {
588
651
  }
589
652
 
590
653
  if (options.offline === true) {
591
- throw new CodeError('Record was not present in the cache or has expired', 'ERR_NOT_FOUND')
654
+ throw new NotFoundError('Record was not present in the cache or has expired')
592
655
  }
593
656
 
594
657
  log('did not have record locally')
@@ -624,17 +687,17 @@ class DefaultIPNS implements IPNS {
624
687
 
625
688
  if (records.length === 0) {
626
689
  if (foundInvalid > 0) {
627
- throw new CodeError(`${foundInvalid > 1 ? `${foundInvalid} records` : 'Record'} found for routing key ${foundInvalid > 1 ? 'were' : 'was'} invalid`, 'ERR_RECORDS_FAILED_VALIDATION')
690
+ throw new RecordsFailedValidationError(`${foundInvalid > 1 ? `${foundInvalid} records` : 'Record'} found for routing key ${foundInvalid > 1 ? 'were' : 'was'} invalid`)
628
691
  }
629
692
 
630
- throw new CodeError('Could not find record for routing key', 'ERR_NOT_FOUND')
693
+ throw new NotFoundError('Could not find record for routing key')
631
694
  }
632
695
 
633
696
  const record = records[ipnsSelector(routingKey, records)]
634
697
 
635
698
  await this.localStore.put(routingKey, record, options)
636
699
 
637
- return unmarshal(record)
700
+ return unmarshalIPNSRecord(record)
638
701
  }
639
702
  }
640
703
 
@@ -22,6 +22,10 @@ export interface IPNSRouting {
22
22
  get(routingKey: Uint8Array, options?: GetOptions): Promise<Uint8Array>
23
23
  }
24
24
 
25
+ export type { DatastoreProgressEvents }
26
+ export type { HeliaRoutingProgressEvents }
27
+ export type { PubSubProgressEvents }
28
+
25
29
  export type IPNSRoutingEvents =
26
30
  DatastoreProgressEvents |
27
31
  HeliaRoutingProgressEvents |
@@ -29,3 +33,4 @@ export type IPNSRoutingEvents =
29
33
 
30
34
  export { helia } from './helia.js'
31
35
  export { pubsub } from './pubsub.js'
36
+ export type { PubsubRoutingComponents } from './pubsub.js'
@@ -48,7 +48,7 @@ export function localStore (datastore: Datastore): LocalStore {
48
48
  return
49
49
  }
50
50
  } catch (err: any) {
51
- if (err.code !== 'ERR_NOT_FOUND') {
51
+ if (err.name !== 'NotFoundError') {
52
52
  throw err
53
53
  }
54
54
  }
@@ -1,16 +1,18 @@
1
- import { CodeError } from '@libp2p/interface'
1
+ import { isPublicKey } from '@libp2p/interface'
2
2
  import { logger } from '@libp2p/logger'
3
- import { peerIdToRoutingKey } from 'ipns'
3
+ import { multihashToIPNSRoutingKey } from 'ipns'
4
4
  import { ipnsSelector } from 'ipns/selector'
5
5
  import { ipnsValidator } from 'ipns/validator'
6
6
  import { CustomProgressEvent, type ProgressEvent } from 'progress-events'
7
7
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
8
8
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
9
9
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
10
+ import { InvalidTopicError } from '../errors.js'
10
11
  import { localStore, type LocalStore } from './local-store.js'
11
12
  import type { GetOptions, IPNSRouting, PutOptions } from './index.js'
12
- import type { PeerId, Message, PublishResult, PubSub } from '@libp2p/interface'
13
+ import type { PeerId, Message, PublishResult, PubSub, PublicKey } from '@libp2p/interface'
13
14
  import type { Datastore } from 'interface-datastore'
15
+ import type { MultihashDigest } from 'multiformats/hashes/interface'
14
16
 
15
17
  const log = logger('helia:ipns:routing:pubsub')
16
18
 
@@ -147,8 +149,9 @@ class PubSubRouting implements IPNSRouting {
147
149
  /**
148
150
  * Cancel pubsub subscriptions related to ipns
149
151
  */
150
- cancel (key: PeerId): void {
151
- const routingKey = peerIdToRoutingKey(key)
152
+ cancel (key: PublicKey | MultihashDigest<0x00 | 0x12>): void {
153
+ const digest = isPublicKey(key) ? key.toMultihash() : key
154
+ const routingKey = multihashToIPNSRoutingKey(digest)
152
155
  const topic = keyToTopic(routingKey)
153
156
 
154
157
  // Not found topic
@@ -177,7 +180,7 @@ function keyToTopic (key: Uint8Array): string {
177
180
  */
178
181
  function topicToKey (topic: string): Uint8Array {
179
182
  if (topic.substring(0, PUBSUB_NAMESPACE.length) !== PUBSUB_NAMESPACE) {
180
- throw new CodeError('topic received is not from a record', 'ERR_TOPIC_IS_NOT_FROM_RECORD_NAMESPACE')
183
+ throw new InvalidTopicError('Topic received is not from a record')
181
184
  }
182
185
 
183
186
  const key = topic.substring(PUBSUB_NAMESPACE.length)
package/src/utils.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { MultihashDigest } from 'multiformats/hashes/interface'
2
+
3
+ export const IDENTITY_CODEC = 0x0
4
+ export const SHA2_256_CODEC = 0x12
5
+
6
+ export function isCodec <T extends number> (digest: MultihashDigest, codec: T): digest is MultihashDigest<T> {
7
+ return digest.code === codec
8
+ }