@helia/ipns 8.2.4 → 9.0.0-d9051cdc
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/README.md +31 -135
- package/dist/index.min.js +10 -11
- package/dist/index.min.js.map +4 -4
- package/dist/src/constants.d.ts +17 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +19 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/errors.d.ts +0 -4
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +0 -7
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +109 -201
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +34 -417
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns.d.ts +22 -0
- package/dist/src/ipns.d.ts.map +1 -0
- package/dist/src/ipns.js +339 -0
- package/dist/src/ipns.js.map +1 -0
- package/dist/src/local-store.d.ts +42 -0
- package/dist/src/local-store.d.ts.map +1 -0
- package/dist/src/local-store.js +119 -0
- package/dist/src/local-store.js.map +1 -0
- package/dist/src/pb/metadata.d.ts +12 -0
- package/dist/src/pb/metadata.d.ts.map +1 -0
- package/dist/src/pb/metadata.js +57 -0
- package/dist/src/pb/metadata.js.map +1 -0
- package/dist/src/routing/index.d.ts +4 -2
- package/dist/src/routing/index.d.ts.map +1 -1
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/routing/local-store.d.ts +4 -19
- package/dist/src/routing/local-store.d.ts.map +1 -1
- package/dist/src/routing/local-store.js +7 -62
- package/dist/src/routing/local-store.js.map +1 -1
- package/dist/src/routing/pubsub.d.ts +21 -1
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +2 -2
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/utils.d.ts +24 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +56 -0
- package/dist/src/utils.js.map +1 -1
- package/package.json +21 -23
- package/src/constants.ts +24 -0
- package/src/errors.ts +0 -9
- package/src/index.ts +116 -545
- package/src/ipns.ts +400 -0
- package/src/local-store.ts +162 -0
- package/src/pb/metadata.proto +9 -0
- package/src/pb/metadata.ts +74 -0
- package/src/routing/index.ts +4 -3
- package/src/routing/local-store.ts +9 -87
- package/src/routing/pubsub.ts +28 -4
- package/src/utils.ts +70 -0
- package/dist/src/dnslink.d.ts +0 -9
- package/dist/src/dnslink.d.ts.map +0 -1
- package/dist/src/dnslink.js +0 -138
- package/dist/src/dnslink.js.map +0 -1
- package/dist/typedoc-urls.json +0 -48
- package/src/dnslink.ts +0 -163
|
@@ -1,96 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { CustomProgressEvent } from 'progress-events'
|
|
4
|
-
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
5
|
-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
6
|
-
import type { GetOptions, PutOptions } from '../routing/index.js'
|
|
7
|
-
import type { AbortOptions } from '@libp2p/interface'
|
|
8
|
-
import type { Datastore } from 'interface-datastore'
|
|
9
|
-
import type { ProgressEvent } from 'progress-events'
|
|
10
|
-
|
|
11
|
-
function dhtRoutingKey (key: Uint8Array): Key {
|
|
12
|
-
return new Key('/dht/record/' + uint8ArrayToString(key, 'base32'), false)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type DatastoreProgressEvents =
|
|
16
|
-
ProgressEvent<'ipns:routing:datastore:put'> |
|
|
17
|
-
ProgressEvent<'ipns:routing:datastore:get'> |
|
|
18
|
-
ProgressEvent<'ipns:routing:datastore:error', Error>
|
|
19
|
-
|
|
20
|
-
export interface GetResult {
|
|
21
|
-
record: Uint8Array
|
|
22
|
-
created: Date
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface LocalStore {
|
|
26
|
-
put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void>
|
|
27
|
-
get(routingKey: Uint8Array, options?: GetOptions): Promise<GetResult>
|
|
28
|
-
has(routingKey: Uint8Array, options?: AbortOptions): Promise<boolean>
|
|
29
|
-
delete(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
|
|
30
|
-
}
|
|
1
|
+
import type { LocalStore } from '../local-store.ts'
|
|
2
|
+
import type { IPNSRouting, PutOptions, GetOptions } from './index.ts'
|
|
31
3
|
|
|
32
4
|
/**
|
|
33
|
-
* Returns an IPNSRouting implementation that reads/writes
|
|
34
|
-
* datastore as DHT records. This lets us publish IPNS records offline then
|
|
35
|
-
* serve them to the network later in response to DHT queries.
|
|
5
|
+
* Returns an IPNSRouting implementation that reads/writes to the local store
|
|
36
6
|
*/
|
|
37
|
-
export function
|
|
7
|
+
export function localStoreRouting (localStore: LocalStore): IPNSRouting {
|
|
38
8
|
return {
|
|
39
|
-
async put (routingKey: Uint8Array,
|
|
40
|
-
|
|
41
|
-
const key = dhtRoutingKey(routingKey)
|
|
42
|
-
|
|
43
|
-
// don't overwrite existing, identical records as this will affect the
|
|
44
|
-
// TTL
|
|
45
|
-
try {
|
|
46
|
-
const existingBuf = await datastore.get(key)
|
|
47
|
-
const existingRecord = Record.deserialize(existingBuf)
|
|
48
|
-
|
|
49
|
-
if (uint8ArrayEquals(existingRecord.value, marshalledRecord)) {
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
} catch (err: any) {
|
|
53
|
-
if (err.name !== 'NotFoundError') {
|
|
54
|
-
throw err
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Marshal to libp2p record as the DHT does
|
|
59
|
-
const record = new Record(routingKey, marshalledRecord, new Date())
|
|
60
|
-
|
|
61
|
-
options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:put'))
|
|
62
|
-
await datastore.put(key, record.serialize(), options)
|
|
63
|
-
} catch (err: any) {
|
|
64
|
-
options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
|
|
65
|
-
throw err
|
|
66
|
-
}
|
|
9
|
+
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void> {
|
|
10
|
+
await localStore.put(routingKey, marshaledRecord, options)
|
|
67
11
|
},
|
|
68
|
-
async get (routingKey: Uint8Array, options
|
|
69
|
-
|
|
70
|
-
const key = dhtRoutingKey(routingKey)
|
|
71
|
-
|
|
72
|
-
options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:get'))
|
|
73
|
-
const buf = await datastore.get(key, options)
|
|
12
|
+
async get (routingKey: Uint8Array, options?: GetOptions): Promise<Uint8Array> {
|
|
13
|
+
const { record } = await localStore.get(routingKey, options)
|
|
74
14
|
|
|
75
|
-
|
|
76
|
-
const record = Record.deserialize(buf)
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
record: record.value,
|
|
80
|
-
created: record.timeReceived
|
|
81
|
-
}
|
|
82
|
-
} catch (err: any) {
|
|
83
|
-
options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
|
|
84
|
-
throw err
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
async has (routingKey: Uint8Array, options: AbortOptions = {}): Promise<boolean> {
|
|
88
|
-
const key = dhtRoutingKey(routingKey)
|
|
89
|
-
return datastore.has(key, options)
|
|
90
|
-
},
|
|
91
|
-
async delete (routingKey, options): Promise<void> {
|
|
92
|
-
const key = dhtRoutingKey(routingKey)
|
|
93
|
-
return datastore.delete(key, options)
|
|
15
|
+
return record
|
|
94
16
|
}
|
|
95
17
|
}
|
|
96
18
|
}
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -8,18 +8,42 @@ 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
10
|
import { InvalidTopicError } from '../errors.js'
|
|
11
|
-
import { localStore } from '
|
|
11
|
+
import { localStore } from '../local-store.js'
|
|
12
12
|
import type { GetOptions, IPNSRouting, PutOptions } from './index.js'
|
|
13
|
-
import type { LocalStore } from '
|
|
14
|
-
import type { PeerId,
|
|
13
|
+
import type { LocalStore } from '../local-store.js'
|
|
14
|
+
import type { PeerId, PublicKey, TypedEventTarget, ComponentLogger } from '@libp2p/interface'
|
|
15
15
|
import type { Datastore } from 'interface-datastore'
|
|
16
16
|
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
17
17
|
import type { ProgressEvent } from 'progress-events'
|
|
18
18
|
|
|
19
19
|
const log = logger('helia:ipns:routing:pubsub')
|
|
20
20
|
|
|
21
|
+
export interface Message {
|
|
22
|
+
type: 'signed' | 'unsigned'
|
|
23
|
+
from: PeerId
|
|
24
|
+
topic: string
|
|
25
|
+
data: Uint8Array
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PubSubEvents {
|
|
29
|
+
message: CustomEvent<Message>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PublishResult {
|
|
33
|
+
recipients: PeerId[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface PubSub extends TypedEventTarget<PubSubEvents> {
|
|
37
|
+
subscribe(topic: string): void
|
|
38
|
+
unsubscribe(topic: string): void
|
|
39
|
+
publish(topic: string, message: Uint8Array): Promise<PublishResult>
|
|
40
|
+
getTopics(): string[]
|
|
41
|
+
getSubscribers(topic: string): PeerId[]
|
|
42
|
+
}
|
|
43
|
+
|
|
21
44
|
export interface PubsubRoutingComponents {
|
|
22
45
|
datastore: Datastore
|
|
46
|
+
logger: ComponentLogger
|
|
23
47
|
libp2p: {
|
|
24
48
|
peerId: PeerId
|
|
25
49
|
services: {
|
|
@@ -41,7 +65,7 @@ class PubSubRouting implements IPNSRouting {
|
|
|
41
65
|
|
|
42
66
|
constructor (components: PubsubRoutingComponents) {
|
|
43
67
|
this.subscriptions = []
|
|
44
|
-
this.localStore = localStore(components.datastore)
|
|
68
|
+
this.localStore = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'))
|
|
45
69
|
this.peerId = components.libp2p.peerId
|
|
46
70
|
this.pubsub = components.libp2p.services.pubsub
|
|
47
71
|
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { InvalidParametersError } from '@libp2p/interface'
|
|
2
|
+
import { Key } from 'interface-datastore'
|
|
3
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
4
|
+
import { DHT_EXPIRY_MS, REPUBLISH_THRESHOLD } from './constants.ts'
|
|
5
|
+
import type { IPNSRecord } from 'ipns'
|
|
6
|
+
import type { CID } from 'multiformats/cid'
|
|
1
7
|
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
2
8
|
|
|
9
|
+
export const LIBP2P_KEY_CODEC = 0x72
|
|
3
10
|
export const IDENTITY_CODEC = 0x0
|
|
4
11
|
export const SHA2_256_CODEC = 0x12
|
|
5
12
|
|
|
@@ -8,3 +15,66 @@ export const IPNS_STRING_PREFIX = '/ipns/'
|
|
|
8
15
|
export function isCodec <T extends number> (digest: MultihashDigest, codec: T): digest is MultihashDigest<T> {
|
|
9
16
|
return digest.code === codec
|
|
10
17
|
}
|
|
18
|
+
|
|
19
|
+
export const DHT_RECORD_PREFIX = '/dht/record/'
|
|
20
|
+
export const IPNS_METADATA_PREFIX = '/ipns/metadata/'
|
|
21
|
+
|
|
22
|
+
export function dhtRoutingKey (key: Uint8Array): Key {
|
|
23
|
+
return new Key(DHT_RECORD_PREFIX + uint8ArrayToString(key, 'base32'), false)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculate the datastore key for IPNS record metadata
|
|
28
|
+
*
|
|
29
|
+
* @param key - The DHT routing key for the IPNS record as defined in
|
|
30
|
+
* https://specs.ipfs.tech/ipns/ipns-record/#routing-record
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* const key = multihashToIPNSRoutingKey(privKey.publicKey.toMultihash())
|
|
36
|
+
* const metadataKey = ipnsMetadataKey(key)
|
|
37
|
+
* ```
|
|
38
|
+
* @returns The local storage key for IPNS record metadata
|
|
39
|
+
*/
|
|
40
|
+
export function ipnsMetadataKey (key: Uint8Array): Key {
|
|
41
|
+
return new Key(IPNS_METADATA_PREFIX + uint8ArrayToString(key, 'base32'), false)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function shouldRepublish (ipnsRecord: IPNSRecord, created: Date): boolean {
|
|
45
|
+
const now = Date.now()
|
|
46
|
+
const dhtExpiry = created.getTime() + DHT_EXPIRY_MS
|
|
47
|
+
const recordExpiry = new Date(ipnsRecord.validity).getTime()
|
|
48
|
+
|
|
49
|
+
// If the DHT expiry is within the threshold, republish it
|
|
50
|
+
if (dhtExpiry - now < REPUBLISH_THRESHOLD) {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If the record expiry (based on validity/lifetime) is within the threshold, republish it
|
|
55
|
+
if (recordExpiry - now < REPUBLISH_THRESHOLD) {
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isCID (obj?: any): obj is CID {
|
|
63
|
+
return obj?.asCID === obj
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isLibp2pCID (obj?: any): obj is CID<unknown, 0x72, 0x00 | 0x12, 1> {
|
|
67
|
+
if (!isCID(obj)) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (obj.code !== LIBP2P_KEY_CODEC) {
|
|
72
|
+
throw new InvalidParametersError(`CID codec ${obj.code} was not libp2p-key`)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (obj.multihash.code !== IDENTITY_CODEC && obj.multihash.code !== SHA2_256_CODEC) {
|
|
76
|
+
throw new InvalidParametersError(`Multihash algorithm codec ${obj.multihash.code} was not Identity or SHA256 hash`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true
|
|
80
|
+
}
|
package/dist/src/dnslink.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ResolveDNSLinkOptions } from './index.js';
|
|
2
|
-
import type { Logger } from '@libp2p/interface';
|
|
3
|
-
import type { Answer, DNS } from '@multiformats/dns';
|
|
4
|
-
export interface DNSLinkResult {
|
|
5
|
-
answer: Answer;
|
|
6
|
-
value: string;
|
|
7
|
-
}
|
|
8
|
-
export declare function resolveDNSLink(domain: string, dns: DNS, log: Logger, options?: ResolveDNSLinkOptions): Promise<DNSLinkResult>;
|
|
9
|
-
//# sourceMappingURL=dnslink.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dnslink.d.ts","sourceRoot":"","sources":["../../src/dnslink.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAU,MAAM,mBAAmB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAA;AAIpD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAkJD,wBAAsB,cAAc,CAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,aAAa,CAAC,CAExI"}
|
package/dist/src/dnslink.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import {} from '@libp2p/interface';
|
|
2
|
-
import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id';
|
|
3
|
-
import { RecordType } from '@multiformats/dns';
|
|
4
|
-
import { CID } from 'multiformats/cid';
|
|
5
|
-
import { DNSLinkNotFoundError } from './errors.js';
|
|
6
|
-
const MAX_RECURSIVE_DEPTH = 32;
|
|
7
|
-
async function recursiveResolveDnslink(domain, depth, dns, log, options = {}) {
|
|
8
|
-
if (depth === 0) {
|
|
9
|
-
throw new Error('recursion limit exceeded');
|
|
10
|
-
}
|
|
11
|
-
log('query %s for TXT and CNAME records', domain);
|
|
12
|
-
const txtRecordsResponse = await dns.query(domain, {
|
|
13
|
-
...options,
|
|
14
|
-
types: [
|
|
15
|
-
RecordType.TXT
|
|
16
|
-
]
|
|
17
|
-
});
|
|
18
|
-
// sort the TXT records to ensure deterministic processing
|
|
19
|
-
const txtRecords = (txtRecordsResponse?.Answer ?? [])
|
|
20
|
-
.sort((a, b) => a.data.localeCompare(b.data));
|
|
21
|
-
log('found %d TXT records for %s', txtRecords.length, domain);
|
|
22
|
-
for (const answer of txtRecords) {
|
|
23
|
-
try {
|
|
24
|
-
let result = answer.data;
|
|
25
|
-
// strip leading and trailing " characters
|
|
26
|
-
if (result.startsWith('"') && result.endsWith('"')) {
|
|
27
|
-
result = result.substring(1, result.length - 1);
|
|
28
|
-
}
|
|
29
|
-
if (!result.startsWith('dnslink=')) {
|
|
30
|
-
// invalid record?
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
log('%s TXT %s', answer.name, result);
|
|
34
|
-
result = result.replace('dnslink=', '');
|
|
35
|
-
// result is now a `/ipfs/<cid>` or `/ipns/<cid>` string
|
|
36
|
-
const [, protocol, domainOrCID, ...rest] = result.split('/'); // e.g. ["", "ipfs", "<cid>"]
|
|
37
|
-
if (protocol === 'ipfs') {
|
|
38
|
-
try {
|
|
39
|
-
const cid = CID.parse(domainOrCID);
|
|
40
|
-
// if the result is a CID, we've reached the end of the recursion
|
|
41
|
-
return {
|
|
42
|
-
value: `/ipfs/${cid}${rest.length > 0 ? `/${rest.join('/')}` : ''}`,
|
|
43
|
-
answer
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
catch { }
|
|
47
|
-
}
|
|
48
|
-
else if (protocol === 'ipns') {
|
|
49
|
-
try {
|
|
50
|
-
let peerId;
|
|
51
|
-
// eslint-disable-next-line max-depth
|
|
52
|
-
if (domainOrCID.charAt(0) === '1' || domainOrCID.charAt(0) === 'Q') {
|
|
53
|
-
peerId = peerIdFromString(domainOrCID);
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// try parsing as a CID
|
|
57
|
-
peerId = peerIdFromCID(CID.parse(domainOrCID));
|
|
58
|
-
}
|
|
59
|
-
// if the result is a PeerId, we've reached the end of the recursion
|
|
60
|
-
return {
|
|
61
|
-
value: `/ipns/${peerId}${rest.length > 0 ? `/${rest.join('/')}` : ''}`,
|
|
62
|
-
answer
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
catch { }
|
|
66
|
-
// if the result was another IPNS domain, try to follow it
|
|
67
|
-
return await recursiveResolveDomain(domainOrCID, depth - 1, dns, log, options);
|
|
68
|
-
}
|
|
69
|
-
else if (protocol === 'dnslink') {
|
|
70
|
-
// if the result was another DNSLink domain, try to follow it
|
|
71
|
-
return await recursiveResolveDomain(domainOrCID, depth - 1, dns, log, options);
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
log('unknown protocol "%s" in DNSLink record for domain: %s', protocol, domain);
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch (err) {
|
|
79
|
-
log.error('could not parse DNS link record for domain %s, %s', domain, answer.data, err);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
// no dnslink records found, try CNAMEs
|
|
83
|
-
log('no DNSLink records found for %s, falling back to CNAME', domain);
|
|
84
|
-
const cnameRecordsResponse = await dns.query(domain, {
|
|
85
|
-
...options,
|
|
86
|
-
types: [
|
|
87
|
-
RecordType.CNAME
|
|
88
|
-
]
|
|
89
|
-
});
|
|
90
|
-
// sort the CNAME records to ensure deterministic processing
|
|
91
|
-
const cnameRecords = (cnameRecordsResponse?.Answer ?? [])
|
|
92
|
-
.sort((a, b) => a.data.localeCompare(b.data));
|
|
93
|
-
log('found %d CNAME records for %s', cnameRecords.length, domain);
|
|
94
|
-
for (const cname of cnameRecords) {
|
|
95
|
-
try {
|
|
96
|
-
return await recursiveResolveDomain(cname.data, depth - 1, dns, log, options);
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
log.error('domain %s cname %s had no DNSLink records', domain, cname.data, err);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
throw new DNSLinkNotFoundError(`No DNSLink records found for domain: ${domain}`);
|
|
103
|
-
}
|
|
104
|
-
async function recursiveResolveDomain(domain, depth, dns, log, options = {}) {
|
|
105
|
-
if (depth === 0) {
|
|
106
|
-
throw new Error('recursion limit exceeded');
|
|
107
|
-
}
|
|
108
|
-
// the DNSLink spec says records MUST be stored on the `_dnslink.` subdomain
|
|
109
|
-
// so start looking for records there, we will fall back to the bare domain
|
|
110
|
-
// if none are found
|
|
111
|
-
if (!domain.startsWith('_dnslink.')) {
|
|
112
|
-
domain = `_dnslink.${domain}`;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
return await recursiveResolveDnslink(domain, depth, dns, log, options);
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
// If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error
|
|
119
|
-
if (err.code !== 'ENOTFOUND' && err.code !== 'ENODATA' && err.name !== 'DNSLinkNotFoundError' && err.name !== 'NotFoundError') {
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
if (domain.startsWith('_dnslink.')) {
|
|
123
|
-
// The supplied domain contains a _dnslink component
|
|
124
|
-
// Check the non-_dnslink domain
|
|
125
|
-
domain = domain.replace('_dnslink.', '');
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
// Check the _dnslink subdomain
|
|
129
|
-
domain = `_dnslink.${domain}`;
|
|
130
|
-
}
|
|
131
|
-
// If this throws then we propagate the error
|
|
132
|
-
return recursiveResolveDnslink(domain, depth, dns, log, options);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
export async function resolveDNSLink(domain, dns, log, options = {}) {
|
|
136
|
-
return recursiveResolveDomain(domain, options.maxRecursiveDepth ?? MAX_RECURSIVE_DEPTH, dns, log, options);
|
|
137
|
-
}
|
|
138
|
-
//# sourceMappingURL=dnslink.js.map
|
package/dist/src/dnslink.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dnslink.js","sourceRoot":"","sources":["../../src/dnslink.ts"],"names":[],"mappings":"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"}
|
package/dist/typedoc-urls.json
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"DNSLinkResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.DNSLinkResolveResult.html",
|
|
3
|
-
".:DNSLinkResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.DNSLinkResolveResult.html",
|
|
4
|
-
"IPNS": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNS.html",
|
|
5
|
-
".:IPNS": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNS.html",
|
|
6
|
-
"IPNSComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSComponents.html",
|
|
7
|
-
".:IPNSComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSComponents.html",
|
|
8
|
-
"IPNSOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSOptions.html",
|
|
9
|
-
".:IPNSOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSOptions.html",
|
|
10
|
-
"IPNSResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSResolveResult.html",
|
|
11
|
-
".:IPNSResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.IPNSResolveResult.html",
|
|
12
|
-
"PublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.PublishOptions.html",
|
|
13
|
-
".:PublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.PublishOptions.html",
|
|
14
|
-
"RepublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.RepublishOptions.html",
|
|
15
|
-
".:RepublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.RepublishOptions.html",
|
|
16
|
-
"RepublishRecordOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.RepublishRecordOptions.html",
|
|
17
|
-
".:RepublishRecordOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.RepublishRecordOptions.html",
|
|
18
|
-
"ResolveDNSLinkOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveDNSLinkOptions.html",
|
|
19
|
-
".:ResolveDNSLinkOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveDNSLinkOptions.html",
|
|
20
|
-
"ResolveOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveOptions.html",
|
|
21
|
-
".:ResolveOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveOptions.html",
|
|
22
|
-
"ResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveResult.html",
|
|
23
|
-
".:ResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.index.ResolveResult.html",
|
|
24
|
-
"PublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.PublishProgressEvents.html",
|
|
25
|
-
".:PublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.PublishProgressEvents.html",
|
|
26
|
-
"RepublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.RepublishProgressEvents.html",
|
|
27
|
-
".:RepublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.RepublishProgressEvents.html",
|
|
28
|
-
"ResolveDNSLinkProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.ResolveDNSLinkProgressEvents.html",
|
|
29
|
-
".:ResolveDNSLinkProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.ResolveDNSLinkProgressEvents.html",
|
|
30
|
-
"ResolveProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.ResolveProgressEvents.html",
|
|
31
|
-
".:ResolveProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.index.ResolveProgressEvents.html",
|
|
32
|
-
"ipns": "https://ipfs.github.io/helia/functions/_helia_ipns.index.ipns.html",
|
|
33
|
-
".:ipns": "https://ipfs.github.io/helia/functions/_helia_ipns.index.ipns.html",
|
|
34
|
-
"GetOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.GetOptions.html",
|
|
35
|
-
"./routing:GetOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.GetOptions.html",
|
|
36
|
-
"IPNSRouting": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.IPNSRouting.html",
|
|
37
|
-
"./routing:IPNSRouting": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.IPNSRouting.html",
|
|
38
|
-
"PubsubRoutingComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.PubsubRoutingComponents.html",
|
|
39
|
-
"PutOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.PutOptions.html",
|
|
40
|
-
"./routing:PutOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.routing.PutOptions.html",
|
|
41
|
-
"DatastoreProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.routing.DatastoreProgressEvents.html",
|
|
42
|
-
"HeliaRoutingProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.routing.HeliaRoutingProgressEvents.html",
|
|
43
|
-
"IPNSRoutingEvents": "https://ipfs.github.io/helia/types/_helia_ipns.routing.IPNSRoutingEvents.html",
|
|
44
|
-
"./routing:IPNSRoutingEvents": "https://ipfs.github.io/helia/types/_helia_ipns.routing.IPNSRoutingEvents.html",
|
|
45
|
-
"PubSubProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.routing.PubSubProgressEvents.html",
|
|
46
|
-
"helia": "https://ipfs.github.io/helia/functions/_helia_ipns.routing.helia.html",
|
|
47
|
-
"pubsub": "https://ipfs.github.io/helia/functions/_helia_ipns.routing.pubsub.html"
|
|
48
|
-
}
|
package/src/dnslink.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { } from '@libp2p/interface'
|
|
2
|
-
import { peerIdFromCID, peerIdFromString } from '@libp2p/peer-id'
|
|
3
|
-
import { RecordType } from '@multiformats/dns'
|
|
4
|
-
import { CID } from 'multiformats/cid'
|
|
5
|
-
import { DNSLinkNotFoundError } from './errors.js'
|
|
6
|
-
import type { ResolveDNSLinkOptions } from './index.js'
|
|
7
|
-
import type { Logger, PeerId } from '@libp2p/interface'
|
|
8
|
-
import type { Answer, DNS } from '@multiformats/dns'
|
|
9
|
-
|
|
10
|
-
const MAX_RECURSIVE_DEPTH = 32
|
|
11
|
-
|
|
12
|
-
export interface DNSLinkResult {
|
|
13
|
-
answer: Answer
|
|
14
|
-
value: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS, log: Logger, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResult> {
|
|
18
|
-
if (depth === 0) {
|
|
19
|
-
throw new Error('recursion limit exceeded')
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
log('query %s for TXT and CNAME records', domain)
|
|
23
|
-
const txtRecordsResponse = await dns.query(domain, {
|
|
24
|
-
...options,
|
|
25
|
-
types: [
|
|
26
|
-
RecordType.TXT
|
|
27
|
-
]
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// sort the TXT records to ensure deterministic processing
|
|
31
|
-
const txtRecords = (txtRecordsResponse?.Answer ?? [])
|
|
32
|
-
.sort((a, b) => a.data.localeCompare(b.data))
|
|
33
|
-
|
|
34
|
-
log('found %d TXT records for %s', txtRecords.length, domain)
|
|
35
|
-
|
|
36
|
-
for (const answer of txtRecords) {
|
|
37
|
-
try {
|
|
38
|
-
let result = answer.data
|
|
39
|
-
|
|
40
|
-
// strip leading and trailing " characters
|
|
41
|
-
if (result.startsWith('"') && result.endsWith('"')) {
|
|
42
|
-
result = result.substring(1, result.length - 1)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!result.startsWith('dnslink=')) {
|
|
46
|
-
// invalid record?
|
|
47
|
-
continue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
log('%s TXT %s', answer.name, result)
|
|
51
|
-
|
|
52
|
-
result = result.replace('dnslink=', '')
|
|
53
|
-
|
|
54
|
-
// result is now a `/ipfs/<cid>` or `/ipns/<cid>` string
|
|
55
|
-
const [, protocol, domainOrCID, ...rest] = result.split('/') // e.g. ["", "ipfs", "<cid>"]
|
|
56
|
-
|
|
57
|
-
if (protocol === 'ipfs') {
|
|
58
|
-
try {
|
|
59
|
-
const cid = CID.parse(domainOrCID)
|
|
60
|
-
|
|
61
|
-
// if the result is a CID, we've reached the end of the recursion
|
|
62
|
-
return {
|
|
63
|
-
value: `/ipfs/${cid}${rest.length > 0 ? `/${rest.join('/')}` : ''}`,
|
|
64
|
-
answer
|
|
65
|
-
}
|
|
66
|
-
} catch {}
|
|
67
|
-
} else if (protocol === 'ipns') {
|
|
68
|
-
try {
|
|
69
|
-
let peerId: PeerId
|
|
70
|
-
|
|
71
|
-
// eslint-disable-next-line max-depth
|
|
72
|
-
if (domainOrCID.charAt(0) === '1' || domainOrCID.charAt(0) === 'Q') {
|
|
73
|
-
peerId = peerIdFromString(domainOrCID)
|
|
74
|
-
} else {
|
|
75
|
-
// try parsing as a CID
|
|
76
|
-
peerId = peerIdFromCID(CID.parse(domainOrCID))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// if the result is a PeerId, we've reached the end of the recursion
|
|
80
|
-
return {
|
|
81
|
-
value: `/ipns/${peerId}${rest.length > 0 ? `/${rest.join('/')}` : ''}`,
|
|
82
|
-
answer
|
|
83
|
-
}
|
|
84
|
-
} catch {}
|
|
85
|
-
|
|
86
|
-
// if the result was another IPNS domain, try to follow it
|
|
87
|
-
return await recursiveResolveDomain(domainOrCID, depth - 1, dns, log, options)
|
|
88
|
-
} else if (protocol === 'dnslink') {
|
|
89
|
-
// if the result was another DNSLink domain, try to follow it
|
|
90
|
-
return await recursiveResolveDomain(domainOrCID, depth - 1, dns, log, options)
|
|
91
|
-
} else {
|
|
92
|
-
log('unknown protocol "%s" in DNSLink record for domain: %s', protocol, domain)
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
} catch (err: any) {
|
|
96
|
-
log.error('could not parse DNS link record for domain %s, %s', domain, answer.data, err)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// no dnslink records found, try CNAMEs
|
|
101
|
-
log('no DNSLink records found for %s, falling back to CNAME', domain)
|
|
102
|
-
|
|
103
|
-
const cnameRecordsResponse = await dns.query(domain, {
|
|
104
|
-
...options,
|
|
105
|
-
types: [
|
|
106
|
-
RecordType.CNAME
|
|
107
|
-
]
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
// sort the CNAME records to ensure deterministic processing
|
|
111
|
-
const cnameRecords = (cnameRecordsResponse?.Answer ?? [])
|
|
112
|
-
.sort((a, b) => a.data.localeCompare(b.data))
|
|
113
|
-
|
|
114
|
-
log('found %d CNAME records for %s', cnameRecords.length, domain)
|
|
115
|
-
|
|
116
|
-
for (const cname of cnameRecords) {
|
|
117
|
-
try {
|
|
118
|
-
return await recursiveResolveDomain(cname.data, depth - 1, dns, log, options)
|
|
119
|
-
} catch (err: any) {
|
|
120
|
-
log.error('domain %s cname %s had no DNSLink records', domain, cname.data, err)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
throw new DNSLinkNotFoundError(`No DNSLink records found for domain: ${domain}`)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function recursiveResolveDomain (domain: string, depth: number, dns: DNS, log: Logger, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResult> {
|
|
128
|
-
if (depth === 0) {
|
|
129
|
-
throw new Error('recursion limit exceeded')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// the DNSLink spec says records MUST be stored on the `_dnslink.` subdomain
|
|
133
|
-
// so start looking for records there, we will fall back to the bare domain
|
|
134
|
-
// if none are found
|
|
135
|
-
if (!domain.startsWith('_dnslink.')) {
|
|
136
|
-
domain = `_dnslink.${domain}`
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
return await recursiveResolveDnslink(domain, depth, dns, log, options)
|
|
141
|
-
} catch (err: any) {
|
|
142
|
-
// If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error
|
|
143
|
-
if (err.code !== 'ENOTFOUND' && err.code !== 'ENODATA' && err.name !== 'DNSLinkNotFoundError' && err.name !== 'NotFoundError') {
|
|
144
|
-
throw err
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (domain.startsWith('_dnslink.')) {
|
|
148
|
-
// The supplied domain contains a _dnslink component
|
|
149
|
-
// Check the non-_dnslink domain
|
|
150
|
-
domain = domain.replace('_dnslink.', '')
|
|
151
|
-
} else {
|
|
152
|
-
// Check the _dnslink subdomain
|
|
153
|
-
domain = `_dnslink.${domain}`
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// If this throws then we propagate the error
|
|
157
|
-
return recursiveResolveDnslink(domain, depth, dns, log, options)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function resolveDNSLink (domain: string, dns: DNS, log: Logger, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResult> {
|
|
162
|
-
return recursiveResolveDomain(domain, options.maxRecursiveDepth ?? MAX_RECURSIVE_DEPTH, dns, log, options)
|
|
163
|
-
}
|