@helia/ipns 7.2.3-c9c644c → 7.2.3-ec8bf66
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/dist/index.min.js +4 -5
- package/dist/src/dnslink.d.ts +1 -1
- package/dist/src/dnslink.d.ts.map +1 -1
- package/dist/src/dnslink.js +6 -5
- package/dist/src/dnslink.js.map +1 -1
- package/dist/src/errors.d.ts +25 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +43 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +46 -15
- package/dist/src/index.js.map +1 -1
- package/dist/src/routing/local-store.js +1 -1
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +6 -4
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/utils.d.ts +5 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +6 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +9 -9
- package/src/dnslink.ts +7 -5
- package/src/errors.ts +53 -0
- package/src/index.ts +56 -21
- package/src/routing/local-store.ts +1 -1
- package/src/routing/pubsub.ts +9 -6
- package/src/utils.ts +8 -0
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
|
@@ -227,23 +227,28 @@
|
|
|
227
227
|
* ```
|
|
228
228
|
*/
|
|
229
229
|
|
|
230
|
-
import {
|
|
230
|
+
import { NotFoundError, isPublicKey } from '@libp2p/interface'
|
|
231
231
|
import { logger } from '@libp2p/logger'
|
|
232
|
-
import {
|
|
233
|
-
import { create, marshal, peerIdToRoutingKey, unmarshal } from 'ipns'
|
|
232
|
+
import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord, type IPNSRecord } from 'ipns'
|
|
234
233
|
import { ipnsSelector } from 'ipns/selector'
|
|
235
234
|
import { ipnsValidator } from 'ipns/validator'
|
|
235
|
+
import { base36 } from 'multiformats/bases/base36'
|
|
236
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
236
237
|
import { CID } from 'multiformats/cid'
|
|
238
|
+
import * as Digest from 'multiformats/hashes/digest'
|
|
237
239
|
import { CustomProgressEvent } from 'progress-events'
|
|
238
240
|
import { resolveDNSLink } from './dnslink.js'
|
|
241
|
+
import { InvalidValueError, RecordsFailedValidationError, UnsupportedMultibasePrefixError, UnsupportedMultihashCodecError } from './errors.js'
|
|
239
242
|
import { helia } from './routing/helia.js'
|
|
240
243
|
import { localStore, type LocalStore } from './routing/local-store.js'
|
|
244
|
+
import { isCodec, IDENTITY_CODEC, SHA2_256_CODEC } from './utils.js'
|
|
241
245
|
import type { IPNSRouting, IPNSRoutingEvents } from './routing/index.js'
|
|
242
246
|
import type { Routing } from '@helia/interface'
|
|
243
|
-
import type { AbortOptions, ComponentLogger, Logger,
|
|
247
|
+
import type { AbortOptions, ComponentLogger, Logger, PrivateKey, PublicKey } from '@libp2p/interface'
|
|
244
248
|
import type { Answer, DNS, ResolveDnsProgressEvents } from '@multiformats/dns'
|
|
245
249
|
import type { Datastore } from 'interface-datastore'
|
|
246
|
-
import type {
|
|
250
|
+
import type { MultibaseDecoder } from 'multiformats/bases/interface'
|
|
251
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
247
252
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
248
253
|
|
|
249
254
|
const log = logger('helia:ipns')
|
|
@@ -375,13 +380,13 @@ export interface IPNS {
|
|
|
375
380
|
*
|
|
376
381
|
* If the value is a PeerId, a recursive IPNS record will be created.
|
|
377
382
|
*/
|
|
378
|
-
publish(key:
|
|
383
|
+
publish(key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options?: PublishOptions): Promise<IPNSRecord>
|
|
379
384
|
|
|
380
385
|
/**
|
|
381
386
|
* Accepts a public key formatted as a libp2p PeerID and resolves the IPNS record
|
|
382
387
|
* corresponding to that public key until a value is found
|
|
383
388
|
*/
|
|
384
|
-
resolve(key:
|
|
389
|
+
resolve(key: PublicKey | MultihashDigest<0x00 | 0x12>, options?: ResolveOptions): Promise<IPNSResolveResult>
|
|
385
390
|
|
|
386
391
|
/**
|
|
387
392
|
* Resolve a CID from a dns-link style IPNS record
|
|
@@ -403,6 +408,11 @@ export interface IPNSComponents {
|
|
|
403
408
|
logger: ComponentLogger
|
|
404
409
|
}
|
|
405
410
|
|
|
411
|
+
const bases: Record<string, MultibaseDecoder<string>> = {
|
|
412
|
+
[base36.prefix]: base36,
|
|
413
|
+
[base58btc.prefix]: base58btc
|
|
414
|
+
}
|
|
415
|
+
|
|
406
416
|
class DefaultIPNS implements IPNS {
|
|
407
417
|
private readonly routers: IPNSRouting[]
|
|
408
418
|
private readonly localStore: LocalStore
|
|
@@ -420,21 +430,21 @@ class DefaultIPNS implements IPNS {
|
|
|
420
430
|
this.log = components.logger.forComponent('helia:ipns')
|
|
421
431
|
}
|
|
422
432
|
|
|
423
|
-
async publish (key:
|
|
433
|
+
async publish (key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options: PublishOptions = {}): Promise<IPNSRecord> {
|
|
424
434
|
try {
|
|
425
435
|
let sequenceNumber = 1n
|
|
426
|
-
const routingKey =
|
|
436
|
+
const routingKey = multihashToIPNSRoutingKey(key.publicKey.toMultihash())
|
|
427
437
|
|
|
428
438
|
if (await this.localStore.has(routingKey, options)) {
|
|
429
439
|
// if we have published under this key before, increment the sequence number
|
|
430
440
|
const { record } = await this.localStore.get(routingKey, options)
|
|
431
|
-
const existingRecord =
|
|
441
|
+
const existingRecord = unmarshalIPNSRecord(record)
|
|
432
442
|
sequenceNumber = existingRecord.sequence + 1n
|
|
433
443
|
}
|
|
434
444
|
|
|
435
445
|
// create record
|
|
436
|
-
const record = await
|
|
437
|
-
const marshaledRecord =
|
|
446
|
+
const record = await createIPNSRecord(key, value, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS, options)
|
|
447
|
+
const marshaledRecord = marshalIPNSRecord(record)
|
|
438
448
|
|
|
439
449
|
await this.localStore.put(routingKey, marshaledRecord, options)
|
|
440
450
|
|
|
@@ -450,8 +460,9 @@ class DefaultIPNS implements IPNS {
|
|
|
450
460
|
}
|
|
451
461
|
}
|
|
452
462
|
|
|
453
|
-
async resolve (key:
|
|
454
|
-
const
|
|
463
|
+
async resolve (key: PublicKey | MultihashDigest<0x00 | 0x12>, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
|
|
464
|
+
const digest = isPublicKey(key) ? key.toMultihash() : key
|
|
465
|
+
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
455
466
|
const record = await this.#findIpnsRecord(routingKey, options)
|
|
456
467
|
|
|
457
468
|
return {
|
|
@@ -511,7 +522,31 @@ class DefaultIPNS implements IPNS {
|
|
|
511
522
|
const scheme = parts[1]
|
|
512
523
|
|
|
513
524
|
if (scheme === 'ipns') {
|
|
514
|
-
const
|
|
525
|
+
const str = parts[2]
|
|
526
|
+
const prefix = str.substring(0, 1)
|
|
527
|
+
let buf: Uint8Array | undefined
|
|
528
|
+
|
|
529
|
+
if (prefix === '1' || prefix === 'Q') {
|
|
530
|
+
buf = base58btc.decode(`z${str}`)
|
|
531
|
+
} else if (bases[prefix] != null) {
|
|
532
|
+
buf = bases[prefix].decode(str)
|
|
533
|
+
} else {
|
|
534
|
+
throw new UnsupportedMultibasePrefixError(`Unsupported multibase prefix "${prefix}"`)
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let digest: MultihashDigest<number>
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
digest = Digest.decode(buf)
|
|
541
|
+
} catch {
|
|
542
|
+
digest = CID.decode(buf).multihash
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!isCodec(digest, IDENTITY_CODEC) && !isCodec(digest, SHA2_256_CODEC)) {
|
|
546
|
+
throw new UnsupportedMultihashCodecError(`Unsupported multihash codec "${digest.code}"`)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const { cid } = await this.resolve(digest, options)
|
|
515
550
|
const path = parts.slice(3).join('/')
|
|
516
551
|
return {
|
|
517
552
|
cid,
|
|
@@ -530,7 +565,7 @@ class DefaultIPNS implements IPNS {
|
|
|
530
565
|
}
|
|
531
566
|
|
|
532
567
|
log.error('invalid ipfs path %s', ipfsPath)
|
|
533
|
-
throw new
|
|
568
|
+
throw new InvalidValueError('Invalid value')
|
|
534
569
|
}
|
|
535
570
|
|
|
536
571
|
async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
|
|
@@ -553,7 +588,7 @@ class DefaultIPNS implements IPNS {
|
|
|
553
588
|
this.log('record was valid')
|
|
554
589
|
|
|
555
590
|
// check the TTL
|
|
556
|
-
const ipnsRecord =
|
|
591
|
+
const ipnsRecord = unmarshalIPNSRecord(record)
|
|
557
592
|
|
|
558
593
|
// IPNS TTL is in nanoseconds, convert to milliseconds, default to one
|
|
559
594
|
// hour
|
|
@@ -588,7 +623,7 @@ class DefaultIPNS implements IPNS {
|
|
|
588
623
|
}
|
|
589
624
|
|
|
590
625
|
if (options.offline === true) {
|
|
591
|
-
throw new
|
|
626
|
+
throw new NotFoundError('Record was not present in the cache or has expired')
|
|
592
627
|
}
|
|
593
628
|
|
|
594
629
|
log('did not have record locally')
|
|
@@ -624,17 +659,17 @@ class DefaultIPNS implements IPNS {
|
|
|
624
659
|
|
|
625
660
|
if (records.length === 0) {
|
|
626
661
|
if (foundInvalid > 0) {
|
|
627
|
-
throw new
|
|
662
|
+
throw new RecordsFailedValidationError(`${foundInvalid > 1 ? `${foundInvalid} records` : 'Record'} found for routing key ${foundInvalid > 1 ? 'were' : 'was'} invalid`)
|
|
628
663
|
}
|
|
629
664
|
|
|
630
|
-
throw new
|
|
665
|
+
throw new NotFoundError('Could not find record for routing key')
|
|
631
666
|
}
|
|
632
667
|
|
|
633
668
|
const record = records[ipnsSelector(routingKey, records)]
|
|
634
669
|
|
|
635
670
|
await this.localStore.put(routingKey, record, options)
|
|
636
671
|
|
|
637
|
-
return
|
|
672
|
+
return unmarshalIPNSRecord(record)
|
|
638
673
|
}
|
|
639
674
|
}
|
|
640
675
|
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPublicKey } from '@libp2p/interface'
|
|
2
2
|
import { logger } from '@libp2p/logger'
|
|
3
|
-
import {
|
|
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:
|
|
151
|
-
const
|
|
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
|
|
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
|
+
}
|