@helia/ipns 8.2.4 → 9.0.0-4d51f16d

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 (75) hide show
  1. package/README.md +31 -135
  2. package/dist/index.min.js +11 -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 +131 -207
  13. package/dist/src/index.d.ts.map +1 -1
  14. package/dist/src/index.js +49 -416
  15. package/dist/src/index.js.map +1 -1
  16. package/dist/src/ipns/publisher.d.ts +29 -0
  17. package/dist/src/ipns/publisher.d.ts.map +1 -0
  18. package/dist/src/ipns/publisher.js +73 -0
  19. package/dist/src/ipns/publisher.js.map +1 -0
  20. package/dist/src/ipns/republisher.d.ts +30 -0
  21. package/dist/src/ipns/republisher.d.ts.map +1 -0
  22. package/dist/src/ipns/republisher.js +112 -0
  23. package/dist/src/ipns/republisher.js.map +1 -0
  24. package/dist/src/ipns/resolver.d.ts +26 -0
  25. package/dist/src/ipns/resolver.d.ts.map +1 -0
  26. package/dist/src/ipns/resolver.js +165 -0
  27. package/dist/src/ipns/resolver.js.map +1 -0
  28. package/dist/src/ipns.d.ts +20 -0
  29. package/dist/src/ipns.d.ts.map +1 -0
  30. package/dist/src/ipns.js +70 -0
  31. package/dist/src/ipns.js.map +1 -0
  32. package/dist/src/local-store.d.ts +42 -0
  33. package/dist/src/local-store.d.ts.map +1 -0
  34. package/dist/src/local-store.js +119 -0
  35. package/dist/src/local-store.js.map +1 -0
  36. package/dist/src/pb/metadata.d.ts +12 -0
  37. package/dist/src/pb/metadata.d.ts.map +1 -0
  38. package/dist/src/pb/metadata.js +57 -0
  39. package/dist/src/pb/metadata.js.map +1 -0
  40. package/dist/src/routing/index.d.ts +5 -3
  41. package/dist/src/routing/index.d.ts.map +1 -1
  42. package/dist/src/routing/index.js.map +1 -1
  43. package/dist/src/routing/local-store.d.ts +4 -19
  44. package/dist/src/routing/local-store.d.ts.map +1 -1
  45. package/dist/src/routing/local-store.js +7 -62
  46. package/dist/src/routing/local-store.js.map +1 -1
  47. package/dist/src/routing/pubsub.d.ts +21 -1
  48. package/dist/src/routing/pubsub.d.ts.map +1 -1
  49. package/dist/src/routing/pubsub.js +2 -2
  50. package/dist/src/routing/pubsub.js.map +1 -1
  51. package/dist/src/utils.d.ts +24 -0
  52. package/dist/src/utils.d.ts.map +1 -1
  53. package/dist/src/utils.js +56 -0
  54. package/dist/src/utils.js.map +1 -1
  55. package/package.json +21 -23
  56. package/src/constants.ts +24 -0
  57. package/src/errors.ts +0 -9
  58. package/src/index.ts +154 -548
  59. package/src/ipns/publisher.ts +97 -0
  60. package/src/ipns/republisher.ts +144 -0
  61. package/src/ipns/resolver.ts +217 -0
  62. package/src/ipns.ts +87 -0
  63. package/src/local-store.ts +162 -0
  64. package/src/pb/metadata.proto +9 -0
  65. package/src/pb/metadata.ts +74 -0
  66. package/src/routing/index.ts +5 -4
  67. package/src/routing/local-store.ts +9 -87
  68. package/src/routing/pubsub.ts +28 -4
  69. package/src/utils.ts +70 -0
  70. package/dist/src/dnslink.d.ts +0 -9
  71. package/dist/src/dnslink.d.ts.map +0 -1
  72. package/dist/src/dnslink.js +0 -138
  73. package/dist/src/dnslink.js.map +0 -1
  74. package/dist/typedoc-urls.json +0 -48
  75. package/src/dnslink.ts +0 -163
