@helia/ipns 10.0.1-cee53d7d → 10.0.2-688bd9de

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 (80) hide show
  1. package/README.md +3 -7
  2. package/dist/index.min.js +16 -5
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/errors.d.ts +0 -8
  5. package/dist/src/errors.d.ts.map +1 -1
  6. package/dist/src/errors.js +0 -8
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/index.d.ts +58 -20
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +11 -11
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/ipns/publisher.d.ts.map +1 -1
  13. package/dist/src/ipns/publisher.js +27 -14
  14. package/dist/src/ipns/publisher.js.map +1 -1
  15. package/dist/src/ipns/republisher.d.ts.map +1 -1
  16. package/dist/src/ipns/republisher.js +22 -9
  17. package/dist/src/ipns/republisher.js.map +1 -1
  18. package/dist/src/ipns/resolver.d.ts.map +1 -1
  19. package/dist/src/ipns/resolver.js +39 -17
  20. package/dist/src/ipns/resolver.js.map +1 -1
  21. package/dist/src/ipns.d.ts.map +1 -1
  22. package/dist/src/ipns.js +8 -8
  23. package/dist/src/ipns.js.map +1 -1
  24. package/dist/src/local-store.d.ts +4 -4
  25. package/dist/src/local-store.d.ts.map +1 -1
  26. package/dist/src/local-store.js +2 -1
  27. package/dist/src/local-store.js.map +1 -1
  28. package/dist/src/pb/ipns.d.ts +29 -29
  29. package/dist/src/pb/ipns.d.ts.map +1 -1
  30. package/dist/src/pb/ipns.js +15 -15
  31. package/dist/src/records.d.ts +27 -142
  32. package/dist/src/records.d.ts.map +1 -1
  33. package/dist/src/records.js +39 -46
  34. package/dist/src/records.js.map +1 -1
  35. package/dist/src/routing/helia.d.ts +5 -5
  36. package/dist/src/routing/helia.d.ts.map +1 -1
  37. package/dist/src/routing/helia.js +3 -3
  38. package/dist/src/routing/helia.js.map +1 -1
  39. package/dist/src/routing/index.d.ts +7 -7
  40. package/dist/src/routing/index.d.ts.map +1 -1
  41. package/dist/src/routing/index.js +2 -2
  42. package/dist/src/routing/index.js.map +1 -1
  43. package/dist/src/routing/local-store.d.ts +1 -1
  44. package/dist/src/routing/local-store.d.ts.map +1 -1
  45. package/dist/src/routing/local-store.js +3 -3
  46. package/dist/src/routing/local-store.js.map +1 -1
  47. package/dist/src/routing/pubsub.d.ts +11 -11
  48. package/dist/src/routing/pubsub.d.ts.map +1 -1
  49. package/dist/src/routing/pubsub.js +11 -12
  50. package/dist/src/routing/pubsub.js.map +1 -1
  51. package/dist/src/selector.d.ts +2 -2
  52. package/dist/src/selector.d.ts.map +1 -1
  53. package/dist/src/selector.js +13 -9
  54. package/dist/src/selector.js.map +1 -1
  55. package/dist/src/utils.d.ts +9 -10
  56. package/dist/src/utils.d.ts.map +1 -1
  57. package/dist/src/utils.js +20 -128
  58. package/dist/src/utils.js.map +1 -1
  59. package/dist/src/validator.d.ts +9 -3
  60. package/dist/src/validator.d.ts.map +1 -1
  61. package/dist/src/validator.js +57 -14
  62. package/dist/src/validator.js.map +1 -1
  63. package/package.json +6 -31
  64. package/src/errors.ts +0 -10
  65. package/src/index.ts +75 -25
  66. package/src/ipns/publisher.ts +27 -14
  67. package/src/ipns/republisher.ts +31 -13
  68. package/src/ipns/resolver.ts +39 -21
  69. package/src/ipns.ts +8 -8
  70. package/src/local-store.ts +8 -7
  71. package/src/pb/ipns.proto +1 -1
  72. package/src/pb/ipns.ts +37 -37
  73. package/src/records.ts +57 -210
  74. package/src/routing/helia.ts +6 -6
  75. package/src/routing/index.ts +7 -7
  76. package/src/routing/local-store.ts +6 -6
  77. package/src/routing/pubsub.ts +24 -25
  78. package/src/selector.ts +14 -11
  79. package/src/utils.ts +25 -147
  80. package/src/validator.ts +73 -16
