@helia/ipns 7.0.0-ecf5394 → 7.1.0-1561e4a
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 -4
- package/dist/src/dnslink.d.ts +6 -2
- package/dist/src/dnslink.d.ts.map +1 -1
- package/dist/src/dnslink.js +8 -2
- package/dist/src/dnslink.js.map +1 -1
- package/dist/src/index.d.ts +32 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +58 -19
- package/dist/src/index.js.map +1 -1
- package/dist/src/routing/local-store.d.ts +9 -2
- package/dist/src/routing/local-store.d.ts.map +1 -1
- package/dist/src/routing/local-store.js +23 -1
- package/dist/src/routing/local-store.js.map +1 -1
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +3 -2
- package/dist/src/routing/pubsub.js.map +1 -1
- package/package.json +4 -3
- package/src/dnslink.ts +17 -6
- package/src/index.ts +108 -25
- package/src/routing/local-store.ts +35 -4
- package/src/routing/pubsub.ts +4 -2
package/src/index.ts
CHANGED
|
@@ -241,7 +241,7 @@ import { localStore, type LocalStore } from './routing/local-store.js'
|
|
|
241
241
|
import type { IPNSRouting, IPNSRoutingEvents } from './routing/index.js'
|
|
242
242
|
import type { Routing } from '@helia/interface'
|
|
243
243
|
import type { AbortOptions, ComponentLogger, Logger, PeerId } from '@libp2p/interface'
|
|
244
|
-
import type { DNS, ResolveDnsProgressEvents } from '@multiformats/dns'
|
|
244
|
+
import type { Answer, DNS, ResolveDnsProgressEvents } from '@multiformats/dns'
|
|
245
245
|
import type { Datastore } from 'interface-datastore'
|
|
246
246
|
import type { IPNSRecord } from 'ipns'
|
|
247
247
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
@@ -254,6 +254,8 @@ const HOUR = 60 * MINUTE
|
|
|
254
254
|
const DEFAULT_LIFETIME_MS = 24 * HOUR
|
|
255
255
|
const DEFAULT_REPUBLISH_INTERVAL_MS = 23 * HOUR
|
|
256
256
|
|
|
257
|
+
const DEFAULT_TTL_NS = BigInt(HOUR) * 1_000_000n
|
|
258
|
+
|
|
257
259
|
export type PublishProgressEvents =
|
|
258
260
|
ProgressEvent<'ipns:publish:start'> |
|
|
259
261
|
ProgressEvent<'ipns:publish:success', IPNSRecord> |
|
|
@@ -294,9 +296,18 @@ export interface PublishOptions extends AbortOptions, ProgressOptions<PublishPro
|
|
|
294
296
|
|
|
295
297
|
export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingEvents> {
|
|
296
298
|
/**
|
|
297
|
-
* Do not query the network for the IPNS record
|
|
299
|
+
* Do not query the network for the IPNS record
|
|
300
|
+
*
|
|
301
|
+
* @default false
|
|
298
302
|
*/
|
|
299
303
|
offline?: boolean
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Do not use cached IPNS Record entries
|
|
307
|
+
*
|
|
308
|
+
* @default false
|
|
309
|
+
*/
|
|
310
|
+
nocache?: boolean
|
|
300
311
|
}
|
|
301
312
|
|
|
302
313
|
export interface ResolveDNSLinkOptions extends AbortOptions, ProgressOptions<ResolveDNSLinkProgressEvents> {
|
|
@@ -331,10 +342,33 @@ export interface RepublishOptions extends AbortOptions, ProgressOptions<Republis
|
|
|
331
342
|
}
|
|
332
343
|
|
|
333
344
|
export interface ResolveResult {
|
|
345
|
+
/**
|
|
346
|
+
* The CID that was resolved
|
|
347
|
+
*/
|
|
334
348
|
cid: CID
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Any path component that was part of the resolved record
|
|
352
|
+
*
|
|
353
|
+
* @default ""
|
|
354
|
+
*/
|
|
335
355
|
path: string
|
|
336
356
|
}
|
|
337
357
|
|
|
358
|
+
export interface IPNSResolveResult extends ResolveResult {
|
|
359
|
+
/**
|
|
360
|
+
* The resolved record
|
|
361
|
+
*/
|
|
362
|
+
record: IPNSRecord
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface DNSLinkResolveResult extends ResolveResult {
|
|
366
|
+
/**
|
|
367
|
+
* The resolved record
|
|
368
|
+
*/
|
|
369
|
+
answer: Answer
|
|
370
|
+
}
|
|
371
|
+
|
|
338
372
|
export interface IPNS {
|
|
339
373
|
/**
|
|
340
374
|
* Creates an IPNS record signed by the passed PeerId that will resolve to the passed value
|
|
@@ -347,12 +381,12 @@ export interface IPNS {
|
|
|
347
381
|
* Accepts a public key formatted as a libp2p PeerID and resolves the IPNS record
|
|
348
382
|
* corresponding to that public key until a value is found
|
|
349
383
|
*/
|
|
350
|
-
resolve(key: PeerId, options?: ResolveOptions): Promise<
|
|
384
|
+
resolve(key: PeerId, options?: ResolveOptions): Promise<IPNSResolveResult>
|
|
351
385
|
|
|
352
386
|
/**
|
|
353
387
|
* Resolve a CID from a dns-link style IPNS record
|
|
354
388
|
*/
|
|
355
|
-
resolveDNSLink(domain: string, options?: ResolveDNSLinkOptions): Promise<
|
|
389
|
+
resolveDNSLink(domain: string, options?: ResolveDNSLinkOptions): Promise<DNSLinkResolveResult>
|
|
356
390
|
|
|
357
391
|
/**
|
|
358
392
|
* Periodically republish all IPNS records found in the datastore
|
|
@@ -393,8 +427,8 @@ class DefaultIPNS implements IPNS {
|
|
|
393
427
|
|
|
394
428
|
if (await this.localStore.has(routingKey, options)) {
|
|
395
429
|
// if we have published under this key before, increment the sequence number
|
|
396
|
-
const
|
|
397
|
-
const existingRecord = unmarshal(
|
|
430
|
+
const { record } = await this.localStore.get(routingKey, options)
|
|
431
|
+
const existingRecord = unmarshal(record)
|
|
398
432
|
sequenceNumber = existingRecord.sequence + 1n
|
|
399
433
|
}
|
|
400
434
|
|
|
@@ -416,17 +450,23 @@ class DefaultIPNS implements IPNS {
|
|
|
416
450
|
}
|
|
417
451
|
}
|
|
418
452
|
|
|
419
|
-
async resolve (key: PeerId, options: ResolveOptions = {}): Promise<
|
|
453
|
+
async resolve (key: PeerId, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
|
|
420
454
|
const routingKey = peerIdToRoutingKey(key)
|
|
421
455
|
const record = await this.#findIpnsRecord(routingKey, options)
|
|
422
456
|
|
|
423
|
-
return
|
|
457
|
+
return {
|
|
458
|
+
...(await this.#resolve(record.value, options)),
|
|
459
|
+
record
|
|
460
|
+
}
|
|
424
461
|
}
|
|
425
462
|
|
|
426
|
-
async resolveDNSLink (domain: string, options: ResolveDNSLinkOptions = {}): Promise<
|
|
463
|
+
async resolveDNSLink (domain: string, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResolveResult> {
|
|
427
464
|
const dnslink = await resolveDNSLink(domain, this.dns, this.log, options)
|
|
428
465
|
|
|
429
|
-
return
|
|
466
|
+
return {
|
|
467
|
+
...(await this.#resolve(dnslink.value, options)),
|
|
468
|
+
answer: dnslink.answer
|
|
469
|
+
}
|
|
430
470
|
}
|
|
431
471
|
|
|
432
472
|
republish (options: RepublishOptions = {}): void {
|
|
@@ -465,7 +505,7 @@ class DefaultIPNS implements IPNS {
|
|
|
465
505
|
}, options.interval ?? DEFAULT_REPUBLISH_INTERVAL_MS)
|
|
466
506
|
}
|
|
467
507
|
|
|
468
|
-
async #resolve (ipfsPath: string, options: ResolveOptions = {}): Promise<
|
|
508
|
+
async #resolve (ipfsPath: string, options: ResolveOptions = {}): Promise<{ cid: CID, path: string }> {
|
|
469
509
|
const parts = ipfsPath.split('/')
|
|
470
510
|
try {
|
|
471
511
|
const scheme = parts[1]
|
|
@@ -494,22 +534,69 @@ class DefaultIPNS implements IPNS {
|
|
|
494
534
|
}
|
|
495
535
|
|
|
496
536
|
async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
537
|
+
const records: Uint8Array[] = []
|
|
538
|
+
const cached = await this.localStore.has(routingKey, options)
|
|
539
|
+
|
|
540
|
+
if (cached) {
|
|
541
|
+
log('record is present in the cache')
|
|
542
|
+
|
|
543
|
+
if (options.nocache !== true) {
|
|
544
|
+
try {
|
|
545
|
+
// check the local cache first
|
|
546
|
+
const { record, created } = await this.localStore.get(routingKey, options)
|
|
547
|
+
|
|
548
|
+
this.log('record retrieved from cache')
|
|
549
|
+
|
|
550
|
+
// validate the record
|
|
551
|
+
await ipnsValidator(routingKey, record)
|
|
552
|
+
|
|
553
|
+
this.log('record was valid')
|
|
554
|
+
|
|
555
|
+
// check the TTL
|
|
556
|
+
const ipnsRecord = unmarshal(record)
|
|
557
|
+
|
|
558
|
+
// IPNS TTL is in nanoseconds, convert to milliseconds, default to one
|
|
559
|
+
// hour
|
|
560
|
+
const ttlMs = Number((ipnsRecord.ttl ?? DEFAULT_TTL_NS) / 1_000_000n)
|
|
561
|
+
const ttlExpires = created.getTime() + ttlMs
|
|
562
|
+
|
|
563
|
+
if (ttlExpires > Date.now()) {
|
|
564
|
+
// the TTL has not yet expired, return the cached record
|
|
565
|
+
this.log('record TTL was valid')
|
|
566
|
+
return ipnsRecord
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (options.offline === true) {
|
|
570
|
+
// the TTL has expired but we are skipping the routing search
|
|
571
|
+
this.log('record TTL has been reached but we are resolving offline-only, returning record')
|
|
572
|
+
return ipnsRecord
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
this.log('record TTL has been reached, searching routing for updates')
|
|
576
|
+
|
|
577
|
+
// add the local record to our list of resolved record, and also
|
|
578
|
+
// search the routing for updates - the most up to date record will be
|
|
579
|
+
// returned
|
|
580
|
+
records.push(record)
|
|
581
|
+
} catch (err) {
|
|
582
|
+
this.log('cached record was invalid', err)
|
|
583
|
+
await this.localStore.delete(routingKey, options)
|
|
584
|
+
}
|
|
585
|
+
} else {
|
|
586
|
+
log('ignoring local cache due to nocache=true option')
|
|
587
|
+
}
|
|
588
|
+
}
|
|
501
589
|
|
|
502
590
|
if (options.offline === true) {
|
|
503
|
-
|
|
504
|
-
this.localStore
|
|
505
|
-
]
|
|
591
|
+
throw new CodeError('Record was not present in the cache or has expired', 'ERR_NOT_FOUND')
|
|
506
592
|
}
|
|
507
593
|
|
|
508
|
-
|
|
594
|
+
log('did not have record locally')
|
|
595
|
+
|
|
509
596
|
let foundInvalid = 0
|
|
510
597
|
|
|
511
598
|
await Promise.all(
|
|
512
|
-
routers.map(async (router) => {
|
|
599
|
+
this.routers.map(async (router) => {
|
|
513
600
|
let record: Uint8Array
|
|
514
601
|
|
|
515
602
|
try {
|
|
@@ -518,11 +605,7 @@ class DefaultIPNS implements IPNS {
|
|
|
518
605
|
validate: false
|
|
519
606
|
})
|
|
520
607
|
} catch (err: any) {
|
|
521
|
-
|
|
522
|
-
log('did not have record locally')
|
|
523
|
-
} else {
|
|
524
|
-
log.error('error finding IPNS record', err)
|
|
525
|
-
}
|
|
608
|
+
log.error('error finding IPNS record', err)
|
|
526
609
|
|
|
527
610
|
return
|
|
528
611
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Record } from '@libp2p/kad-dht'
|
|
2
2
|
import { type Datastore, Key } from 'interface-datastore'
|
|
3
3
|
import { CustomProgressEvent, type ProgressEvent } from 'progress-events'
|
|
4
|
+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
4
5
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
5
|
-
import type { GetOptions,
|
|
6
|
+
import type { GetOptions, PutOptions } from '../routing'
|
|
6
7
|
import type { AbortOptions } from '@libp2p/interface'
|
|
7
8
|
|
|
8
9
|
function dhtRoutingKey (key: Uint8Array): Key {
|
|
@@ -14,8 +15,16 @@ export type DatastoreProgressEvents =
|
|
|
14
15
|
ProgressEvent<'ipns:routing:datastore:get'> |
|
|
15
16
|
ProgressEvent<'ipns:routing:datastore:error', Error>
|
|
16
17
|
|
|
17
|
-
export interface
|
|
18
|
+
export interface GetResult {
|
|
19
|
+
record: Uint8Array
|
|
20
|
+
created: Date
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LocalStore {
|
|
24
|
+
put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void>
|
|
25
|
+
get(routingKey: Uint8Array, options?: GetOptions): Promise<GetResult>
|
|
18
26
|
has(routingKey: Uint8Array, options?: AbortOptions): Promise<boolean>
|
|
27
|
+
delete(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
/**
|
|
@@ -29,6 +38,21 @@ export function localStore (datastore: Datastore): LocalStore {
|
|
|
29
38
|
try {
|
|
30
39
|
const key = dhtRoutingKey(routingKey)
|
|
31
40
|
|
|
41
|
+
// don't overwrite existing, identical records as this will affect the
|
|
42
|
+
// TTL
|
|
43
|
+
try {
|
|
44
|
+
const existingBuf = await datastore.get(key)
|
|
45
|
+
const existingRecord = Record.deserialize(existingBuf)
|
|
46
|
+
|
|
47
|
+
if (uint8ArrayEquals(existingRecord.value, marshalledRecord)) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
if (err.code !== 'ERR_NOT_FOUND') {
|
|
52
|
+
throw err
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
32
56
|
// Marshal to libp2p record as the DHT does
|
|
33
57
|
const record = new Record(routingKey, marshalledRecord, new Date())
|
|
34
58
|
|
|
@@ -39,7 +63,7 @@ export function localStore (datastore: Datastore): LocalStore {
|
|
|
39
63
|
throw err
|
|
40
64
|
}
|
|
41
65
|
},
|
|
42
|
-
async get (routingKey: Uint8Array, options: GetOptions = {}): Promise<
|
|
66
|
+
async get (routingKey: Uint8Array, options: GetOptions = {}): Promise<GetResult> {
|
|
43
67
|
try {
|
|
44
68
|
const key = dhtRoutingKey(routingKey)
|
|
45
69
|
|
|
@@ -49,7 +73,10 @@ export function localStore (datastore: Datastore): LocalStore {
|
|
|
49
73
|
// Unmarshal libp2p record as the DHT does
|
|
50
74
|
const record = Record.deserialize(buf)
|
|
51
75
|
|
|
52
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
record: record.value,
|
|
78
|
+
created: record.timeReceived
|
|
79
|
+
}
|
|
53
80
|
} catch (err: any) {
|
|
54
81
|
options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
|
|
55
82
|
throw err
|
|
@@ -58,6 +85,10 @@ export function localStore (datastore: Datastore): LocalStore {
|
|
|
58
85
|
async has (routingKey: Uint8Array, options: AbortOptions = {}): Promise<boolean> {
|
|
59
86
|
const key = dhtRoutingKey(routingKey)
|
|
60
87
|
return datastore.has(key, options)
|
|
88
|
+
},
|
|
89
|
+
async delete (routingKey, options): Promise<void> {
|
|
90
|
+
const key = dhtRoutingKey(routingKey)
|
|
91
|
+
return datastore.delete(key, options)
|
|
61
92
|
}
|
|
62
93
|
}
|
|
63
94
|
}
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -72,7 +72,7 @@ class PubSubRouting implements IPNSRouting {
|
|
|
72
72
|
await ipnsValidator(routingKey, message.data)
|
|
73
73
|
|
|
74
74
|
if (await this.localStore.has(routingKey)) {
|
|
75
|
-
const currentRecord = await this.localStore.get(routingKey)
|
|
75
|
+
const { record: currentRecord } = await this.localStore.get(routingKey)
|
|
76
76
|
|
|
77
77
|
if (uint8ArrayEquals(currentRecord, message.data)) {
|
|
78
78
|
log('not storing record as we already have it')
|
|
@@ -128,7 +128,9 @@ class PubSubRouting implements IPNSRouting {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
// chain through to local store
|
|
131
|
-
|
|
131
|
+
const { record } = await this.localStore.get(routingKey, options)
|
|
132
|
+
|
|
133
|
+
return record
|
|
132
134
|
} catch (err: any) {
|
|
133
135
|
options.onProgress?.(new CustomProgressEvent<Error>('ipns:pubsub:error', err))
|
|
134
136
|
throw err
|