@helia/ipns 10.1.1-b34bc7c7 → 10.2.0

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 (61) hide show
  1. package/README.md +40 -0
  2. package/dist/index.min.js +5 -5
  3. package/dist/index.min.js.map +4 -4
  4. package/dist/src/errors.d.ts +11 -0
  5. package/dist/src/errors.d.ts.map +1 -1
  6. package/dist/src/errors.js +13 -0
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/index.d.ts +136 -7
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +41 -0
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/ipns/publisher.d.ts +3 -3
  13. package/dist/src/ipns/publisher.d.ts.map +1 -1
  14. package/dist/src/ipns/publisher.js +17 -18
  15. package/dist/src/ipns/publisher.js.map +1 -1
  16. package/dist/src/ipns/republisher.d.ts +6 -1
  17. package/dist/src/ipns/republisher.d.ts.map +1 -1
  18. package/dist/src/ipns/republisher.js +157 -24
  19. package/dist/src/ipns/republisher.js.map +1 -1
  20. package/dist/src/ipns/resolver.d.ts +13 -1
  21. package/dist/src/ipns/resolver.d.ts.map +1 -1
  22. package/dist/src/ipns/resolver.js +40 -31
  23. package/dist/src/ipns/resolver.js.map +1 -1
  24. package/dist/src/ipns.d.ts +6 -3
  25. package/dist/src/ipns.d.ts.map +1 -1
  26. package/dist/src/ipns.js +12 -3
  27. package/dist/src/ipns.js.map +1 -1
  28. package/dist/src/local-store.d.ts +5 -0
  29. package/dist/src/local-store.d.ts.map +1 -1
  30. package/dist/src/local-store.js +68 -23
  31. package/dist/src/local-store.js.map +1 -1
  32. package/dist/src/pb/metadata.d.ts +14 -1
  33. package/dist/src/pb/metadata.d.ts.map +1 -1
  34. package/dist/src/pb/metadata.js +35 -2
  35. package/dist/src/pb/metadata.js.map +1 -1
  36. package/dist/src/routing/index.d.ts +13 -1
  37. package/dist/src/routing/index.d.ts.map +1 -1
  38. package/dist/src/routing/index.js +17 -0
  39. package/dist/src/routing/index.js.map +1 -1
  40. package/dist/src/utils.d.ts +5 -0
  41. package/dist/src/utils.d.ts.map +1 -1
  42. package/dist/src/utils.js +18 -0
  43. package/dist/src/utils.js.map +1 -1
  44. package/dist/src/validator.d.ts +6 -0
  45. package/dist/src/validator.d.ts.map +1 -1
  46. package/dist/src/validator.js +24 -14
  47. package/dist/src/validator.js.map +1 -1
  48. package/dist/typedoc-urls.json +90 -0
  49. package/package.json +2 -2
  50. package/src/errors.ts +18 -0
  51. package/src/index.ts +145 -7
  52. package/src/ipns/publisher.ts +22 -20
  53. package/src/ipns/republisher.ts +182 -26
  54. package/src/ipns/resolver.ts +54 -36
  55. package/src/ipns.ts +20 -6
  56. package/src/local-store.ts +77 -22
  57. package/src/pb/metadata.proto +10 -0
  58. package/src/pb/metadata.ts +44 -3
  59. package/src/routing/index.ts +30 -1
  60. package/src/utils.ts +19 -0
  61. package/src/validator.ts +30 -18
@@ -1,10 +1,29 @@
1
- import { decodeMessage, encodeMessage, message, streamMessage } from 'protons-runtime'
1
+ import { decodeMessage, encodeMessage, enumeration, message, streamMessage } from 'protons-runtime'
2
2
  import type { Codec, DecodeOptions } from 'protons-runtime'
3
3
  import type { Uint8ArrayList } from 'uint8arraylist'
4
4
 
5
+ export enum Upkeep {
6
+ reissue = 'reissue',
7
+ rebroadcast = 'rebroadcast',
8
+ none = 'none'
9
+ }
10
+
11
+ enum __UpkeepValues {
12
+ reissue = 0,
13
+ rebroadcast = 1,
14
+ none = 2
15
+ }
16
+
17
+ export namespace Upkeep {
18
+ export const codec = (): Codec<Upkeep> => {
19
+ return enumeration<Upkeep>(__UpkeepValues)
20
+ }
21
+ }
22
+
5
23
  export interface IPNSPublishMetadata {
6
24
  keyName: string
7
25
  lifetime: number
26
+ upkeep: Upkeep
8
27
  }
9
28
 