package/src/index.ts CHANGED
@@ -37,7 +37,6 @@
37
37
  * import { createHelia } from 'helia'
38
38
  * import { ipns } from '@helia/ipns'
39
39
  * import { unixfs } from '@helia/unixfs'
40
- * import { generateKeyPair } from '@libp2p/crypto/keys'
41
40
  *
42
41
  * const helia = await createHelia()
43
42
  * const name = ipns(helia)
@@ -66,7 +65,6 @@
66
65
  * import { createHelia } from 'helia'
67
66
  * import { ipns } from '@helia/ipns'
68
67
  * import { unixfs } from '@helia/unixfs'
69
- * import { generateKeyPair } from '@libp2p/crypto/keys'
70
68
  *
71
69
  * const helia = await createHelia()
72
70
  * const name = ipns(helia)
@@ -105,15 +103,13 @@
105
103
  * may fail to be published with "Insufficient peers" errors.
106
104
  *
107
105
  * ```TypeScript
108
- * import { ipns } from '@helia/ipns'
109
- * import { pubsub } from '@helia/ipns/routing'
106
+ * import { ipns, pubSubIPNSRouting } from '@helia/ipns'
110
107
  * import { withLibp2p, libp2pDefaults } from '@helia/libp2p'
111
108
  * import { unixfs } from '@helia/unixfs'
112
- * import { generateKeyPair } from '@libp2p/crypto/keys'
113
109
  * import { floodsub } from '@libp2p/floodsub'
114
110
  * import { createHelia } from 'helia'
115
111
  * import type { Helia } from '@helia/interface'
116
- * import type { PubSub } from '@helia/ipns/routing'
112
+ * import type { PubSub } from '@helia/ipns'
117
113
  * import type { DefaultLibp2pServices } from '@helia/libp2p'
118
114
  * import type { FloodSub } from '@libp2p/floodsub'
119
115
  * import type { Libp2p } from '@libp2p/interface'
@@ -125,7 +121,7 @@
125
121
  *
126
122
  * const name = ipns(helia, {
127
123
  * routers: [
128
- * pubsub(helia)
124
+ * pubSubIPNSRouting(helia)
129
125
  * ]
130
126
  * })
131
127
  *
@@ -147,10 +143,10 @@ import { CID } from 'multiformats/cid'
147
143
  import { IPNSResolver as IPNSResolverClass } from './ipns/resolver.ts'
148
144
  import { IPNS as IPNSClass } from './ipns.ts'
149
145
  import { localStore } from './local-store.ts'
150
- import { helia } from './routing/index.ts'
151
- import { localStoreRouting } from './routing/local-store.ts'
146
+ import { heliaIPNSRouting } from './routing/index.ts'
147
+ import { localStoreIPNSRouting } from './routing/local-store.ts'
152
148
  import type { IPNSResolverComponents } from './ipns/resolver.ts'
153
- import type { IPNSRecord } from './records.ts'
149
+ import type { IPNSEntry } from './pb/ipns.ts'
154
150
  import type { IPNSRouting, IPNSRoutingProgressEvents } from './routing/index.ts'
155
151
  import type { Routing, HeliaEvents, Keychain, PublicKey } from '@helia/interface'
156
152
  import type { ComponentLogger, TypedEventEmitter } from '@libp2p/interface'
@@ -159,14 +155,29 @@ import type { Datastore } from 'interface-datastore'
159
155
  import type { MultihashDigest } from 'multiformats/hashes/interface'
160
156
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
161
157
 
158
+ export * from './routing/index.ts'
159
+ export * from './pb/ipns.ts'
160
+
161
+ export {
162
+ multihashFromIPNSRoutingKey,
163
+ multihashToIPNSRoutingKey
164
+ } from './utils.ts'
165
+
166
+ export {
167
+ createIPNSRecord
168
+ } from './records.ts'
169
+ export type {
170
+ CreateIPNSRecordOptions
171
+ } from './records.ts'
172
+
162
173
  export type PublishProgressEvents =
163
174
  ProgressEvent<'ipns:publish:start'> |
164
- ProgressEvent<'ipns:publish:success', IPNSRecord> |
175
+ ProgressEvent<'ipns:publish:success', IPNSEntry> |
165
176
  ProgressEvent<'ipns:publish:error', Error>
166
177
 
167
178
  export type ResolveProgressEvents =
168
179
  ProgressEvent<'ipns:resolve:start', unknown> |
169
- ProgressEvent<'ipns:resolve:success', IPNSRecord> |
180
+ ProgressEvent<'ipns:resolve:success', IPNSEntry> |
170
181
  ProgressEvent<'ipns:resolve:error', Error>
171
182
 
172
183
  export type DatastoreProgressEvents =
@@ -177,7 +188,8 @@ export type DatastoreProgressEvents =
177
188
 
178
189
  export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingProgressEvents> {
179
190
  /**
180
- * Time duration of the signature validity in ms
191
+ * Time duration of the signature validity in ms - after this many ms have
192
+ * expired the record will be invalidated.
181
193
  *
182
194
  * @default 172_800_000
183
195
  */
@@ -199,16 +211,44 @@ export interface PublishOptions extends AbortOptions, ProgressOptions<PublishPro
199
211
  v1Compatible?: boolean
200
212
 
201
213
  /**
202
- * The TTL of the record in ms
214
+ * The TTL of the record in ms - after this many ms have expired, resolving
215
+ * the record will query the routing for an updated version.
216
+ *
217
+ * Before this many ms have expired any locally stored copy will be treated as
218
+ * the latest version and the routing will not be queried.
203
219
  *
204
220
  * @default 300_000
205
221
  */
206
222
  ttl?: number
223
+
224
+ /**
225
+ * Extensible data that will be added to the IPNS record data and signed to
226
+ * verify it's integrity.
227
+ *
228
+ * Note that this data will be encoded as DAG-CBOR so it must be valid.
229
+ *
230
+ * It is recommended that any custom fields set here are prefixed with `_` to
231
+ * avoid collision with any mandatory fields added to future versions of the
232
+ * IPNS specification.
233
+ *
234
+ * @see https://specs.ipfs.tech/ipns/ipns-record/#extensible-data-dag-cbor
235
+ */
236
+ data?: Record<string, any>
207
237
  }
208
238
 
209
- export interface IPNSRecordMetadata {
210
- keyName: string
211
- lifetime: number
239
+ /**
240
+ * Extensible data from the record `data` field.
241
+ *
242
+ * The wire format of this data is DAG-CBOR.
243
+ *
244
+ * @see https://specs.ipfs.tech/ipns/ipns-record/#extensible-data-dag-cbor
245
+ */
246
+ export interface IPNSRecordData extends Record<string, any> {
247
+ Value: Uint8Array<ArrayBuffer>
248
+ Validity: Uint8Array<ArrayBuffer>
249
+ ValidityType: IPNSEntry.ValidityType
250
+ Sequence: bigint
251
+ TTL: bigint
212
252
  }
213
253
 
214
254
  export interface IPNSResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingProgressEvents> {
@@ -225,20 +265,33 @@ export interface IPNSResolveOptions extends AbortOptions, ProgressOptions<Resolv
225
265
  * @default false
226
266
  */
227
267
  nocache?: boolean
268
+
269
+ /**
270
+ * If true, ensure the record fields and signature are valid. If false, just
271
+ * return the record.
272
+ *
273
+ * @default true
274
+ */
275
+ validate?: boolean
228
276
  }
229
277
 
230
278
  export interface IPNSResolveResult {
231
279
  /**
232
280
  * The resolved record
233
281
  */
234
- record: IPNSRecord
282
+ record: IPNSEntry
283
+
284
+ /**
285
+ * The value contained within the IPNS record
286
+ */
287
+ value: string
235
288
  }
236
289
 
237
290
  export interface IPNSPublishResult {
238
291
  /**
239
292
  * The published record
240
293
  */
241
- record: IPNSRecord
294
+ record: IPNSEntry
242
295
 
243
296
  /**
244
297
  * The IPNS name that can be used to resolve this record
@@ -309,7 +362,7 @@ export interface IPNS {
309
362
  resolve(name: CID | PublicKey | MultihashDigest | string, options?: IPNSResolveOptions): AsyncGenerator<IPNSResolveResult>
310
363
 
311
364
  /**
312
- * Stop republishing of an IPNS record
365
+ * Stop republishing of an IPNS record.
313
366
  *
314
367
  * This will delete the last signed IPNS record from the datastore.
315
368
  *
@@ -319,9 +372,6 @@ export interface IPNS {
319
372
  unpublish(keyName: string, options?: AbortOptions): Promise<void>
320
373
  }
321
374
 
322
- export type { IPNSRouting } from './routing/index.ts'
323
- export type { IPNSRecord } from './records.ts'
324
-
325
375
  export interface IPNSComponents {
326
376
  datastore: Datastore
327
377
  routing: Routing
@@ -366,8 +416,8 @@ export function ipns (components: IPNSComponents, options: IPNSOptions = {}): IP
366
416
  export function ipnsResolver (components: IPNSResolverComponents, options: IPNSResolverOptions = {}): IPNSResolver {
367
417
  const store = localStore(components.datastore, components.logger.forComponent('helia:ipns:local-store'))
368
418
  const routers = [
369
- localStoreRouting(store),
370
- helia(components.routing),
419
+ localStoreIPNSRouting(store),
420
+ heliaIPNSRouting(components.routing),
371
421
  ...(options.routers ?? [])
372
422
  ]
373
423
 
@@ -1,8 +1,9 @@
1
1
  import { base36 } from 'multiformats/bases/base36'
2
2
  import { CustomProgressEvent } from 'progress-events'
3
- import { DEFAULT_LIFETIME_MS, DEFAULT_TTL_NS } from '../constants.ts'
3
+ import { DEFAULT_LIFETIME_MS } from '../constants.ts'
4
+ import { IPNSEntry } from '../pb/ipns.ts'
4
5
  import { createIPNSRecord } from '../records.ts'
5
- import { marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from '../utils.ts'
6
+ import { decodeExtensibleData, multihashToIPNSRoutingKey } from '../utils.ts'
6
7
  import type { IPNSPublishResult, PublishOptions } from '../index.ts'
7
8
  import type { LocalStore } from '../local-store.ts'
8
9
  import type { IPNSRouting } from '../routing/index.ts'
@@ -42,31 +43,43 @@ export class IPNSPublisher {
42
43
  if (await this.localStore.has(routingKey, options)) {
43
44
  // if we have published under this key before, increment the sequence number
44
45
  const { record } = await this.localStore.get(routingKey, options)
45
- const existingRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
46
- sequenceNumber = existingRecord.sequence + 1n
46
+ const existingRecord = IPNSEntry.decode(record)
47
+ const data = decodeExtensibleData(existingRecord.data)
48
+ sequenceNumber = (data.Sequence ?? 0n) + 1n
47
49
  }
48
50
 
49
- // convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
50
- const ttlNs = options.ttl != null ? BigInt(options.ttl) * 1_000_000n : DEFAULT_TTL_NS
51
51
  const lifetime = options.lifetime ?? DEFAULT_LIFETIME_MS
52
- const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, { ...options, ttlNs })
53
- const marshaledRecord = marshalIPNSRecord(record)
52
+ const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, {
53
+ ...options,
54
+ // convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
55
+ ttlNs: options.ttl != null ? BigInt(options.ttl) * 1_000_000n : undefined
56
+ })
57
+ const marshaledRecord = IPNSEntry.encode(record)
54
58
 
55
59
  if (options.offline === true) {
56
60
  // only store record locally
57
- await this.localStore.put(routingKey, marshaledRecord, { ...options, metadata: { keyName, lifetime } })
61
+ await this.localStore.put(routingKey, marshaledRecord, {
62
+ ...options,
63
+ metadata: {
64
+ keyName,
65
+ lifetime
66
+ }
67
+ })
58
68
  } else {
59
69
  // publish record to routing (including the local store)
60
70
  await Promise.all(this.routers.map(async r => {
61
- await r.put(routingKey, marshaledRecord, { ...options, metadata: { keyName, lifetime } })
71
+ await r.put(routingKey, marshaledRecord, {
72
+ ...options,
73
+ metadata: {
74
+ keyName,
75
+ lifetime
76
+ }
77
+ })
62
78
  }))
63
79
  }
64
80
 
65
81
  return {
66
- record: {
67
- ...record,
68
- publicKey: key.publicKey
69
- },
82
+ record,
70
83
  name: `/ipns/${key.publicKey.toCID().toString(base36)}`,
71
84
  publicKey: key.publicKey
72
85
  }
@@ -1,9 +1,10 @@
1
1
  import { Queue, repeatingTask } from '@libp2p/utils'
2
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
2
3
  import { DEFAULT_REPUBLISH_CONCURRENCY, DEFAULT_REPUBLISH_INTERVAL_MS, DEFAULT_TTL_NS } from '../constants.ts'
4
+ import { IPNSEntry } from '../pb/ipns.ts'
3
5
  import { createIPNSRecord } from '../records.ts'
4
- import { marshalIPNSRecord, unmarshalIPNSRecord } from '../utils.ts'
5
- import { shouldRepublish } from '../utils.ts'
6
- import type { IPNSRecord } from '../index.ts'
6
+ import { decodeExtensibleData, shouldRepublish } from '../utils.ts'
7
+ import type { IPNSRecordData } from '../index.ts'
7
8
  import type { LocalStore } from '../local-store.ts'
8
9
  import type { IPNSRouting } from '../routing/index.ts'
9
10
  import type { Keychain, PrivateKey } from '@helia/interface'
@@ -78,7 +79,7 @@ export class IPNSRepublisher {
78
79
  })
79
80
 
80
81
  try {
81
- const recordsToRepublish: Array<{ routingKey: Uint8Array, record: IPNSRecord }> = []
82
+ const recordsToRepublish: Array<{ routingKey: Uint8Array, record: IPNSEntry }> = []
82
83
  let listed = 0
83
84
 
84
85
  // Find all records using the localStore.list method
@@ -91,21 +92,38 @@ export class IPNSRepublisher {
91
92
  this.log('no metadata found for record %b, skipping', routingKey)
92
93
  continue
93
94
  }
94
- let ipnsRecord: IPNSRecord
95
+
96
+ let ipnsRecord: IPNSEntry
97
+
95
98
  try {
96
- ipnsRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
97
- } catch (err: any) {
98
- this.log.error('error unmarshaling record - %e', err)
99
+ ipnsRecord = IPNSEntry.decode(record)
100
+ } catch (err) {
101
+ this.log.trace('skipping invalid record %b because could not decode', routingKey)
102
+ continue
103
+ }
104
+
105
+ if (ipnsRecord.data == null) {
106
+ this.log.trace('skipping record %b because data was missing', routingKey)
107
+ continue
108
+ }
109
+
110
+ let data: IPNSRecordData
111
+
112
+ try {
113
+ data = decodeExtensibleData(ipnsRecord.data)
114
+ } catch {
115
+ this.log.trace('skipping record %b because could not decode data', routingKey)
99
116
  continue
100
117
  }
101
118
 
102
119
  // Only republish records that are within the DHT or record expiry threshold
103
- if (!shouldRepublish(ipnsRecord, created)) {
120
+ if (!shouldRepublish(created, new Date(uint8ArrayToString(data.Validity)))) {
104
121
  this.log.trace('skipping record %b within republish threshold', routingKey)
105
122
  continue
106
123
  }
107
- const sequenceNumber = ipnsRecord.sequence + 1n
108
- const ttlNs = ipnsRecord.ttl ?? DEFAULT_TTL_NS
124
+
125
+ const sequenceNumber = data.Sequence + 1n
126
+ const ttlNs = data.TTL ?? DEFAULT_TTL_NS
109
127
  let privKey: PrivateKey
110
128
 
111
129
  try {
@@ -116,7 +134,7 @@ export class IPNSRepublisher {
116
134
  }
117
135
 
118
136
  try {
119
- const updatedRecord = await createIPNSRecord(privKey, ipnsRecord.value, sequenceNumber, metadata.lifetime, {
137
+ const updatedRecord = await createIPNSRecord(privKey, uint8ArrayToString(data.Value), sequenceNumber, metadata.lifetime, {
120
138
  ...options,
121
139
  ttlNs
122
140
  })
@@ -137,7 +155,7 @@ export class IPNSRepublisher {
137
155
  // Add job to queue to republish the record to all routers
138
156
  queue.add(async () => {
139
157
  try {
140
- const marshaledRecord = marshalIPNSRecord(record)
158
+ const marshaledRecord = IPNSEntry.encode(record)
141
159
  await Promise.all(
142
160
  this.routers.map(r => r.put(routingKey, marshaledRecord, options))
143
161
  )
@@ -1,9 +1,11 @@
1
+ import { base36 } from 'multiformats/bases/base36'
1
2
  import { DEFAULT_TTL_NS } from '../constants.ts'
2
- import { RecordNotFoundError, RecordsFailedValidationError } from '../errors.ts'
3
+ import { InvalidValueError, RecordNotFoundError, RecordsFailedValidationError } from '../errors.ts'
4
+ import { IPNSEntry } from '../pb/ipns.ts'
3
5
  import { ipnsSelector } from '../selector.ts'
4
- import { multihashToIPNSRoutingKey, unmarshalIPNSRecord, normalizeKey, IPNS_STRING_PREFIX } from '../utils.ts'
6
+ import { multihashToIPNSRoutingKey, normalizeKey, IPNS_STRING_PREFIX, ipnsRecordValueToString, decodeExtensibleData } from '../utils.ts'
5
7
  import { ipnsValidator } from '../validator.ts'
6
- import type { IPNSRecord, IPNSResolveOptions, IPNSResolveResult } from '../index.ts'
8
+ import type { IPNSResolveOptions, IPNSResolveResult } from '../index.ts'
7
9
  import type { LocalStore } from '../local-store.ts'
8
10
  import type { IPNSRouting } from '../routing/index.ts'
9
11
  import type { Routing, Keychain } from '@helia/interface'
@@ -42,22 +44,33 @@ export class IPNSResolver {
42
44
  while (true) {
43
45
  const routingKey = multihashToIPNSRoutingKey(digest)
44
46
  const record = await this.#findIpnsRecord(routingKey, options)
47
+ let value = ''
48
+
49
+ if (record.data != null) {
50
+ const data = decodeExtensibleData(record.data)
51
+ value = ipnsRecordValueToString(data.Value)
52
+ } else if (record.value != null) {
53
+ value = ipnsRecordValueToString(record.value)
54
+ } else {
55
+ throw new InvalidValueError(`Record for key ${base36.encode(digest.bytes)} contained no value`)
56
+ }
45
57
 
46
58
  yield {
47
- record
59
+ record,
60
+ value
48
61
  }
49
62
 
50
- if (!record.value.startsWith(IPNS_STRING_PREFIX)) {
63
+ if (!value.startsWith(IPNS_STRING_PREFIX)) {
51
64
  // not a recursive record
52
65
  break
53
66
  }
54
67
 
55
- ({ digest } = normalizeKey(record.value))
68
+ ({ digest } = normalizeKey(value))
56
69
  }
57
70
  }
58
71
 
59
- async #findIpnsRecord (routingKey: Uint8Array, options: IPNSResolveOptions = {}): Promise<IPNSRecord> {
60
- const records: IPNSRecord[] = []
72
+ async #findIpnsRecord (routingKey: Uint8Array, options: IPNSResolveOptions = {}): Promise<IPNSEntry> {
73
+ const records: IPNSEntry[] = []
61
74
  const cached = await this.localStore.has(routingKey, options)
62
75
 
63
76
  if (cached) {
@@ -70,19 +83,23 @@ export class IPNSResolver {
70
83
 
71
84
  this.log('record retrieved from cache')
72
85
 
73
- // unmarshal the record
74
- const ipnsRecord = await unmarshalIPNSRecord(routingKey, marshaledIPNSRecord, this.keychain, options)
86
+ // unmarshal and validate the record
87
+ let ipnsRecord: IPNSEntry
75
88
 
76
- // validate the record
77
- await ipnsValidator(ipnsRecord, options)
78
-
79
- this.log('record was valid')
89
+ if (options.validate === false) {
90
+ ipnsRecord = IPNSEntry.decode(marshaledIPNSRecord)
91
+ this.log('skipped validation of record')
92
+ } else {
93
+ ipnsRecord = await ipnsValidator(routingKey, marshaledIPNSRecord, this.keychain, options)
94
+ this.log('record was valid')
95
+ }
80
96
 
81
97
  // check the TTL
98
+ const data = decodeExtensibleData(ipnsRecord.data)
82
99
 
83
100
  // IPNS TTL is in nanoseconds, convert to milliseconds, default to one
84
101
  // hour
85
- const ttlMs = Number((ipnsRecord.ttl ?? DEFAULT_TTL_NS) / 1_000_000n)
102
+ const ttlMs = Number((data.TTL ?? DEFAULT_TTL_NS) / 1_000_000n)
86
103
  const ttlExpires = created.getTime() + ttlMs
87
104
 
88
105
  if (ttlExpires > Date.now()) {
@@ -138,12 +155,13 @@ export class IPNSResolver {
138
155
  }
139
156
 
140
157
  try {
141
- // unmarshal ensures that (1) SignatureV2 and Data are present, (2) that ValidityType
142
- // and Validity are of valid types and have a value, (3) that CBOR data matches protobuf
143
- // if it's a V1+V2 record
144
- const record = await unmarshalIPNSRecord(routingKey, marshaledIPNSRecord, this.keychain, options)
158
+ let record: IPNSEntry
145
159
 
146
- await ipnsValidator(record, options)
160
+ if (options.validate === false) {
161
+ record = IPNSEntry.decode(marshaledIPNSRecord)
162
+ } else {
163
+ record = await ipnsValidator(routingKey, marshaledIPNSRecord, this.keychain, options)
164
+ }
147
165
 
148
166
  records.push(record)
149
167
  } catch (err) {
@@ -164,7 +182,7 @@ export class IPNSResolver {
164
182
 
165
183
  const record = records[ipnsSelector(routingKey, records)]
166
184
 
167
- await this.localStore.put(routingKey, record.bytes, options)
185
+ await this.localStore.put(routingKey, IPNSEntry.encode(record), options)
168
186
 
169
187
  return record
170
188
  }
package/src/ipns.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import { CID } from 'multiformats/cid'
2
+ import { IPNSEntry } from './index.ts'
2
3
  import { IPNSPublisher } from './ipns/publisher.ts'
3
4
  import { IPNSRepublisher } from './ipns/republisher.ts'
4
5
  import { IPNSResolver } from './ipns/resolver.ts'
5
6
  import { localStore } from './local-store.ts'
6
- import { helia } from './routing/helia.ts'
7
- import { localStoreRouting } from './routing/local-store.ts'
7
+ import { heliaIPNSRouting } from './routing/helia.ts'
8
+ import { localStoreIPNSRouting } from './routing/local-store.ts'
8
9
  import { ipnsSelector } from './selector.ts'
9
- import { normalizeKey, normalizeValue, unmarshalIPNSRecord } from './utils.ts'
10
+ import { normalizeKey, normalizeValue } from './utils.ts'
10
11
  import { ipnsValidator } from './validator.ts'
11
12
  import type { IPNSComponents, IPNS as IPNSInterface, IPNSOptions, IPNSPublishResult, PublishOptions, IPNSResolveOptions, IPNSResolveResult } from './index.ts'
12
13
  import type { LocalStore } from './local-store.ts'
@@ -31,8 +32,8 @@ export class IPNS implements IPNSInterface, Startable {
31
32
  this.started = false
32
33
 
33
34
  this.routers = [
34
- localStoreRouting(this.localStore),
35
- helia(components.routing),
35
+ localStoreIPNSRouting(this.localStore),
36
+ heliaIPNSRouting(components.routing),
36
37
  ...(init.routers ?? [])
37
38
  ]
38
39
 
@@ -67,14 +68,13 @@ export class IPNS implements IPNSInterface, Startable {
67
68
  if (isKadDHT(service)) {
68
69
  // @ ts-expect-error https://github.com/libp2p/js-libp2p/pull/3506
69
70
  service.selectors.ipns = async (key: Uint8Array, values: Uint8Array[]): Promise<number> => {
70
- const records = await Promise.all(values.map(buf => unmarshalIPNSRecord(key, buf, this.components.keychain)))
71
+ const records = values.map(buf => IPNSEntry.decode(buf))
71
72
 
72
73
  return ipnsSelector(key, records)
73
74
  }
74
75
 
75
76
  service.validators.ipns = async (key: Uint8Array, value: Uint8Array): Promise<void> => {
76
- const record = await unmarshalIPNSRecord(key, value, this.components.keychain)
77
- await ipnsValidator(record)
77
+ await ipnsValidator(key, value, this.components.keychain)
78
78
  }
79
79
  }
80
80
  }
@@ -2,14 +2,15 @@ import { Record } from '@libp2p/kad-dht'
2
2
  import { CustomProgressEvent } from 'progress-events'
3
3
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
4
4
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
5
+ import { withArrayBuffer } from 'uint8arrays/with-array-buffer'
5
6
  import { IPNSPublishMetadata } from './pb/metadata.ts'
6
7
  import { dhtRoutingKey, DHT_RECORD_PREFIX, ipnsMetadataKey } from './utils.ts'
7
- import type { DatastoreProgressEvents, GetOptions, PutOptions } from './routing/index.ts'
8
+ import type { DatastoreProgressEvents, IPNSRoutingGetOptions, IPNSRoutingPutOptions } from './routing/index.ts'
8
9
  import type { AbortOptions, Logger } from '@libp2p/interface'
9
10
  import type { Datastore } from 'interface-datastore'
10
11
 
11
12
  export interface GetResult {
12
- record: Uint8Array
13
+ record: Uint8Array<ArrayBuffer>
13
14
  created: Date
14
15
  }
15
16
 
@@ -32,8 +33,8 @@ export interface LocalStore {
32
33
  * @param marshaledRecord - The marshaled IPNS record
33
34
  * @param options - options for the put operation including metadata
34
35
  */
35
- put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: PutOptions): Promise<void>
36
- get(routingKey: Uint8Array, options?: GetOptions): Promise<GetResult>
36
+ put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: IPNSRoutingPutOptions): Promise<void>
37
+ get(routingKey: Uint8Array, options?: IPNSRoutingGetOptions): Promise<GetResult>
37
38
  has(routingKey: Uint8Array, options?: AbortOptions): Promise<boolean>
38
39
  delete(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
39
40
  /**
@@ -50,7 +51,7 @@ export interface LocalStore {
50
51
  */
51
52
  export function localStore (datastore: Datastore, log: Logger): LocalStore {
52
53
  return {
53
- async put (routingKey: Uint8Array, marshalledRecord: Uint8Array, options: PutOptions = {}) {
54
+ async put (routingKey: Uint8Array, marshalledRecord: Uint8Array, options: IPNSRoutingPutOptions = {}) {
54
55
  try {
55
56
  const key = dhtRoutingKey(routingKey)
56
57
 
@@ -86,7 +87,7 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
86
87
  throw err
87
88
  }
88
89
  },
89
- async get (routingKey: Uint8Array, options: GetOptions = {}): Promise<GetResult> {
90
+ async get (routingKey: Uint8Array, options: IPNSRoutingGetOptions = {}): Promise<GetResult> {
90
91
  try {
91
92
  const key = dhtRoutingKey(routingKey)
92
93
 
@@ -97,7 +98,7 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
97
98
  const record = Record.deserialize(buf)
98
99
 
99
100
  return {
100
- record: record.value,
101
+ record: withArrayBuffer(record.value),
101
102
  created: record.timeReceived
102
103
  }
103
104
  } catch (err: any) {
package/src/pb/ipns.proto CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  syntax = "proto3";
4
4
 
5
- message IpnsEntry {
5
+ message IPNSEntry {
6
6
  enum ValidityType {
7
7
  // setting an EOL says "this record is valid until..."
8
8
  EOL = 0;