@@ -0,0 +1,162 @@
1
+ import { Record } from '@libp2p/kad-dht'
2
+ import { CustomProgressEvent } from 'progress-events'
3
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
5
+ import { IPNSPublishMetadata } from './pb/metadata.js'
6
+ import { dhtRoutingKey, DHT_RECORD_PREFIX, ipnsMetadataKey } from './utils.js'
7
+ import type { DatastoreProgressEvents, GetOptions, PutOptions } from './routing/index.js'
8
+ import type { AbortOptions, Logger } from '@libp2p/interface'
9
+ import type { Datastore } from 'interface-datastore'
10
+
11
+ export interface GetResult {
12
+ record: Uint8Array
13
+ created: Date
14
+ }
15
+
16
+ export interface ListResult {
17
+ routingKey: Uint8Array
18
+ record: Uint8Array
19
+ created: Date
20
+ metadata?: IPNSPublishMetadata
21
+ }
22
+
23
+ export interface ListOptions extends AbortOptions {
24
+ onProgress?(evt: DatastoreProgressEvents): void
25
+ }
26
+
27
+ export interface LocalStore {
28
+ /**
29
+ * Put an IPNS record into the datastore
30
+ *
31
+ * @param routingKey - The routing key for the IPNS record
32
+ * @param marshaledRecord - The marshaled IPNS record
33
+ * @param options - options for the put operation including metadata
34
+ */
35
+ put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void>
36
+ get(routingKey: Uint8Array, options?: GetOptions): Promise<GetResult>
37
+ has(routingKey: Uint8Array, options?: AbortOptions): Promise<boolean>
38
+ delete(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
39
+ /**
40
+ * List all IPNS records in the datastore
41
+ */
42
+ list(options?: ListOptions): AsyncIterable<ListResult>
43
+ }
44
+
45
+ /**
46
+ * Read/write IPNS records to the datastore as DHT records.
47
+ *
48
+ * This lets us publish IPNS records offline then serve them to the network
49
+ * later in response to DHT queries.
50
+ */
51
+ export function localStore (datastore: Datastore, log: Logger): LocalStore {
52
+ return {
53
+ async put (routingKey: Uint8Array, marshalledRecord: Uint8Array, options: PutOptions = {}) {
54
+ try {
55
+ const key = dhtRoutingKey(routingKey)
56
+
57
+ // don't overwrite existing, identical records as this will affect the
58
+ // TTL
59
+ try {
60
+ const existingBuf = await datastore.get(key)
61
+ const existingRecord = Record.deserialize(existingBuf)
62
+
63
+ if (uint8ArrayEquals(existingRecord.value, marshalledRecord)) {
64
+ return
65
+ }
66
+ } catch (err: any) {
67
+ if (err.name !== 'NotFoundError') {
68
+ throw err
69
+ }
70
+ }
71
+
72
+ // Marshal to libp2p record as the DHT does
73
+ const record = new Record(routingKey, marshalledRecord, new Date())
74
+
75
+ options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:put'))
76
+ const batch = datastore.batch()
77
+ batch.put(key, record.serialize())
78
+
79
+ if (options.metadata != null) {
80
+ // derive the datastore key for the IPNS metadata from the same routing key
81
+ batch.put(ipnsMetadataKey(routingKey), IPNSPublishMetadata.encode(options.metadata))
82
+ }
83
+ await batch.commit(options)
84
+ } catch (err: any) {
85
+ options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
86
+ throw err
87
+ }
88
+ },
89
+ async get (routingKey: Uint8Array, options: GetOptions = {}): Promise<GetResult> {
90
+ try {
91
+ const key = dhtRoutingKey(routingKey)
92
+
93
+ options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:get'))
94
+ const buf = await datastore.get(key, options)
95
+
96
+ // Unmarshal libp2p record as the DHT does
97
+ const record = Record.deserialize(buf)
98
+
99
+ return {
100
+ record: record.value,
101
+ created: record.timeReceived
102
+ }
103
+ } catch (err: any) {
104
+ options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
105
+ throw err
106
+ }
107
+ },
108
+ async has (routingKey: Uint8Array, options: AbortOptions = {}): Promise<boolean> {
109
+ const key = dhtRoutingKey(routingKey)
110
+ return datastore.has(key, options)
111
+ },
112
+ async delete (routingKey, options): Promise<void> {
113
+ const key = dhtRoutingKey(routingKey)
114
+ const batch = datastore.batch()
115
+ batch.delete(key)
116
+ batch.delete(ipnsMetadataKey(routingKey))
117
+ await batch.commit(options)
118
+ },
119
+ async * list (options: ListOptions = {}): AsyncIterable<ListResult> {
120
+ try {
121
+ options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:list'))
122
+
123
+ // Query all records with the DHT_RECORD_PREFIX
124
+ for await (const { key, value } of datastore.query({
125
+ prefix: DHT_RECORD_PREFIX
126
+ }, options)) {
127
+ try {
128
+ // Deserialize the record
129
+ const libp2pRecord = Record.deserialize(value)
130
+
131
+ // Extract the routing key from the datastore key
132
+ const keyString = key.toString()
133
+ const routingKeyBase32 = keyString.substring(DHT_RECORD_PREFIX.length)
134
+ const routingKey = uint8ArrayFromString(routingKeyBase32, 'base32')
135
+
136
+ const metadataKey = ipnsMetadataKey(routingKey)
137
+ let metadata: IPNSPublishMetadata | undefined
138
+ try {
139
+ const metadataBuf = await datastore.get(metadataKey, options)
140
+ metadata = IPNSPublishMetadata.decode(metadataBuf)
141
+ } catch (err: any) {
142
+ log.error('Error deserializing metadata for %s - %e', routingKeyBase32, err)
143
+ }
144
+
145
+ yield {
146
+ routingKey,
147
+ metadata,
148
+ record: libp2pRecord.value,
149
+ created: libp2pRecord.timeReceived
150
+ }
151
+ } catch (err) {
152
+ // Skip invalid records
153
+ log.error('Error deserializing record - %e', err)
154
+ }
155
+ }
156
+ } catch (err: any) {
157
+ options.onProgress?.(new CustomProgressEvent<Error>('ipns:routing:datastore:error', err))
158
+ throw err
159
+ }
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,9 @@
1
+ syntax = "proto3";
2
+
3
+ message IPNSPublishMetadata {
4
+ // The name of the key that was used to publish the record
5
+ string keyName = 1;
6
+
7
+ // Lifetime is the duration of the signature validity in milliseconds
8
+ uint32 lifetime = 2;
9
+ }
@@ -0,0 +1,74 @@
1
+ import { decodeMessage, encodeMessage, message } from 'protons-runtime'
2
+ import type { Codec, DecodeOptions } from 'protons-runtime'
3
+ import type { Uint8ArrayList } from 'uint8arraylist'
4
+
5
+ export interface IPNSPublishMetadata {
6
+ keyName: string
7
+ lifetime: number
8
+ }
9
+
10
+ export namespace IPNSPublishMetadata {
11
+ let _codec: Codec<IPNSPublishMetadata>
12
+
13
+ export const codec = (): Codec<IPNSPublishMetadata> => {
14
+ if (_codec == null) {
15
+ _codec = message<IPNSPublishMetadata>((obj, w, opts = {}) => {
16
+ if (opts.lengthDelimited !== false) {
17
+ w.fork()
18
+ }
19
+
20
+ if ((obj.keyName != null && obj.keyName !== '')) {
21
+ w.uint32(10)
22
+ w.string(obj.keyName)
23
+ }
24
+
25
+ if ((obj.lifetime != null && obj.lifetime !== 0)) {
26
+ w.uint32(16)
27
+ w.uint32(obj.lifetime)
28
+ }
29
+
30
+ if (opts.lengthDelimited !== false) {
31
+ w.ldelim()
32
+ }
33
+ }, (reader, length, opts = {}) => {
34
+ const obj: any = {
35
+ keyName: '',
36
+ lifetime: 0
37
+ }
38
+
39
+ const end = length == null ? reader.len : reader.pos + length
40
+
41
+ while (reader.pos < end) {
42
+ const tag = reader.uint32()
43
+
44
+ switch (tag >>> 3) {
45
+ case 1: {
46
+ obj.keyName = reader.string()
47
+ break
48
+ }
49
+ case 2: {
50
+ obj.lifetime = reader.uint32()
51
+ break
52
+ }
53
+ default: {
54
+ reader.skipType(tag & 7)
55
+ break
56
+ }
57
+ }
58
+ }
59
+
60
+ return obj
61
+ })
62
+ }
63
+
64
+ return _codec
65
+ }
66
+
67
+ export const encode = (obj: Partial<IPNSPublishMetadata>): Uint8Array => {
68
+ return encodeMessage(obj, IPNSPublishMetadata.codec())
69
+ }
70
+
71
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<IPNSPublishMetadata>): IPNSPublishMetadata => {
72
+ return decodeMessage(buf, IPNSPublishMetadata.codec(), opts)
73
+ }
74
+ }
@@ -1,11 +1,12 @@
1
1
  import type { HeliaRoutingProgressEvents } from './helia.js'
2
- import type { DatastoreProgressEvents } from './local-store.js'
2
+ import type { DatastoreProgressEvents } from '../index.js'
3
3
  import type { PubSubProgressEvents } from './pubsub.js'
4
+ import type { IPNSPublishMetadata } from '../pb/metadata.ts'
4
5
  import type { AbortOptions } from '@libp2p/interface'
5
6
  import type { ProgressOptions } from 'progress-events'
6
7
 
7
8
  export interface PutOptions extends AbortOptions, ProgressOptions {
8
-
9
+ metadata?: IPNSPublishMetadata
9
10
  }
10
11
 
11
12
  export interface GetOptions extends AbortOptions, ProgressOptions {
@@ -26,11 +27,11 @@ export type { DatastoreProgressEvents }
26
27
  export type { HeliaRoutingProgressEvents }
27
28
  export type { PubSubProgressEvents }
28
29
 
29
- export type IPNSRoutingEvents =
30
+ export type IPNSRoutingProgressEvents =
30
31
  DatastoreProgressEvents |
31
32
  HeliaRoutingProgressEvents |
32
33
  PubSubProgressEvents
33
34
 
34
35
  export { helia } from './helia.js'
35
36
  export { pubsub } from './pubsub.js'
36
- export type { PubsubRoutingComponents } from './pubsub.js'
37
+ export type { PubsubRoutingComponents, PubSub, Message, PublishResult, PubSubEvents } from './pubsub.js'
@@ -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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"}