@helia/ipns 7.2.2 → 7.2.3-2f88fc8
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 +86 -58
- package/dist/index.min.js +4 -5
- package/dist/src/dnslink.d.ts +1 -1
- package/dist/src/dnslink.d.ts.map +1 -1
- package/dist/src/dnslink.js +7 -6
- package/dist/src/dnslink.js.map +1 -1
- package/dist/src/errors.d.ts +25 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +43 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/index.d.ts +90 -61
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +131 -72
- package/dist/src/index.js.map +1 -1
- package/dist/src/routing/index.d.ts +4 -0
- 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.js +1 -1
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +6 -4
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/utils.d.ts +5 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +6 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +12 -11
- package/src/dnslink.ts +8 -6
- package/src/errors.ts +53 -0
- package/src/index.ts +141 -78
- package/src/routing/index.ts +5 -0
- package/src/routing/local-store.ts +1 -1
- package/src/routing/pubsub.ts +9 -6
- package/src/utils.ts +8 -0
- package/dist/typedoc-urls.json +0 -42
package/src/dnslink.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { } from '@libp2p/interface'
|
|
2
2
|
import { peerIdFromString } from '@libp2p/peer-id'
|
|
3
3
|
import { RecordType } from '@multiformats/dns'
|
|
4
4
|
import { CID } from 'multiformats/cid'
|
|
5
|
+
import { DNSLinkNotFoundError } from './errors.js'
|
|
5
6
|
import type { ResolveDNSLinkOptions } from './index.js'
|
|
7
|
+
import type { Logger } from '@libp2p/interface'
|
|
6
8
|
import type { Answer, DNS } from '@multiformats/dns'
|
|
7
9
|
|
|
8
10
|
const MAX_RECURSIVE_DEPTH = 32
|
|
@@ -26,7 +28,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
|
|
|
26
28
|
})
|
|
27
29
|
|
|
28
30
|
// sort the TXT records to ensure deterministic processing
|
|
29
|
-
const txtRecords = txtRecordsResponse
|
|
31
|
+
const txtRecords = (txtRecordsResponse?.Answer ?? [])
|
|
30
32
|
.sort((a, b) => a.data.localeCompare(b.data))
|
|
31
33
|
|
|
32
34
|
log('found %d TXT records for %s', txtRecords.length, domain)
|
|
@@ -98,7 +100,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
|
|
|
98
100
|
})
|
|
99
101
|
|
|
100
102
|
// sort the CNAME records to ensure deterministic processing
|
|
101
|
-
const cnameRecords = cnameRecordsResponse
|
|
103
|
+
const cnameRecords = (cnameRecordsResponse?.Answer ?? [])
|
|
102
104
|
.sort((a, b) => a.data.localeCompare(b.data))
|
|
103
105
|
|
|
104
106
|
log('found %d CNAME records for %s', cnameRecords.length, domain)
|
|
@@ -111,7 +113,7 @@ async function recursiveResolveDnslink (domain: string, depth: number, dns: DNS,
|
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
throw new
|
|
116
|
+
throw new DNSLinkNotFoundError(`No DNSLink records found for domain: ${domain}`)
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
async function recursiveResolveDomain (domain: string, depth: number, dns: DNS, log: Logger, options: ResolveDNSLinkOptions = {}): Promise<DNSLinkResult> {
|
|
@@ -130,7 +132,7 @@ async function recursiveResolveDomain (domain: string, depth: number, dns: DNS,
|
|
|
130
132
|
return await recursiveResolveDnslink(domain, depth, dns, log, options)
|
|
131
133
|
} catch (err: any) {
|
|
132
134
|
// If the code is not ENOTFOUND or ERR_DNSLINK_NOT_FOUND or ENODATA then throw the error
|
|
133
|
-
if (err.code !== 'ENOTFOUND' && err.code !== '
|
|
135
|
+
if (err.code !== 'ENOTFOUND' && err.code !== 'ENODATA' && err.name !== 'DNSLinkNotFoundError' && err.name !== 'NotFoundError') {
|
|
134
136
|
throw err
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -144,7 +146,7 @@ async function recursiveResolveDomain (domain: string, depth: number, dns: DNS,
|
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
// If this throws then we propagate the error
|
|
147
|
-
return
|
|
149
|
+
return recursiveResolveDnslink(domain, depth, dns, log, options)
|
|
148
150
|
}
|
|
149
151
|
}
|
|
150
152
|
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export class DNSLinkNotFoundError extends Error {
|
|
2
|
+
static name = 'DNSLinkNotFoundError'
|
|
3
|
+
|
|
4
|
+
constructor (message = 'DNSLink not found') {
|
|
5
|
+
super(message)
|
|
6
|
+
this.name = 'DNSLinkNotFoundError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class RecordsFailedValidationError extends Error {
|
|
11
|
+
static name = 'RecordsFailedValidationError'
|
|
12
|
+
|
|
13
|
+
constructor (message = 'Records failed validation') {
|
|
14
|
+
super(message)
|
|
15
|
+
this.name = 'RecordsFailedValidationError'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class UnsupportedMultibasePrefixError extends Error {
|
|
20
|
+
static name = 'UnsupportedMultibasePrefixError'
|
|
21
|
+
|
|
22
|
+
constructor (message = 'Unsupported multibase prefix') {
|
|
23
|
+
super(message)
|
|
24
|
+
this.name = 'UnsupportedMultibasePrefixError'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class UnsupportedMultihashCodecError extends Error {
|
|
29
|
+
static name = 'UnsupportedMultihashCodecError'
|
|
30
|
+
|
|
31
|
+
constructor (message = 'Unsupported multihash codec') {
|
|
32
|
+
super(message)
|
|
33
|
+
this.name = 'UnsupportedMultihashCodecError'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class InvalidValueError extends Error {
|
|
38
|
+
static name = 'InvalidValueError'
|
|
39
|
+
|
|
40
|
+
constructor (message = 'Invalid value') {
|
|
41
|
+
super(message)
|
|
42
|
+
this.name = 'InvalidValueError'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class InvalidTopicError extends Error {
|
|
47
|
+
static name = 'InvalidTopicError'
|
|
48
|
+
|
|
49
|
+
constructor (message = 'Invalid topic') {
|
|
50
|
+
super(message)
|
|
51
|
+
this.name = 'InvalidTopicError'
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
* import { createHelia } from 'helia'
|
|
12
12
|
* import { ipns } from '@helia/ipns'
|
|
13
13
|
* import { unixfs } from '@helia/unixfs'
|
|
14
|
+
* import { generateKeyPair } from '@libp2p/crypto/keys'
|
|
14
15
|
*
|
|
15
16
|
* const helia = await createHelia()
|
|
16
17
|
* const name = ipns(helia)
|
|
17
18
|
*
|
|
18
|
-
* // create a
|
|
19
|
-
* const
|
|
20
|
-
* const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
|
|
19
|
+
* // create a keypair to publish an IPNS name
|
|
20
|
+
* const privateKey = await generateKeyPair('Ed25519')
|
|
21
21
|
*
|
|
22
22
|
* // store some data to publish
|
|
23
23
|
* const fs = unixfs(helia)
|
|
24
|
-
* const cid = await fs.
|
|
24
|
+
* const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
25
25
|
*
|
|
26
26
|
* // publish the name
|
|
27
|
-
* await name.publish(
|
|
27
|
+
* await name.publish(privateKey, cid)
|
|
28
28
|
*
|
|
29
29
|
* // resolve the name
|
|
30
|
-
* const result = name.resolve(
|
|
30
|
+
* const result = await name.resolve(privateKey.publicKey)
|
|
31
31
|
*
|
|
32
32
|
* console.info(result.cid, result.path)
|
|
33
33
|
* ```
|
|
@@ -41,30 +41,29 @@
|
|
|
41
41
|
* import { createHelia } from 'helia'
|
|
42
42
|
* import { ipns } from '@helia/ipns'
|
|
43
43
|
* import { unixfs } from '@helia/unixfs'
|
|
44
|
+
* import { generateKeyPair } from '@libp2p/crypto/keys'
|
|
44
45
|
*
|
|
45
46
|
* const helia = await createHelia()
|
|
46
47
|
* const name = ipns(helia)
|
|
47
48
|
*
|
|
48
|
-
* // create a
|
|
49
|
-
* const
|
|
50
|
-
* const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
|
|
49
|
+
* // create a keypair to publish an IPNS name
|
|
50
|
+
* const privateKey = await generateKeyPair('Ed25519')
|
|
51
51
|
*
|
|
52
52
|
* // store some data to publish
|
|
53
53
|
* const fs = unixfs(helia)
|
|
54
|
-
* const cid = await fs.
|
|
54
|
+
* const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
55
55
|
*
|
|
56
56
|
* // publish the name
|
|
57
|
-
* await name.publish(
|
|
57
|
+
* await name.publish(privateKey, cid)
|
|
58
58
|
*
|
|
59
|
-
* // create another
|
|
60
|
-
* const
|
|
61
|
-
* const recursivePeerId = await helia.libp2p.services.keychain.exportPeerId(recursiveKeyInfo.name)
|
|
59
|
+
* // create another keypair to re-publish the original record
|
|
60
|
+
* const recursivePrivateKey = await generateKeyPair('Ed25519')
|
|
62
61
|
*
|
|
63
62
|
* // publish the recursive name
|
|
64
|
-
* await name.publish(
|
|
63
|
+
* await name.publish(recursivePrivateKey, privateKey.publicKey)
|
|
65
64
|
*
|
|
66
65
|
* // resolve the name recursively - it resolves until a CID is found
|
|
67
|
-
* const result = name.resolve(
|
|
66
|
+
* const result = await name.resolve(recursivePrivateKey.publicKey)
|
|
68
67
|
* console.info(result.cid.toString() === cid.toString()) // true
|
|
69
68
|
* ```
|
|
70
69
|
*
|
|
@@ -76,27 +75,27 @@
|
|
|
76
75
|
* import { createHelia } from 'helia'
|
|
77
76
|
* import { ipns } from '@helia/ipns'
|
|
78
77
|
* import { unixfs } from '@helia/unixfs'
|
|
78
|
+
* import { generateKeyPair } from '@libp2p/crypto/keys'
|
|
79
79
|
*
|
|
80
80
|
* const helia = await createHelia()
|
|
81
81
|
* const name = ipns(helia)
|
|
82
82
|
*
|
|
83
|
-
* // create a
|
|
84
|
-
* const
|
|
85
|
-
* const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
|
|
83
|
+
* // create a keypair to publish an IPNS name
|
|
84
|
+
* const privateKey = await generateKeyPair('Ed25519')
|
|
86
85
|
*
|
|
87
86
|
* // store some data to publish
|
|
88
87
|
* const fs = unixfs(helia)
|
|
89
|
-
* const fileCid = await fs.
|
|
88
|
+
* const fileCid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
90
89
|
*
|
|
91
90
|
* // store the file in a directory
|
|
92
|
-
* const dirCid = await fs.
|
|
91
|
+
* const dirCid = await fs.addDirectory()
|
|
93
92
|
* const finalDirCid = await fs.cp(fileCid, dirCid, '/foo.txt')
|
|
94
93
|
*
|
|
95
94
|
* // publish the name
|
|
96
|
-
* await name.publish(
|
|
95
|
+
* await name.publish(privateKey, `/ipfs/${finalDirCid}/foo.txt`)
|
|
97
96
|
*
|
|
98
97
|
* // resolve the name
|
|
99
|
-
* const result = name.resolve(
|
|
98
|
+
* const result = await name.resolve(privateKey.publicKey)
|
|
100
99
|
*
|
|
101
100
|
* console.info(result.cid, result.path) // QmFoo.. 'foo.txt'
|
|
102
101
|
* ```
|
|
@@ -123,11 +122,14 @@
|
|
|
123
122
|
* import { pubsub } from '@helia/ipns/routing'
|
|
124
123
|
* import { unixfs } from '@helia/unixfs'
|
|
125
124
|
* import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
|
125
|
+
* import { generateKeyPair } from '@libp2p/crypto/keys'
|
|
126
|
+
* import type { Libp2p, PubSub } from '@libp2p/interface'
|
|
127
|
+
* import type { DefaultLibp2pServices } from 'helia'
|
|
126
128
|
*
|
|
127
129
|
* const libp2pOptions = libp2pDefaults()
|
|
128
130
|
* libp2pOptions.services.pubsub = gossipsub()
|
|
129
131
|
*
|
|
130
|
-
* const helia = await createHelia({
|
|
132
|
+
* const helia = await createHelia<Libp2p<DefaultLibp2pServices & { pubsub: PubSub }>>({
|
|
131
133
|
* libp2p: libp2pOptions
|
|
132
134
|
* })
|
|
133
135
|
* const name = ipns(helia, {
|
|
@@ -136,44 +138,50 @@
|
|
|
136
138
|
* ]
|
|
137
139
|
* })
|
|
138
140
|
*
|
|
139
|
-
* // create a
|
|
140
|
-
* const
|
|
141
|
-
* const peerId = await helia.libp2p.services.keychain.exportPeerId(keyInfo.name)
|
|
141
|
+
* // create a keypair to publish an IPNS name
|
|
142
|
+
* const privateKey = await generateKeyPair('Ed25519')
|
|
142
143
|
*
|
|
143
144
|
* // store some data to publish
|
|
144
145
|
* const fs = unixfs(helia)
|
|
145
|
-
* const cid = await fs.
|
|
146
|
+
* const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
146
147
|
*
|
|
147
148
|
* // publish the name
|
|
148
|
-
* await name.publish(
|
|
149
|
+
* await name.publish(privateKey, cid)
|
|
149
150
|
*
|
|
150
151
|
* // resolve the name
|
|
151
|
-
* const
|
|
152
|
+
* const result = await name.resolve(privateKey.publicKey)
|
|
152
153
|
* ```
|
|
153
154
|
*
|
|
154
155
|
* @example Using custom DNS over HTTPS resolvers
|
|
155
156
|
*
|
|
156
|
-
*
|
|
157
|
+
* To use custom resolvers, configure Helia's `dns` option:
|
|
157
158
|
*
|
|
158
159
|
* ```TypeScript
|
|
159
160
|
* import { createHelia } from 'helia'
|
|
160
161
|
* import { ipns } from '@helia/ipns'
|
|
161
|
-
* import {
|
|
162
|
-
* import { dnsOverHttps } from '@
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* const
|
|
166
|
-
*
|
|
167
|
-
*
|
|
162
|
+
* import { dns } from '@multiformats/dns'
|
|
163
|
+
* import { dnsOverHttps } from '@multiformats/dns/resolvers'
|
|
164
|
+
* import { helia } from '@helia/ipns/routing'
|
|
165
|
+
*
|
|
166
|
+
* const node = await createHelia({
|
|
167
|
+
* dns: dns({
|
|
168
|
+
* resolvers: {
|
|
169
|
+
* '.': dnsOverHttps('https://private-dns-server.me/dns-query')
|
|
170
|
+
* }
|
|
171
|
+
* })
|
|
172
|
+
* })
|
|
173
|
+
* const name = ipns(node, {
|
|
174
|
+
* routers: [
|
|
175
|
+
* helia(node.routing)
|
|
168
176
|
* ]
|
|
169
177
|
* })
|
|
170
178
|
*
|
|
171
|
-
* const
|
|
179
|
+
* const result = name.resolveDNSLink('some-domain-with-dnslink-entry.com')
|
|
172
180
|
* ```
|
|
173
181
|
*
|
|
174
182
|
* @example Resolving a domain with a dnslink entry
|
|
175
183
|
*
|
|
176
|
-
* Calling `
|
|
184
|
+
* Calling `resolveDNSLink` with the `@helia/ipns` instance:
|
|
177
185
|
*
|
|
178
186
|
* ```TypeScript
|
|
179
187
|
* // resolve a CID from a TXT record in a DNS zone file, using the default
|
|
@@ -185,10 +193,16 @@
|
|
|
185
193
|
* // ;; ANSWER SECTION:
|
|
186
194
|
* // _dnslink.website.ipfs.io. 60 IN TXT "dnslink=/ipfs/QmWebsite"
|
|
187
195
|
*
|
|
188
|
-
*
|
|
196
|
+
* import { createHelia } from 'helia'
|
|
197
|
+
* import { ipns } from '@helia/ipns'
|
|
198
|
+
*
|
|
199
|
+
* const node = await createHelia()
|
|
200
|
+
* const name = ipns(node)
|
|
189
201
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
202
|
+
* const { answer } = await name.resolveDNSLink('ipfs.io')
|
|
203
|
+
*
|
|
204
|
+
* console.info(answer)
|
|
205
|
+
* // { data: '/ipfs/QmWebsite' }
|
|
192
206
|
* ```
|
|
193
207
|
*
|
|
194
208
|
* @example Using DNS-Over-HTTPS
|
|
@@ -200,14 +214,21 @@
|
|
|
200
214
|
* If this is a concern, use the DNS-JSON-Over-HTTPS resolver instead.
|
|
201
215
|
*
|
|
202
216
|
* ```TypeScript
|
|
203
|
-
*
|
|
204
|
-
* import {
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
217
|
+
* import { createHelia } from 'helia'
|
|
218
|
+
* import { ipns } from '@helia/ipns'
|
|
219
|
+
* import { dns } from '@multiformats/dns'
|
|
220
|
+
* import { dnsOverHttps } from '@multiformats/dns/resolvers'
|
|
221
|
+
*
|
|
222
|
+
* const node = await createHelia({
|
|
223
|
+
* dns: dns({
|
|
224
|
+
* resolvers: {
|
|
225
|
+
* '.': dnsOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
|
|
226
|
+
* }
|
|
227
|
+
* })
|
|
210
228
|
* })
|
|
229
|
+
* const name = ipns(node)
|
|
230
|
+
*
|
|
231
|
+
* const result = await name.resolveDNSLink('ipfs.io')
|
|
211
232
|
* ```
|
|
212
233
|
*
|
|
213
234
|
* @example Using DNS-JSON-Over-HTTPS
|
|
@@ -216,34 +237,46 @@
|
|
|
216
237
|
* result in a smaller browser bundle due to the response being plain JSON.
|
|
217
238
|
*
|
|
218
239
|
* ```TypeScript
|
|
219
|
-
*
|
|
220
|
-
* import {
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
240
|
+
* import { createHelia } from 'helia'
|
|
241
|
+
* import { ipns } from '@helia/ipns'
|
|
242
|
+
* import { dns } from '@multiformats/dns'
|
|
243
|
+
* import { dnsJsonOverHttps } from '@multiformats/dns/resolvers'
|
|
244
|
+
*
|
|
245
|
+
* const node = await createHelia({
|
|
246
|
+
* dns: dns({
|
|
247
|
+
* resolvers: {
|
|
248
|
+
* '.': dnsJsonOverHttps('https://mozilla.cloudflare-dns.com/dns-query')
|
|
249
|
+
* }
|
|
250
|
+
* })
|
|
226
251
|
* })
|
|
252
|
+
* const name = ipns(node)
|
|
253
|
+
*
|
|
254
|
+
* const result = await name.resolveDNSLink('ipfs.io')
|
|
227
255
|
* ```
|
|
228
256
|
*/
|
|
229
257
|
|
|
230
|
-
import {
|
|
258
|
+
import { NotFoundError, isPublicKey } from '@libp2p/interface'
|
|
231
259
|
import { logger } from '@libp2p/logger'
|
|
232
|
-
import {
|
|
233
|
-
import { create, marshal, peerIdToRoutingKey, unmarshal } from 'ipns'
|
|
260
|
+
import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord, type IPNSRecord } from 'ipns'
|
|
234
261
|
import { ipnsSelector } from 'ipns/selector'
|
|
235
262
|
import { ipnsValidator } from 'ipns/validator'
|
|
263
|
+
import { base36 } from 'multiformats/bases/base36'
|
|
264
|
+
import { base58btc } from 'multiformats/bases/base58'
|
|
236
265
|
import { CID } from 'multiformats/cid'
|
|
266
|
+
import * as Digest from 'multiformats/hashes/digest'
|
|
237
267
|
import { CustomProgressEvent } from 'progress-events'
|
|
238
268
|
import { resolveDNSLink } from './dnslink.js'
|
|
269
|
+
import { InvalidValueError, RecordsFailedValidationError, UnsupportedMultibasePrefixError, UnsupportedMultihashCodecError } from './errors.js'
|
|
239
270
|
import { helia } from './routing/helia.js'
|
|
240
271
|
import { localStore, type LocalStore } from './routing/local-store.js'
|
|
272
|
+
import { isCodec, IDENTITY_CODEC, SHA2_256_CODEC } from './utils.js'
|
|
241
273
|
import type { IPNSRouting, IPNSRoutingEvents } from './routing/index.js'
|
|
242
274
|
import type { Routing } from '@helia/interface'
|
|
243
|
-
import type { AbortOptions, ComponentLogger, Logger,
|
|
275
|
+
import type { AbortOptions, ComponentLogger, Logger, PrivateKey, PublicKey } from '@libp2p/interface'
|
|
244
276
|
import type { Answer, DNS, ResolveDnsProgressEvents } from '@multiformats/dns'
|
|
245
277
|
import type { Datastore } from 'interface-datastore'
|
|
246
|
-
import type {
|
|
278
|
+
import type { MultibaseDecoder } from 'multiformats/bases/interface'
|
|
279
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
247
280
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
248
281
|
|
|
249
282
|
const log = logger('helia:ipns')
|
|
@@ -375,13 +408,13 @@ export interface IPNS {
|
|
|
375
408
|
*
|
|
376
409
|
* If the value is a PeerId, a recursive IPNS record will be created.
|
|
377
410
|
*/
|
|
378
|
-
publish(key:
|
|
411
|
+
publish(key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options?: PublishOptions): Promise<IPNSRecord>
|
|
379
412
|
|
|
380
413
|
/**
|
|
381
414
|
* Accepts a public key formatted as a libp2p PeerID and resolves the IPNS record
|
|
382
415
|
* corresponding to that public key until a value is found
|
|
383
416
|
*/
|
|
384
|
-
resolve(key:
|
|
417
|
+
resolve(key: PublicKey | MultihashDigest<0x00 | 0x12>, options?: ResolveOptions): Promise<IPNSResolveResult>
|
|
385
418
|
|
|
386
419
|
/**
|
|
387
420
|
* Resolve a CID from a dns-link style IPNS record
|
|
@@ -403,6 +436,11 @@ export interface IPNSComponents {
|
|
|
403
436
|
logger: ComponentLogger
|
|
404
437
|
}
|
|
405
438
|
|
|
439
|
+
const bases: Record<string, MultibaseDecoder<string>> = {
|
|
440
|
+
[base36.prefix]: base36,
|
|
441
|
+
[base58btc.prefix]: base58btc
|
|
442
|
+
}
|
|
443
|
+
|
|
406
444
|
class DefaultIPNS implements IPNS {
|
|
407
445
|
private readonly routers: IPNSRouting[]
|
|
408
446
|
private readonly localStore: LocalStore
|
|
@@ -420,21 +458,21 @@ class DefaultIPNS implements IPNS {
|
|
|
420
458
|
this.log = components.logger.forComponent('helia:ipns')
|
|
421
459
|
}
|
|
422
460
|
|
|
423
|
-
async publish (key:
|
|
461
|
+
async publish (key: PrivateKey, value: CID | PublicKey | MultihashDigest<0x00 | 0x12> | string, options: PublishOptions = {}): Promise<IPNSRecord> {
|
|
424
462
|
try {
|
|
425
463
|
let sequenceNumber = 1n
|
|
426
|
-
const routingKey =
|
|
464
|
+
const routingKey = multihashToIPNSRoutingKey(key.publicKey.toMultihash())
|
|
427
465
|
|
|
428
466
|
if (await this.localStore.has(routingKey, options)) {
|
|
429
467
|
// if we have published under this key before, increment the sequence number
|
|
430
468
|
const { record } = await this.localStore.get(routingKey, options)
|
|
431
|
-
const existingRecord =
|
|
469
|
+
const existingRecord = unmarshalIPNSRecord(record)
|
|
432
470
|
sequenceNumber = existingRecord.sequence + 1n
|
|
433
471
|
}
|
|
434
472
|
|
|
435
473
|
// create record
|
|
436
|
-
const record = await
|
|
437
|
-
const marshaledRecord =
|
|
474
|
+
const record = await createIPNSRecord(key, value, sequenceNumber, options.lifetime ?? DEFAULT_LIFETIME_MS, options)
|
|
475
|
+
const marshaledRecord = marshalIPNSRecord(record)
|
|
438
476
|
|
|
439
477
|
await this.localStore.put(routingKey, marshaledRecord, options)
|
|
440
478
|
|
|
@@ -450,8 +488,9 @@ class DefaultIPNS implements IPNS {
|
|
|
450
488
|
}
|
|
451
489
|
}
|
|
452
490
|
|
|
453
|
-
async resolve (key:
|
|
454
|
-
const
|
|
491
|
+
async resolve (key: PublicKey | MultihashDigest<0x00 | 0x12>, options: ResolveOptions = {}): Promise<IPNSResolveResult> {
|
|
492
|
+
const digest = isPublicKey(key) ? key.toMultihash() : key
|
|
493
|
+
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
455
494
|
const record = await this.#findIpnsRecord(routingKey, options)
|
|
456
495
|
|
|
457
496
|
return {
|
|
@@ -511,7 +550,31 @@ class DefaultIPNS implements IPNS {
|
|
|
511
550
|
const scheme = parts[1]
|
|
512
551
|
|
|
513
552
|
if (scheme === 'ipns') {
|
|
514
|
-
const
|
|
553
|
+
const str = parts[2]
|
|
554
|
+
const prefix = str.substring(0, 1)
|
|
555
|
+
let buf: Uint8Array | undefined
|
|
556
|
+
|
|
557
|
+
if (prefix === '1' || prefix === 'Q') {
|
|
558
|
+
buf = base58btc.decode(`z${str}`)
|
|
559
|
+
} else if (bases[prefix] != null) {
|
|
560
|
+
buf = bases[prefix].decode(str)
|
|
561
|
+
} else {
|
|
562
|
+
throw new UnsupportedMultibasePrefixError(`Unsupported multibase prefix "${prefix}"`)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
let digest: MultihashDigest<number>
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
digest = Digest.decode(buf)
|
|
569
|
+
} catch {
|
|
570
|
+
digest = CID.decode(buf).multihash
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (!isCodec(digest, IDENTITY_CODEC) && !isCodec(digest, SHA2_256_CODEC)) {
|
|
574
|
+
throw new UnsupportedMultihashCodecError(`Unsupported multihash codec "${digest.code}"`)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const { cid } = await this.resolve(digest, options)
|
|
515
578
|
const path = parts.slice(3).join('/')
|
|
516
579
|
return {
|
|
517
580
|
cid,
|
|
@@ -530,7 +593,7 @@ class DefaultIPNS implements IPNS {
|
|
|
530
593
|
}
|
|
531
594
|
|
|
532
595
|
log.error('invalid ipfs path %s', ipfsPath)
|
|
533
|
-
throw new
|
|
596
|
+
throw new InvalidValueError('Invalid value')
|
|
534
597
|
}
|
|
535
598
|
|
|
536
599
|
async #findIpnsRecord (routingKey: Uint8Array, options: ResolveOptions = {}): Promise<IPNSRecord> {
|
|
@@ -553,7 +616,7 @@ class DefaultIPNS implements IPNS {
|
|
|
553
616
|
this.log('record was valid')
|
|
554
617
|
|
|
555
618
|
// check the TTL
|
|
556
|
-
const ipnsRecord =
|
|
619
|
+
const ipnsRecord = unmarshalIPNSRecord(record)
|
|
557
620
|
|
|
558
621
|
// IPNS TTL is in nanoseconds, convert to milliseconds, default to one
|
|
559
622
|
// hour
|
|
@@ -588,7 +651,7 @@ class DefaultIPNS implements IPNS {
|
|
|
588
651
|
}
|
|
589
652
|
|
|
590
653
|
if (options.offline === true) {
|
|
591
|
-
throw new
|
|
654
|
+
throw new NotFoundError('Record was not present in the cache or has expired')
|
|
592
655
|
}
|
|
593
656
|
|
|
594
657
|
log('did not have record locally')
|
|
@@ -624,17 +687,17 @@ class DefaultIPNS implements IPNS {
|
|
|
624
687
|
|
|
625
688
|
if (records.length === 0) {
|
|
626
689
|
if (foundInvalid > 0) {
|
|
627
|
-
throw new
|
|
690
|
+
throw new RecordsFailedValidationError(`${foundInvalid > 1 ? `${foundInvalid} records` : 'Record'} found for routing key ${foundInvalid > 1 ? 'were' : 'was'} invalid`)
|
|
628
691
|
}
|
|
629
692
|
|
|
630
|
-
throw new
|
|
693
|
+
throw new NotFoundError('Could not find record for routing key')
|
|
631
694
|
}
|
|
632
695
|
|
|
633
696
|
const record = records[ipnsSelector(routingKey, records)]
|
|
634
697
|
|
|
635
698
|
await this.localStore.put(routingKey, record, options)
|
|
636
699
|
|
|
637
|
-
return
|
|
700
|
+
return unmarshalIPNSRecord(record)
|
|
638
701
|
}
|
|
639
702
|
}
|
|
640
703
|
|
package/src/routing/index.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface IPNSRouting {
|
|
|
22
22
|
get(routingKey: Uint8Array, options?: GetOptions): Promise<Uint8Array>
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
export type { DatastoreProgressEvents }
|
|
26
|
+
export type { HeliaRoutingProgressEvents }
|
|
27
|
+
export type { PubSubProgressEvents }
|
|
28
|
+
|
|
25
29
|
export type IPNSRoutingEvents =
|
|
26
30
|
DatastoreProgressEvents |
|
|
27
31
|
HeliaRoutingProgressEvents |
|
|
@@ -29,3 +33,4 @@ export type IPNSRoutingEvents =
|
|
|
29
33
|
|
|
30
34
|
export { helia } from './helia.js'
|
|
31
35
|
export { pubsub } from './pubsub.js'
|
|
36
|
+
export type { PubsubRoutingComponents } from './pubsub.js'
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isPublicKey } from '@libp2p/interface'
|
|
2
2
|
import { logger } from '@libp2p/logger'
|
|
3
|
-
import {
|
|
3
|
+
import { multihashToIPNSRoutingKey } from 'ipns'
|
|
4
4
|
import { ipnsSelector } from 'ipns/selector'
|
|
5
5
|
import { ipnsValidator } from 'ipns/validator'
|
|
6
6
|
import { CustomProgressEvent, type ProgressEvent } from 'progress-events'
|
|
7
7
|
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
8
8
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
9
9
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
10
|
+
import { InvalidTopicError } from '../errors.js'
|
|
10
11
|
import { localStore, type LocalStore } from './local-store.js'
|
|
11
12
|
import type { GetOptions, IPNSRouting, PutOptions } from './index.js'
|
|
12
|
-
import type { PeerId, Message, PublishResult, PubSub } from '@libp2p/interface'
|
|
13
|
+
import type { PeerId, Message, PublishResult, PubSub, PublicKey } from '@libp2p/interface'
|
|
13
14
|
import type { Datastore } from 'interface-datastore'
|
|
15
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
14
16
|
|
|
15
17
|
const log = logger('helia:ipns:routing:pubsub')
|
|
16
18
|
|
|
@@ -147,8 +149,9 @@ class PubSubRouting implements IPNSRouting {
|
|
|
147
149
|
/**
|
|
148
150
|
* Cancel pubsub subscriptions related to ipns
|
|
149
151
|
*/
|
|
150
|
-
cancel (key:
|
|
151
|
-
const
|
|
152
|
+
cancel (key: PublicKey | MultihashDigest<0x00 | 0x12>): void {
|
|
153
|
+
const digest = isPublicKey(key) ? key.toMultihash() : key
|
|
154
|
+
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
152
155
|
const topic = keyToTopic(routingKey)
|
|
153
156
|
|
|
154
157
|
// Not found topic
|
|
@@ -177,7 +180,7 @@ function keyToTopic (key: Uint8Array): string {
|
|
|
177
180
|
*/
|
|
178
181
|
function topicToKey (topic: string): Uint8Array {
|
|
179
182
|
if (topic.substring(0, PUBSUB_NAMESPACE.length) !== PUBSUB_NAMESPACE) {
|
|
180
|
-
throw new
|
|
183
|
+
throw new InvalidTopicError('Topic received is not from a record')
|
|
181
184
|
}
|
|
182
185
|
|
|
183
186
|
const key = topic.substring(PUBSUB_NAMESPACE.length)
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
2
|
+
|
|
3
|
+
export const IDENTITY_CODEC = 0x0
|
|
4
|
+
export const SHA2_256_CODEC = 0x12
|
|
5
|
+
|
|
6
|
+
export function isCodec <T extends number> (digest: MultihashDigest, codec: T): digest is MultihashDigest<T> {
|
|
7
|
+
return digest.code === codec
|
|
8
|
+
}
|