@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.
Files changed (60) hide show
  1. package/README.md +31 -135
  2. package/dist/index.min.js +10 -11
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/constants.d.ts +17 -0
  5. package/dist/src/constants.d.ts.map +1 -0
  6. package/dist/src/constants.js +19 -0
  7. package/dist/src/constants.js.map +1 -0
  8. package/dist/src/errors.d.ts +0 -4
  9. package/dist/src/errors.d.ts.map +1 -1
  10. package/dist/src/errors.js +0 -7
  11. package/dist/src/errors.js.map +1 -1
  12. package/dist/src/index.d.ts +109 -201
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/index.js +34 -417
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/ipns.d.ts +22 -0
  17. package/dist/src/ipns.d.ts.map +1 -0
  18. package/dist/src/ipns.js +339 -0
  19. package/dist/src/ipns.js.map +1 -0
  20. package/dist/src/local-store.d.ts +42 -0
  21. package/dist/src/local-store.d.ts.map +1 -0
  22. package/dist/src/local-store.js +119 -0
  23. package/dist/src/local-store.js.map +1 -0
  24. package/dist/src/pb/metadata.d.ts +12 -0
  25. package/dist/src/pb/metadata.d.ts.map +1 -0
  26. package/dist/src/pb/metadata.js +57 -0
  27. package/dist/src/pb/metadata.js.map +1 -0
  28. package/dist/src/routing/index.d.ts +4 -2
  29. package/dist/src/routing/index.d.ts.map +1 -1
  30. package/dist/src/routing/index.js.map +1 -1
  31. package/dist/src/routing/local-store.d.ts +4 -19
  32. package/dist/src/routing/local-store.d.ts.map +1 -1
  33. package/dist/src/routing/local-store.js +7 -62
  34. package/dist/src/routing/local-store.js.map +1 -1
  35. package/dist/src/routing/pubsub.d.ts +21 -1
  36. package/dist/src/routing/pubsub.d.ts.map +1 -1
  37. package/dist/src/routing/pubsub.js +2 -2
  38. package/dist/src/routing/pubsub.js.map +1 -1
  39. package/dist/src/utils.d.ts +24 -0
  40. package/dist/src/utils.d.ts.map +1 -1
  41. package/dist/src/utils.js +56 -0
  42. package/dist/src/utils.js.map +1 -1
  43. package/package.json +21 -23
  44. package/src/constants.ts +24 -0
  45. package/src/errors.ts +0 -9
  46. package/src/index.ts +116 -545
  47. package/src/ipns.ts +400 -0
  48. package/src/local-store.ts +162 -0
  49. package/src/pb/metadata.proto +9 -0
  50. package/src/pb/metadata.ts +74 -0
  51. package/src/routing/index.ts +4 -3
  52. package/src/routing/local-store.ts +9 -87
  53. package/src/routing/pubsub.ts +28 -4
  54. package/src/utils.ts +70 -0
  55. package/dist/src/dnslink.d.ts +0 -9
  56. package/dist/src/dnslink.d.ts.map +0 -1
  57. package/dist/src/dnslink.js +0 -138
  58. package/dist/src/dnslink.js.map +0 -1
  59. package/dist/typedoc-urls.json +0 -48
  60. package/src/dnslink.ts +0 -163
@@ -1,96 +1,18 @@
1
- import { Record } from '@libp2p/kad-dht'
2
- import { Key } from 'interface-datastore'
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 IPNS records to the
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 localStore (datastore: Datastore): LocalStore {
7
+ export function localStoreRouting (localStore: LocalStore): IPNSRouting {
38
8
  return {
39
- async put (routingKey: Uint8Array, marshalledRecord: Uint8Array, options: PutOptions = {}) {
40
- try {
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: GetOptions = {}): Promise<GetResult> {
69
- try {
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
- // Unmarshal libp2p record as the DHT does
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
  }
@@ -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 './local-store.js'
11
+ import { localStore } from '../local-store.js'
12
12
  import type { GetOptions, IPNSRouting, PutOptions } from './index.js'
13
- import type { LocalStore } from './local-store.js'
14
- import type { PeerId, Message, PublishResult, PubSub, PublicKey } from '@libp2p/interface'
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
+ }
@@ -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"}
@@ -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
@@ -1 +0,0 @@
1
- {"version":3,"file":"dnslink.js","sourceRoot":"","sources":["../../src/dnslink.ts"],"names":[],"mappings":"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"}
@@ -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
- }