10
29
  export namespace IPNSPublishMetadata {
@@ -27,13 +46,19 @@ export namespace IPNSPublishMetadata {
27
46
  w.uint32(obj.lifetime)
28
47
  }
29
48
 
49
+ if (obj.upkeep != null && __UpkeepValues[obj.upkeep] !== 0) {
50
+ w.uint32(24)
51
+ Upkeep.codec().encode(obj.upkeep, w)
52
+ }
53
+
30
54
  if (opts.lengthDelimited !== false) {
31
55
  w.ldelim()
32
56
  }
33
57
  }, (reader, length, opts = {}) => {
34
58
  const obj: any = {
35
59
  keyName: '',
36
- lifetime: 0
60
+ lifetime: 0,
61
+ upkeep: Upkeep.reissue
37
62
  }
38
63
 
39
64
  const end = length == null ? reader.len : reader.pos + length
@@ -50,6 +75,10 @@ export namespace IPNSPublishMetadata {
50
75
  obj.lifetime = reader.uint32()
51
76
  break
52
77
  }
78
+ case 3: {
79
+ obj.upkeep = Upkeep.codec().decode(reader)
80
+ break
81
+ }
53
82
  default: {
54
83
  reader.skipType(tag & 7)
55
84
  break
@@ -87,6 +116,13 @@ export namespace IPNSPublishMetadata {
87
116
  }
88
117
  break
89
118
  }
119
+ case 3: {
120
+ yield {
121
+ field: `${prefix}upkeep`,
122
+ value: Upkeep.codec().decode(reader)
123
+ }
124
+ break
125
+ }
90
126
  default: {
91
127
  reader.skipType(tag & 7)
92
128
  break
@@ -117,6 +153,11 @@ export namespace IPNSPublishMetadata {
117
153
  value: number
118
154
  }
119
155
 
156
+ export interface IPNSPublishMetadataUpkeepFieldEvent {
157
+ field: '.upkeep'
158
+ value: Upkeep
159
+ }
160
+
120
161
  export function encode (obj: Partial<IPNSPublishMetadata>): Uint8Array<ArrayBuffer> {
121
162
  return encodeMessage(obj, IPNSPublishMetadata.codec())
122
163
  }
@@ -125,7 +166,7 @@ export namespace IPNSPublishMetadata {
125
166
  return decodeMessage(buf, IPNSPublishMetadata.codec(), opts)
126
167
  }
127
168
 
128
- export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<IPNSPublishMetadata>): Generator<IPNSPublishMetadataKeyNameFieldEvent | IPNSPublishMetadataLifetimeFieldEvent> {
169
+ export function stream (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<IPNSPublishMetadata>): Generator<IPNSPublishMetadataKeyNameFieldEvent | IPNSPublishMetadataLifetimeFieldEvent | IPNSPublishMetadataUpkeepFieldEvent> {
129
170
  return streamMessage(buf, IPNSPublishMetadata.codec(), opts)
130
171
  }
131
172
  }
@@ -6,7 +6,14 @@ import type { AbortOptions } from '@libp2p/interface'
6
6
  import type { ProgressOptions } from 'progress-events'
7
7
 
8
8
  export interface IPNSRoutingPutOptions extends AbortOptions, ProgressOptions {
9
- metadata?: IPNSPublishMetadata
9
+ metadata?: Partial<IPNSPublishMetadata>
10
+
11
+ /**
12
+ * Overwrite an identical stored record instead of keeping the existing one,
13
+ * re-stamping its local timestamp. Passed on republish/rebroadcast so the
14
+ * record is not re-selected for republishing every cycle.
15
+ */
16
+ overwrite?: boolean
10
17
  }
11
18
 
12
19
  export interface IPNSRoutingGetOptions extends AbortOptions, ProgressOptions {
@@ -23,6 +30,28 @@ export interface IPNSRouting {
23
30
  get(routingKey: Uint8Array, options?: IPNSRoutingGetOptions): Promise<Uint8Array>
24
31
  }
25
32
 
33
+ /**
34
+ * Return a printable name for a router, falling back to `CustomRouting()` when
35
+ * the router does not set its own `toString`.
36
+ */
37
+ export function routerName (router: IPNSRouting): string {
38
+ if (router.toString === Object.prototype.toString) {
39
+ return 'CustomRouting()'
40
+ }
41
+
42
+ const name = router.toString()
43
+
44
+ if (typeof name !== 'string' || name === '') {
45
+ return 'CustomRouting()'
46
+ }
47
+
48
+ return name
49
+ }
50
+
51
+ export function isLocalStoreRouting (router: IPNSRouting): boolean {
52
+ return String(router) === 'LocalStoreRouting()'
53
+ }
54
+
26
55
  export type { DatastoreProgressEvents }
27
56
  export type { HeliaRoutingProgressEvents }
28
57
  export type { PubSubProgressEvents }
package/src/utils.ts CHANGED
@@ -281,6 +281,25 @@ export function normalizeKey (key?: PublicKey | CID | MultihashDigest | string):
281
281
  throw new InvalidValueError('Value must be a valid IPNS path starting with /')
282
282
  }
283
283
 
284
+ /**
285
+ * Normalize a key identifier for unpublish: CID/PublicKey/multihash become a
286
+ * MultihashDigest, a keychain key name stays a string.
287
+ */
288
+ export function normalizeKeyName (keyName: CID<unknown, 0x72> | PublicKey | MultihashDigest | string): MultihashDigest | string {
289
+ // a CID/PublicKey/MultihashDigest is always an IPNS name, let it throw if invalid
290
+ if (typeof keyName !== 'string') {
291
+ return normalizeKey(keyName).digest
292
+ }
293
+
294
+ try {
295
+ // a string may be an IPNS name...
296
+ return normalizeKey(keyName).digest
297
+ } catch {
298
+ // ...otherwise treat it as a keychain key name
299
+ return keyName
300
+ }
301
+ }
302
+
284
303
  export function validateCborDataMatchesPbData (entry: IPNSEntry, data: IPNSRecordData): void {
285
304
  if (!uint8ArrayEquals(data.Value, entry.value ?? new Uint8Array(0))) {
286
305
  throw new SignatureVerificationError('Field "value" did not match between protobuf and CBOR')
package/src/validator.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import NanoDate from 'timestamp-nano'
2
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
2
3
  import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
3
4
  import { InvalidEmbeddedPublicKeyError, RecordExpiredError, RecordTooLargeError, SignatureVerificationError, UnsupportedValidityError } from './errors.ts'
4
5
  import { IPNSEntry } from './pb/ipns.ts'
@@ -40,24 +41,7 @@ export async function ipnsValidator (routingKey: Uint8Array, marshalledRecord: U
40
41
  const data = decodeExtensibleData(record.data)
41
42
  const validity = uint8ArrayToString(data.Validity)
42
43
 
43
- let publicKey: PublicKey | undefined
44
-
45
- // try to extract public key from routing key
46
- const routingMultihash = multihashFromIPNSRoutingKey(routingKey)
47
-
48
- // identity hash
49
- if (isCodec(routingMultihash, 0x0)) {
50
- publicKey = await keychain.loadPublicKeyFromProtobuf(routingMultihash.digest, options)
51
- }
52
-
53
- // otherwise try to load key from message
54
- if (publicKey == null && record.publicKey != null) {
55
- publicKey = await keychain.loadPublicKeyFromProtobuf(record.publicKey, options)
56
- }
57
-
58
- if (publicKey == null) {
59
- throw new InvalidEmbeddedPublicKeyError('Could not extract public key from IPNS record or routing key')
60
- }
44
+ const publicKey = await extractPublicKey(routingKey, record, keychain, options)
61
45
 
62
46
  // Validate Signature V2
63
47
  let isValid
@@ -95,6 +79,34 @@ export async function ipnsValidator (routingKey: Uint8Array, marshalledRecord: U
95
79
  return record
96
80
  }
97
81
 
82
+ /**
83
+ * Extract the public key that signed an IPNS record, either from an
84
+ * identity-hashed routing key or from the key embedded in the record.
85
+ */
86
+ export async function extractPublicKey (routingKey: Uint8Array, record: IPNSEntry, keychain: Keychain, options?: AbortOptions): Promise<PublicKey> {
87
+ const routingMultihash = multihashFromIPNSRoutingKey(routingKey)
88
+
89
+ // identity hash, the routing key is the public key
90
+ if (isCodec(routingMultihash, 0x0)) {
91
+ return keychain.loadPublicKeyFromProtobuf(routingMultihash.digest, options)
92
+ }
93
+
94
+ // otherwise the record must embed the public key, and that key must correspond
95
+ // to the routing key, otherwise the record was paired with a routing key it was
96
+ // not signed for
97
+ if (record.publicKey != null) {
98
+ const publicKey = await keychain.loadPublicKeyFromProtobuf(record.publicKey, options)
99
+
100
+ if (!uint8ArrayEquals(publicKey.toMultihash().bytes, routingMultihash.bytes)) {
101
+ throw new InvalidEmbeddedPublicKeyError('Embedded public key does not match the routing key')
102
+ }
103
+
104
+ return publicKey
105
+ }
106
+
107
+ throw new InvalidEmbeddedPublicKeyError('Could not extract public key from IPNS record or routing key')
108
+ }
109
+
98
110
  /**
99
111
  * Returns the number of milliseconds until the record expires.
100
112
  * If the record is already expired, returns 0.