@helia/ipns 10.1.1 → 10.2.0-bbf05cb7
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 +40 -0
- package/dist/index.min.js +5 -5
- package/dist/index.min.js.map +4 -4
- package/dist/src/errors.d.ts +11 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +13 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +136 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +41 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +3 -3
- package/dist/src/ipns/publisher.d.ts.map +1 -1
- package/dist/src/ipns/publisher.js +17 -18
- package/dist/src/ipns/publisher.js.map +1 -1
- package/dist/src/ipns/republisher.d.ts +6 -1
- package/dist/src/ipns/republisher.d.ts.map +1 -1
- package/dist/src/ipns/republisher.js +157 -24
- package/dist/src/ipns/republisher.js.map +1 -1
- package/dist/src/ipns/resolver.d.ts +13 -1
- package/dist/src/ipns/resolver.d.ts.map +1 -1
- package/dist/src/ipns/resolver.js +40 -31
- package/dist/src/ipns/resolver.js.map +1 -1
- package/dist/src/ipns.d.ts +6 -3
- package/dist/src/ipns.d.ts.map +1 -1
- package/dist/src/ipns.js +12 -3
- package/dist/src/ipns.js.map +1 -1
- package/dist/src/local-store.d.ts +5 -0
- package/dist/src/local-store.d.ts.map +1 -1
- package/dist/src/local-store.js +68 -23
- package/dist/src/local-store.js.map +1 -1
- package/dist/src/pb/ipns.js +1 -1
- package/dist/src/pb/metadata.d.ts +14 -1
- package/dist/src/pb/metadata.d.ts.map +1 -1
- package/dist/src/pb/metadata.js +36 -3
- package/dist/src/pb/metadata.js.map +1 -1
- package/dist/src/routing/index.d.ts +13 -1
- package/dist/src/routing/index.d.ts.map +1 -1
- package/dist/src/routing/index.js +17 -0
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/utils.d.ts +5 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +18 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validator.d.ts +6 -0
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +24 -14
- package/dist/src/validator.js.map +1 -1
- package/package.json +4 -4
- package/src/errors.ts +18 -0
- package/src/index.ts +145 -7
- package/src/ipns/publisher.ts +22 -20
- package/src/ipns/republisher.ts +182 -26
- package/src/ipns/resolver.ts +54 -36
- package/src/ipns.ts +20 -6
- package/src/local-store.ts +77 -22
- package/src/pb/ipns.ts +1 -1
- package/src/pb/metadata.proto +10 -0
- package/src/pb/metadata.ts +45 -4
- package/src/routing/index.ts +30 -1
- package/src/utils.ts +19 -0
- package/src/validator.ts +30 -18
- package/dist/typedoc-urls.json +0 -68
package/src/ipns.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { localStore } from './local-store.ts'
|
|
|
7
7
|
import { heliaIPNSRouting } from './routing/helia.ts'
|
|
8
8
|
import { localStoreIPNSRouting } from './routing/local-store.ts'
|
|
9
9
|
import { ipnsSelector } from './selector.ts'
|
|
10
|
-
import { normalizeKey, normalizeValue } from './utils.ts'
|
|
10
|
+
import { normalizeKey, normalizeKeyName, normalizeValue } from './utils.ts'
|
|
11
11
|
import { ipnsValidator } from './validator.ts'
|
|
12
|
-
import type { IPNSComponents, IPNS as IPNSInterface, IPNSOptions, IPNSPublishResult, PublishOptions, IPNSResolveOptions, IPNSResolveResult } from './index.ts'
|
|
12
|
+
import type { IPNSComponents, IPNS as IPNSInterface, IPNSOptions, IPNSPublishResult, PublishOptions, RepublishOptions, RepublishResult, IPNSResolveOptions, IPNSResolveResult, UnpublishOptions } from './index.ts'
|
|
13
13
|
import type { LocalStore } from './local-store.ts'
|
|
14
14
|
import type { IPNSRouting } from './routing/index.ts'
|
|
15
15
|
import type { PublicKey } from '@helia/interface'
|
|
@@ -42,12 +42,12 @@ export class IPNS implements IPNSInterface, Startable {
|
|
|
42
42
|
routers: this.routers,
|
|
43
43
|
localStore: this.localStore
|
|
44
44
|
})
|
|
45
|
-
this.
|
|
45
|
+
this.resolver = new IPNSResolver(components, {
|
|
46
46
|
...init,
|
|
47
47
|
routers: this.routers,
|
|
48
48
|
localStore: this.localStore
|
|
49
49
|
})
|
|
50
|
-
this.
|
|
50
|
+
this.republisher = new IPNSRepublisher(components, {
|
|
51
51
|
...init,
|
|
52
52
|
routers: this.routers,
|
|
53
53
|
localStore: this.localStore
|
|
@@ -103,15 +103,29 @@ export class IPNS implements IPNSInterface, Startable {
|
|
|
103
103
|
return this.publisher.publish(keyName, normalizeValue(value), options)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
async * resolve (key:
|
|
106
|
+
async * resolve (key: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options: IPNSResolveOptions = {}): AsyncGenerator<IPNSResolveResult> {
|
|
107
107
|
const { digest } = normalizeKey(key)
|
|
108
108
|
|
|
109
109
|
yield * this.resolver.resolve(digest, options)
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
async unpublish (keyName: string, options?:
|
|
112
|
+
async unpublish (keyName: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options?: UnpublishOptions): Promise<void> {
|
|
113
|
+
keyName = normalizeKeyName(keyName)
|
|
114
|
+
|
|
113
115
|
return this.publisher.unpublish(keyName, options)
|
|
114
116
|
}
|
|
117
|
+
|
|
118
|
+
async republish (keyName: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options: RepublishOptions = {}): Promise<RepublishResult> {
|
|
119
|
+
const { digest } = normalizeKey(keyName)
|
|
120
|
+
|
|
121
|
+
return this.republisher.republish(digest, options)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async import (key: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, record: IPNSEntry | Uint8Array, options?: AbortOptions): Promise<void> {
|
|
125
|
+
const { digest } = normalizeKey(key)
|
|
126
|
+
|
|
127
|
+
return this.republisher.import(digest, record, options)
|
|
128
|
+
}
|
|
115
129
|
}
|
|
116
130
|
|
|
117
131
|
function isLibp2p (obj?: any): obj is Libp2p {
|
package/src/local-store.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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
5
|
import { withArrayBuffer } from 'uint8arrays/with-array-buffer'
|
|
6
|
-
import { IPNSPublishMetadata } from './pb/metadata.ts'
|
|
6
|
+
import { IPNSPublishMetadata, Upkeep } from './pb/metadata.ts'
|
|
7
7
|
import { dhtRoutingKey, DHT_RECORD_PREFIX, ipnsMetadataKey } from './utils.ts'
|
|
8
8
|
import type { DatastoreProgressEvents, IPNSRoutingGetOptions, IPNSRoutingPutOptions } from './routing/index.ts'
|
|
9
9
|
import type { AbortOptions, Logger } from '@libp2p/interface'
|
|
@@ -37,12 +37,67 @@ export interface LocalStore {
|
|
|
37
37
|
get(routingKey: Uint8Array, options?: IPNSRoutingGetOptions): Promise<GetResult>
|
|
38
38
|
has(routingKey: Uint8Array, options?: AbortOptions): Promise<boolean>
|
|
39
39
|
delete(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
|
|
40
|
+
/**
|
|
41
|
+
* Delete only the IPNS metadata for a record, leaving the record itself in
|
|
42
|
+
* place so it is still served but no longer automatically republished
|
|
43
|
+
*/
|
|
44
|
+
deleteMetadata(routingKey: Uint8Array, options?: AbortOptions): Promise<void>
|
|
40
45
|
/**
|
|
41
46
|
* List all IPNS records in the datastore
|
|
42
47
|
*/
|
|
43
48
|
list(options?: ListOptions): AsyncIterable<ListResult>
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Merge a (possibly partial) metadata update into any metadata already stored
|
|
53
|
+
* for the routing key, so fields that are not being changed are preserved.
|
|
54
|
+
*
|
|
55
|
+
* Throws if the existing metadata cannot be read (rather than silently
|
|
56
|
+
* overwriting it and dropping keyName/lifetime), or if the resulting policy
|
|
57
|
+
* needs a keyName it does not have.
|
|
58
|
+
*/
|
|
59
|
+
async function mergeMetadata (datastore: Datastore, routingKey: Uint8Array, incoming: Partial<IPNSPublishMetadata>, options?: AbortOptions): Promise<Partial<IPNSPublishMetadata>> {
|
|
60
|
+
let metadata = incoming
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// merge into any existing metadata so a partial update (e.g. republish
|
|
64
|
+
// setting only `upkeep`) preserves keyName/lifetime instead of wiping them
|
|
65
|
+
const existing = IPNSPublishMetadata.decode(await datastore.get(ipnsMetadataKey(routingKey), options))
|
|
66
|
+
metadata = { ...existing, ...incoming }
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
if (err.name !== 'NotFoundError') {
|
|
69
|
+
// the stored metadata is unreadable, surface it rather than silently
|
|
70
|
+
// overwriting and dropping keyName/lifetime
|
|
71
|
+
throw err
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// the reissue upkeep policy re-signs the record, which needs the key, so
|
|
76
|
+
// refuse to persist that policy without a keyName
|
|
77
|
+
if (metadata.upkeep === Upkeep.reissue && (metadata.keyName == null || metadata.keyName === '')) {
|
|
78
|
+
throw new Error('a keyName is required to reissue a record')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return metadata
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Read the stored upkeep metadata for a routing key, if any. A record with no
|
|
86
|
+
* metadata is normal (imported, or unpublished), so that is not an error; only
|
|
87
|
+
* an actual decode/corruption failure is logged.
|
|
88
|
+
*/
|
|
89
|
+
async function readMetadata (datastore: Datastore, routingKey: Uint8Array, log: Logger, options?: AbortOptions): Promise<IPNSPublishMetadata | undefined> {
|
|
90
|
+
try {
|
|
91
|
+
return IPNSPublishMetadata.decode(await datastore.get(ipnsMetadataKey(routingKey), options))
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
if (err.name !== 'NotFoundError') {
|
|
94
|
+
log.error('error deserializing metadata for %b - %e', routingKey, err)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
46
101
|
/**
|
|
47
102
|
* Read/write IPNS records to the datastore as DHT records.
|
|
48
103
|
*
|
|
@@ -55,18 +110,20 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
|
|
|
55
110
|
try {
|
|
56
111
|
const key = dhtRoutingKey(routingKey)
|
|
57
112
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
113
|
+
if (options.overwrite !== true) {
|
|
114
|
+
// don't overwrite existing, identical records as this will affect the
|
|
115
|
+
// TTL
|
|
116
|
+
try {
|
|
117
|
+
const existingBuf = await datastore.get(key)
|
|
118
|
+
const existingRecord = Record.deserialize(existingBuf)
|
|
63
119
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
120
|
+
if (uint8ArrayEquals(existingRecord.value, marshalledRecord)) {
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
} catch (err: any) {
|
|
124
|
+
if (err.name !== 'NotFoundError') {
|
|
125
|
+
throw err
|
|
126
|
+
}
|
|
70
127
|
}
|
|
71
128
|
}
|
|
72
129
|
|
|
@@ -78,8 +135,10 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
|
|
|
78
135
|
batch.put(key, record.serialize())
|
|
79
136
|
|
|
80
137
|
if (options.metadata != null) {
|
|
81
|
-
//
|
|
82
|
-
|
|
138
|
+
// merge into any existing metadata (preserving keyName/lifetime on a
|
|
139
|
+
// partial update) and validate the result before writing it
|
|
140
|
+
const metadata = await mergeMetadata(datastore, routingKey, options.metadata, options)
|
|
141
|
+
batch.put(ipnsMetadataKey(routingKey), IPNSPublishMetadata.encode(metadata))
|
|
83
142
|
}
|
|
84
143
|
await batch.commit(options)
|
|
85
144
|
} catch (err: any) {
|
|
@@ -117,6 +176,9 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
|
|
|
117
176
|
batch.delete(ipnsMetadataKey(routingKey))
|
|
118
177
|
await batch.commit(options)
|
|
119
178
|
},
|
|
179
|
+
async deleteMetadata (routingKey, options): Promise<void> {
|
|
180
|
+
await datastore.delete(ipnsMetadataKey(routingKey), options)
|
|
181
|
+
},
|
|
120
182
|
async * list (options: ListOptions = {}): AsyncIterable<ListResult> {
|
|
121
183
|
try {
|
|
122
184
|
options.onProgress?.(new CustomProgressEvent('ipns:routing:datastore:list'))
|
|
@@ -134,14 +196,7 @@ export function localStore (datastore: Datastore, log: Logger): LocalStore {
|
|
|
134
196
|
const routingKeyBase32 = keyString.substring(DHT_RECORD_PREFIX.length)
|
|
135
197
|
const routingKey = uint8ArrayFromString(routingKeyBase32, 'base32')
|
|
136
198
|
|
|
137
|
-
const
|
|
138
|
-
let metadata: IPNSPublishMetadata | undefined
|
|
139
|
-
try {
|
|
140
|
-
const metadataBuf = await datastore.get(metadataKey, options)
|
|
141
|
-
metadata = IPNSPublishMetadata.decode(metadataBuf)
|
|
142
|
-
} catch (err: any) {
|
|
143
|
-
log.error('Error deserializing metadata for %s - %e', routingKeyBase32, err)
|
|
144
|
-
}
|
|
199
|
+
const metadata = await readMetadata(datastore, routingKey, log, options)
|
|
145
200
|
|
|
146
201
|
yield {
|
|
147
202
|
routingKey,
|
package/src/pb/ipns.ts
CHANGED
|
@@ -139,7 +139,7 @@ export namespace IPNSEntry {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
return obj
|
|
142
|
-
}, function * (reader,
|
|
142
|
+
}, function * (reader, length, prefix, opts = {}) {
|
|
143
143
|
const end = length == null ? reader.len : reader.pos + length
|
|
144
144
|
|
|
145
145
|
if (prefix !== '.') {
|
package/src/pb/metadata.proto
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
syntax = "proto3";
|
|
2
2
|
|
|
3
|
+
enum Upkeep {
|
|
4
|
+
reissue = 0; // Re-sign the record with a new validity (key must be in keychain)
|
|
5
|
+
rebroadcast = 1; // Re-publish the record as is (while it is still valid)
|
|
6
|
+
none = 2; // Disables automated republishing for the record
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
message IPNSPublishMetadata {
|
|
4
10
|
// The name of the key that was used to publish the record
|
|
5
11
|
string keyName = 1;
|
|
6
12
|
|
|
7
13
|
// Lifetime is the duration of the signature validity in milliseconds
|
|
14
|
+
// Relevant for Upkeep.reissue
|
|
8
15
|
uint32 lifetime = 2;
|
|
16
|
+
|
|
17
|
+
// Republishing policy
|
|
18
|
+
Upkeep upkeep = 3;
|
|
9
19
|
}
|
package/src/pb/metadata.ts
CHANGED
|
@@ -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
|
|
@@ -58,7 +87,7 @@ export namespace IPNSPublishMetadata {
|
|
|
58
87
|
}
|
|
59
88
|
|
|
60
89
|
return obj
|
|
61
|
-
}, function * (reader,
|
|
90
|
+
}, function * (reader, length, prefix, opts = {}) {
|
|
62
91
|
const end = length == null ? reader.len : reader.pos + length
|
|
63
92
|
|
|
64
93
|
if (prefix !== '.') {
|
|
@@ -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
|
}
|
package/src/routing/index.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
package/dist/typedoc-urls.json
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"codec": "https://ipfs.github.io/helia/functions/_helia_ipns.IPNSEntry.ValidityType.codec.html",
|
|
3
|
-
"ValidityType": "https://ipfs.github.io/helia/enums/_helia_ipns.IPNSEntry.ValidityType.html",
|
|
4
|
-
"IPNSEntryDataFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryDataFieldEvent.html",
|
|
5
|
-
"IPNSEntryPublicKeyFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryPublicKeyFieldEvent.html",
|
|
6
|
-
"IPNSEntrySequenceFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntrySequenceFieldEvent.html",
|
|
7
|
-
"IPNSEntrySignatureV1FieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntrySignatureV1FieldEvent.html",
|
|
8
|
-
"IPNSEntrySignatureV2FieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntrySignatureV2FieldEvent.html",
|
|
9
|
-
"IPNSEntryTtlFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryTtlFieldEvent.html",
|
|
10
|
-
"IPNSEntryValidityFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryValidityFieldEvent.html",
|
|
11
|
-
"IPNSEntryValidityTypeFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryValidityTypeFieldEvent.html",
|
|
12
|
-
"IPNSEntryValueFieldEvent": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.IPNSEntryValueFieldEvent.html",
|
|
13
|
-
"decode": "https://ipfs.github.io/helia/functions/_helia_ipns.IPNSEntry.decode.html",
|
|
14
|
-
"encode": "https://ipfs.github.io/helia/functions/_helia_ipns.IPNSEntry.encode.html",
|
|
15
|
-
"stream": "https://ipfs.github.io/helia/functions/_helia_ipns.IPNSEntry.stream.html",
|
|
16
|
-
"CreateIPNSRecordOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.CreateIPNSRecordOptions.html",
|
|
17
|
-
"IPNS": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNS.html",
|
|
18
|
-
".:IPNS": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNS.html",
|
|
19
|
-
"IPNSComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSComponents.html",
|
|
20
|
-
".:IPNSComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSComponents.html",
|
|
21
|
-
"IPNSEntry": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSEntry.html",
|
|
22
|
-
"IPNSOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSOptions.html",
|
|
23
|
-
".:IPNSOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSOptions.html",
|
|
24
|
-
"IPNSPublishResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSPublishResult.html",
|
|
25
|
-
".:IPNSPublishResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSPublishResult.html",
|
|
26
|
-
"IPNSRecordData": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSRecordData.html",
|
|
27
|
-
".:IPNSRecordData": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSRecordData.html",
|
|
28
|
-
"IPNSResolveOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolveOptions.html",
|
|
29
|
-
".:IPNSResolveOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolveOptions.html",
|
|
30
|
-
"IPNSResolver": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolver.html",
|
|
31
|
-
".:IPNSResolver": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolver.html",
|
|
32
|
-
"IPNSResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolveResult.html",
|
|
33
|
-
".:IPNSResolveResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolveResult.html",
|
|
34
|
-
"IPNSResolverOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolverOptions.html",
|
|
35
|
-
".:IPNSResolverOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSResolverOptions.html",
|
|
36
|
-
"IPNSRouting": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSRouting.html",
|
|
37
|
-
"IPNSRoutingGetOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSRoutingGetOptions.html",
|
|
38
|
-
"IPNSRoutingPutOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.IPNSRoutingPutOptions.html",
|
|
39
|
-
"PublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PublishOptions.html",
|
|
40
|
-
".:PublishOptions": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PublishOptions.html",
|
|
41
|
-
"PublishResult": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PublishResult.html",
|
|
42
|
-
"PubSub": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubSub.html",
|
|
43
|
-
"PubSubEvents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubSubEvents.html",
|
|
44
|
-
"PubSubMessage": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubSubMessage.html",
|
|
45
|
-
"PubsubRoutingComponents": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubsubRoutingComponents.html",
|
|
46
|
-
"PubSubSubscription": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubSubSubscription.html",
|
|
47
|
-
"PubSubSubscriptionChangeData": "https://ipfs.github.io/helia/interfaces/_helia_ipns.PubSubSubscriptionChangeData.html",
|
|
48
|
-
"DatastoreProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.DatastoreProgressEvents.html",
|
|
49
|
-
".:DatastoreProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.DatastoreProgressEvents.html",
|
|
50
|
-
"HeliaRoutingProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.HeliaRoutingProgressEvents.html",
|
|
51
|
-
"IPNSRoutingProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.IPNSRoutingProgressEvents.html",
|
|
52
|
-
"PublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.PublishProgressEvents.html",
|
|
53
|
-
".:PublishProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.PublishProgressEvents.html",
|
|
54
|
-
"PubSubProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.PubSubProgressEvents.html",
|
|
55
|
-
"ResolveProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.ResolveProgressEvents.html",
|
|
56
|
-
".:ResolveProgressEvents": "https://ipfs.github.io/helia/types/_helia_ipns.ResolveProgressEvents.html",
|
|
57
|
-
"createIPNSRecord": "https://ipfs.github.io/helia/functions/_helia_ipns.createIPNSRecord.html",
|
|
58
|
-
"heliaIPNSRouting": "https://ipfs.github.io/helia/functions/_helia_ipns.heliaIPNSRouting.html",
|
|
59
|
-
"ipns": "https://ipfs.github.io/helia/functions/_helia_ipns.ipns.html",
|
|
60
|
-
".:ipns": "https://ipfs.github.io/helia/functions/_helia_ipns.ipns.html",
|
|
61
|
-
"ipnsResolver": "https://ipfs.github.io/helia/functions/_helia_ipns.ipnsResolver.html",
|
|
62
|
-
".:ipnsResolver": "https://ipfs.github.io/helia/functions/_helia_ipns.ipnsResolver.html",
|
|
63
|
-
"ipnsSelector": "https://ipfs.github.io/helia/functions/_helia_ipns.ipnsSelector.html",
|
|
64
|
-
"ipnsValidator": "https://ipfs.github.io/helia/functions/_helia_ipns.ipnsValidator.html",
|
|
65
|
-
"multihashFromIPNSRoutingKey": "https://ipfs.github.io/helia/functions/_helia_ipns.multihashFromIPNSRoutingKey.html",
|
|
66
|
-
"multihashToIPNSRoutingKey": "https://ipfs.github.io/helia/functions/_helia_ipns.multihashToIPNSRoutingKey.html",
|
|
67
|
-
"pubSubIPNSRouting": "https://ipfs.github.io/helia/functions/_helia_ipns.pubSubIPNSRouting.html"
|
|
68
|
-
}
|