@helia/ipns 10.0.0 → 10.0.1-883ff053
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 +3 -7
- package/dist/index.min.js +16 -5
- package/dist/index.min.js.map +4 -4
- package/dist/src/errors.d.ts +0 -8
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +0 -8
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +68 -31
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +11 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +2 -2
- package/dist/src/ipns/publisher.d.ts.map +1 -1
- package/dist/src/ipns/publisher.js +27 -14
- package/dist/src/ipns/publisher.js.map +1 -1
- package/dist/src/ipns/republisher.d.ts.map +1 -1
- package/dist/src/ipns/republisher.js +22 -9
- package/dist/src/ipns/republisher.js.map +1 -1
- package/dist/src/ipns/resolver.d.ts +2 -2
- package/dist/src/ipns/resolver.d.ts.map +1 -1
- package/dist/src/ipns/resolver.js +39 -17
- package/dist/src/ipns/resolver.js.map +1 -1
- package/dist/src/ipns.d.ts +3 -3
- package/dist/src/ipns.d.ts.map +1 -1
- package/dist/src/ipns.js +8 -8
- package/dist/src/ipns.js.map +1 -1
- package/dist/src/local-store.d.ts +4 -4
- package/dist/src/local-store.d.ts.map +1 -1
- package/dist/src/local-store.js +2 -1
- package/dist/src/local-store.js.map +1 -1
- package/dist/src/pb/ipns.d.ts +29 -29
- package/dist/src/pb/ipns.d.ts.map +1 -1
- package/dist/src/pb/ipns.js +15 -15
- package/dist/src/records.d.ts +27 -142
- package/dist/src/records.d.ts.map +1 -1
- package/dist/src/records.js +39 -46
- package/dist/src/records.js.map +1 -1
- package/dist/src/routing/helia.d.ts +5 -5
- package/dist/src/routing/helia.d.ts.map +1 -1
- package/dist/src/routing/helia.js +3 -3
- package/dist/src/routing/helia.js.map +1 -1
- package/dist/src/routing/index.d.ts +7 -7
- package/dist/src/routing/index.d.ts.map +1 -1
- package/dist/src/routing/index.js +2 -2
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/routing/local-store.d.ts +1 -1
- package/dist/src/routing/local-store.d.ts.map +1 -1
- package/dist/src/routing/local-store.js +3 -3
- package/dist/src/routing/local-store.js.map +1 -1
- package/dist/src/routing/pubsub.d.ts +11 -11
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +11 -12
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/selector.d.ts +2 -2
- package/dist/src/selector.d.ts.map +1 -1
- package/dist/src/selector.js +13 -9
- package/dist/src/selector.js.map +1 -1
- package/dist/src/utils.d.ts +10 -11
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +20 -128
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validator.d.ts +9 -3
- package/dist/src/validator.d.ts.map +1 -1
- package/dist/src/validator.js +57 -14
- package/dist/src/validator.js.map +1 -1
- package/package.json +6 -31
- package/src/errors.ts +0 -10
- package/src/index.ts +85 -36
- package/src/ipns/publisher.ts +29 -16
- package/src/ipns/republisher.ts +31 -13
- package/src/ipns/resolver.ts +40 -22
- package/src/ipns.ts +11 -11
- package/src/local-store.ts +8 -7
- package/src/pb/ipns.proto +1 -1
- package/src/pb/ipns.ts +37 -37
- package/src/records.ts +57 -210
- package/src/routing/helia.ts +6 -6
- package/src/routing/index.ts +7 -7
- package/src/routing/local-store.ts +6 -6
- package/src/routing/pubsub.ts +24 -25
- package/src/selector.ts +14 -11
- package/src/utils.ts +26 -148
- package/src/validator.ts +73 -16
- package/dist/typedoc-urls.json +0 -49
package/src/records.ts
CHANGED
|
@@ -2,259 +2,106 @@ import { logger } from '@libp2p/logger'
|
|
|
2
2
|
import NanoDate from 'timestamp-nano'
|
|
3
3
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
4
4
|
import { SignatureCreationError } from './errors.ts'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { IPNSEntry } from './pb/ipns.ts'
|
|
6
|
+
import { encodeExtensibleData, IDENTITY_CODEC, ipnsRecordDataForV1Sig, ipnsRecordDataForV2Sig } from './utils.ts'
|
|
7
7
|
import type { PrivateKey, PublicKey } from '@helia/interface'
|
|
8
8
|
import type { AbortOptions } from 'abort-error'
|
|
9
|
-
import type { Key } from 'interface-datastore/key'
|
|
10
9
|
|
|
11
10
|
const log = logger('ipns')
|
|
12
|
-
const DEFAULT_TTL_NS =
|
|
13
|
-
|
|
14
|
-
export const namespace = '/ipns/'
|
|
15
|
-
export const namespaceLength = namespace.length
|
|
16
|
-
|
|
17
|
-
export interface IPNSRecordV1V2 {
|
|
18
|
-
/**
|
|
19
|
-
* value of the record
|
|
20
|
-
*/
|
|
21
|
-
value: string
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* signature of the record
|
|
25
|
-
*/
|
|
26
|
-
signatureV1: Uint8Array
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Type of validation being used
|
|
30
|
-
*/
|
|
31
|
-
validityType: IpnsEntry.ValidityType
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* expiration datetime for the record in RFC3339 format
|
|
35
|
-
*/
|
|
36
|
-
validity: string
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* number representing the version of the record
|
|
40
|
-
*/
|
|
41
|
-
sequence: bigint
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* ttl in nanoseconds
|
|
45
|
-
*/
|
|
46
|
-
ttl?: bigint
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* the public portion of the key that signed this record
|
|
50
|
-
*/
|
|
51
|
-
publicKey: PublicKey
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* the v2 signature of the record
|
|
55
|
-
*/
|
|
56
|
-
signatureV2: Uint8Array
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* extensible data
|
|
60
|
-
*/
|
|
61
|
-
data: Uint8Array
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The marshalled record
|
|
65
|
-
*/
|
|
66
|
-
bytes: Uint8Array
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface IPNSRecordV2 {
|
|
70
|
-
/**
|
|
71
|
-
* value of the record
|
|
72
|
-
*/
|
|
73
|
-
value: string
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* the v2 signature of the record
|
|
77
|
-
*/
|
|
78
|
-
signatureV2: Uint8Array
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Type of validation being used
|
|
82
|
-
*/
|
|
83
|
-
validityType: IpnsEntry.ValidityType
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* If the validity type is EOL, this is the expiration datetime for the record
|
|
87
|
-
* in RFC3339 format
|
|
88
|
-
*/
|
|
89
|
-
validity: string
|
|
11
|
+
const DEFAULT_TTL_NS = 300_000_000_000n // 5 Minutes or 300 Seconds, as suggested by https://specs.ipfs.tech/ipns/ipns-record/#ttl-uint64
|
|
90
12
|
|
|
13
|
+
export interface CreateIPNSRecordOptions extends AbortOptions {
|
|
91
14
|
/**
|
|
92
|
-
*
|
|
15
|
+
* By default a IPNS V1 and a V2 signature is added to every record. Pass
|
|
16
|
+
* false here to only add a V2 signature.
|
|
17
|
+
*
|
|
18
|
+
* @default true
|
|
93
19
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* ttl in nanoseconds
|
|
98
|
-
*/
|
|
99
|
-
ttl?: bigint
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* the public portion of the key that signed this record
|
|
103
|
-
*/
|
|
104
|
-
publicKey: PublicKey
|
|
20
|
+
v1Compatible?: boolean
|
|
105
21
|
|
|
106
22
|
/**
|
|
107
|
-
*
|
|
23
|
+
* The TTL of the record in ms - after this many ms have expired, resolving
|
|
24
|
+
* the record will query the routing for an updated version.
|
|
25
|
+
*
|
|
26
|
+
* Before this many ms have expired any locally stored copy will be treated as
|
|
27
|
+
* the latest version and the routing will not be queried.
|
|
28
|
+
*
|
|
29
|
+
* @default 300_000_000_000n
|
|
108
30
|
*/
|
|
109
|
-
|
|
31
|
+
ttlNs?: bigint
|
|
110
32
|
|
|
111
33
|
/**
|
|
112
|
-
*
|
|
34
|
+
* Extensible data that will be added to the IPNS record data and signed to
|
|
35
|
+
* verify it's integrity.
|
|
36
|
+
*
|
|
37
|
+
* Note that this data will be encoded as DAG-CBOR so it must be valid.
|
|
113
38
|
*/
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export type IPNSRecord = IPNSRecordV1V2 | IPNSRecordV2
|
|
118
|
-
|
|
119
|
-
export interface IPNSRecordData {
|
|
120
|
-
Value: Uint8Array
|
|
121
|
-
Validity: Uint8Array
|
|
122
|
-
ValidityType: IpnsEntry.ValidityType
|
|
123
|
-
Sequence: bigint
|
|
124
|
-
TTL: bigint
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export interface IDKeys {
|
|
128
|
-
routingPubKey: Key
|
|
129
|
-
pkKey: Key
|
|
130
|
-
routingKey: Key
|
|
131
|
-
ipnsKey: Key
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export interface CreateOptions extends AbortOptions {
|
|
135
|
-
ttlNs?: number | bigint
|
|
136
|
-
v1Compatible?: boolean
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export interface CreateV2OrV1Options extends AbortOptions {
|
|
140
|
-
v1Compatible: true
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export interface CreateV2Options extends AbortOptions {
|
|
144
|
-
v1Compatible: false
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const defaultCreateOptions: CreateOptions = {
|
|
148
|
-
v1Compatible: true,
|
|
149
|
-
ttlNs: DEFAULT_TTL_NS
|
|
39
|
+
data?: Record<string, any>
|
|
150
40
|
}
|
|
151
41
|
|
|
152
42
|
/**
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* Note: This function does not embed the public key. If you want to do that, use `EmbedPublicKey`.
|
|
156
|
-
*
|
|
157
|
-
* The passed value can be a CID, a PublicKey or an arbitrary string path e.g. `/ipfs/...` or `/ipns/...`.
|
|
43
|
+
* A low-level function that creates a new IPNS record and signs it with the
|
|
44
|
+
* passed private key.
|
|
158
45
|
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* String paths will be stored in the record as-is, but they must start with `"/"`
|
|
46
|
+
* The IPNS Record validity should follow the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt}
|
|
47
|
+
* with nanosecond precision.
|
|
162
48
|
*
|
|
163
|
-
*
|
|
164
|
-
* @param {CID | PublicKey | string} value - content to be stored in the record.
|
|
165
|
-
* @param {number | bigint} seq - number representing the current version of the record.
|
|
166
|
-
* @param {number} lifetime - lifetime of the record (in milliseconds).
|
|
167
|
-
* @param {CreateOptions} options - additional create options.
|
|
49
|
+
* The passed value should be a string path e.g. `/ipfs/...` or `/ipns/...`.
|
|
168
50
|
*/
|
|
169
|
-
export async function createIPNSRecord (privateKey: PrivateKey,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
51
|
+
export async function createIPNSRecord (privateKey: PrivateKey, val: string, seq: number | bigint, lifetime: number, options?: CreateIPNSRecordOptions): Promise<IPNSEntry> {
|
|
52
|
+
seq = BigInt(seq)
|
|
53
|
+
const value = uint8ArrayFromString(val)
|
|
54
|
+
// convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
|
|
55
|
+
const ttlNs = options?.ttlNs ?? DEFAULT_TTL_NS
|
|
56
|
+
|
|
173
57
|
// Validity in ISOString with nanoseconds precision and validity type EOL
|
|
174
58
|
const expirationDate = new NanoDate(Date.now() + Number(lifetime))
|
|
175
|
-
const validityType =
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
* CIDs will be converted to v1 and stored in the record as a string similar to: `/ipfs/${cid}`
|
|
188
|
-
* PublicKeys will create recursive records, eg. the record value will be `/ipns/${cidV1Libp2pKey}`
|
|
189
|
-
* String paths will be stored in the record as-is, but they must start with `"/"`
|
|
190
|
-
*
|
|
191
|
-
* @param {PrivateKey} privateKey - the private key for signing the record.
|
|
192
|
-
* @param {CID | PublicKey | string} value - content to be stored in the record.
|
|
193
|
-
* @param {number | bigint} seq - number representing the current version of the record.
|
|
194
|
-
* @param {string} expiration - expiration datetime for record in the [RFC3339]{@link https://www.ietf.org/rfc/rfc3339.txt} with nanoseconds precision.
|
|
195
|
-
* @param {CreateOptions} options - additional creation options.
|
|
196
|
-
*/
|
|
197
|
-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options?: CreateV2OrV1Options): Promise<IPNSRecordV1V2>
|
|
198
|
-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateV2Options): Promise<IPNSRecordV2>
|
|
199
|
-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateOptions): Promise<IPNSRecordV1V2>
|
|
200
|
-
export async function createIPNSRecordWithExpiration (privateKey: PrivateKey, value: string, seq: number | bigint, expiration: string, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> {
|
|
201
|
-
const expirationDate = NanoDate.fromString(expiration)
|
|
202
|
-
const validityType = IpnsEntry.ValidityType.EOL
|
|
203
|
-
const ttlNs = BigInt(options.ttlNs ?? DEFAULT_TTL_NS)
|
|
204
|
-
|
|
205
|
-
return _create(privateKey, value, seq, validityType, expirationDate.toString(), ttlNs, options)
|
|
206
|
-
}
|
|
59
|
+
const validityType = IPNSEntry.ValidityType.EOL
|
|
60
|
+
const validity = uint8ArrayFromString(expirationDate.toString())
|
|
61
|
+
|
|
62
|
+
const data = encodeExtensibleData({
|
|
63
|
+
...(options?.data ?? {}),
|
|
64
|
+
Value: value,
|
|
65
|
+
Validity: validity,
|
|
66
|
+
// @ts-expect-error should be a number
|
|
67
|
+
ValidityType: 0,
|
|
68
|
+
Sequence: seq,
|
|
69
|
+
TTL: ttlNs
|
|
70
|
+
})
|
|
207
71
|
|
|
208
|
-
const _create = async (privateKey: PrivateKey, value: string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
|
|
209
|
-
seq = BigInt(seq)
|
|
210
|
-
const isoValidity = uint8ArrayFromString(validity)
|
|
211
|
-
const data = createCborData(value, validityType, isoValidity, seq, ttl)
|
|
212
72
|
const sigData = ipnsRecordDataForV2Sig(data)
|
|
213
73
|
const signatureV2 = await privateKey.sign(sigData, options)
|
|
214
|
-
|
|
215
|
-
let record: any
|
|
216
|
-
|
|
217
|
-
if (options.v1Compatible === true) {
|
|
218
|
-
const signatureV1 = await signLegacyV1(privateKey, value, validityType, isoValidity)
|
|
74
|
+
let record: IPNSEntry
|
|
219
75
|
|
|
76
|
+
if (options?.v1Compatible === false) {
|
|
220
77
|
record = {
|
|
221
|
-
value,
|
|
222
|
-
signatureV1,
|
|
223
|
-
validity,
|
|
224
|
-
validityType,
|
|
225
|
-
sequence: seq,
|
|
226
|
-
ttl,
|
|
227
78
|
signatureV2,
|
|
228
|
-
data
|
|
229
|
-
publicKey
|
|
79
|
+
data
|
|
230
80
|
}
|
|
231
81
|
} else {
|
|
232
82
|
record = {
|
|
233
83
|
value,
|
|
234
|
-
validity,
|
|
84
|
+
signatureV1: await signLegacyV1(privateKey, value, validityType, validity),
|
|
235
85
|
validityType,
|
|
86
|
+
validity,
|
|
236
87
|
sequence: seq,
|
|
237
|
-
ttl,
|
|
88
|
+
ttl: ttlNs,
|
|
238
89
|
signatureV2,
|
|
239
|
-
data
|
|
240
|
-
publicKey
|
|
90
|
+
data
|
|
241
91
|
}
|
|
242
92
|
}
|
|
243
93
|
|
|
244
|
-
|
|
94
|
+
if (shouldEmbedPublicKey(privateKey.publicKey)) {
|
|
95
|
+
record.publicKey = privateKey.publicKey.toProtobuf()
|
|
96
|
+
}
|
|
245
97
|
|
|
246
98
|
return record
|
|
247
99
|
}
|
|
248
100
|
|
|
249
|
-
export { unmarshalIPNSRecord } from './utils.ts'
|
|
250
|
-
export { marshalIPNSRecord } from './utils.ts'
|
|
251
|
-
export { multihashToIPNSRoutingKey } from './utils.ts'
|
|
252
|
-
export { multihashFromIPNSRoutingKey } from './utils.ts'
|
|
253
|
-
|
|
254
101
|
/**
|
|
255
102
|
* Sign ipns record data using the legacy V1 signature scheme
|
|
256
103
|
*/
|
|
257
|
-
const signLegacyV1 = async (privateKey: PrivateKey, value:
|
|
104
|
+
const signLegacyV1 = async (privateKey: PrivateKey, value: Uint8Array, validityType: IPNSEntry.ValidityType, validity: Uint8Array, options?: AbortOptions): Promise<Uint8Array<ArrayBuffer>> => {
|
|
258
105
|
try {
|
|
259
106
|
const dataForSignature = ipnsRecordDataForV1Sig(value, validityType, validity)
|
|
260
107
|
|
|
@@ -269,5 +116,5 @@ const signLegacyV1 = async (privateKey: PrivateKey, value: string, validityType:
|
|
|
269
116
|
* Returns true if the public key multihash is not an identity hash
|
|
270
117
|
*/
|
|
271
118
|
function shouldEmbedPublicKey (key: PublicKey): boolean {
|
|
272
|
-
return key.toMultihash().code !==
|
|
119
|
+
return key.toMultihash().code !== IDENTITY_CODEC
|
|
273
120
|
}
|
package/src/routing/helia.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomProgressEvent } from 'progress-events'
|
|
2
|
-
import type {
|
|
2
|
+
import type { IPNSRoutingGetOptions, IPNSRoutingPutOptions } from './index.ts'
|
|
3
3
|
import type { IPNSRouting } from '../index.ts'
|
|
4
4
|
import type { Routing } from '@helia/interface'
|
|
5
5
|
import type { ProgressEvent } from 'progress-events'
|
|
@@ -11,14 +11,14 @@ export interface HeliaRoutingComponents {
|
|
|
11
11
|
export type HeliaRoutingProgressEvents =
|
|
12
12
|
ProgressEvent<'ipns:routing:helia:error', Error>
|
|
13
13
|
|
|
14
|
-
export class
|
|
14
|
+
export class HeliaIPNSRouting implements IPNSRouting {
|
|
15
15
|
private readonly routing: Routing
|
|
16
16
|
|
|
17
17
|
constructor (routing: Routing) {
|
|
18
18
|
this.routing = routing
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options:
|
|
21
|
+
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options: IPNSRoutingPutOptions = {}): Promise<void> {
|
|
22
22
|
try {
|
|
23
23
|
await this.routing.put(routingKey, marshaledRecord, options)
|
|
24
24
|
} catch (err: any) {
|
|
@@ -27,7 +27,7 @@ export class HeliaRouting implements IPNSRouting {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async get (routingKey: Uint8Array, options:
|
|
30
|
+
async get (routingKey: Uint8Array, options: IPNSRoutingGetOptions = {}): Promise<Uint8Array<ArrayBuffer>> {
|
|
31
31
|
try {
|
|
32
32
|
return await this.routing.get(routingKey, options)
|
|
33
33
|
} catch (err: any) {
|
|
@@ -45,6 +45,6 @@ export class HeliaRouting implements IPNSRouting {
|
|
|
45
45
|
* The helia routing uses any available Routers configured on the passed Helia
|
|
46
46
|
* node. This could be libp2p, HTTP API Delegated Routing, etc.
|
|
47
47
|
*/
|
|
48
|
-
export function
|
|
49
|
-
return new
|
|
48
|
+
export function heliaIPNSRouting (routing: Routing): IPNSRouting {
|
|
49
|
+
return new HeliaIPNSRouting(routing)
|
|
50
50
|
}
|
package/src/routing/index.ts
CHANGED
|
@@ -5,11 +5,11 @@ import type { IPNSPublishMetadata } from '../pb/metadata.ts'
|
|
|
5
5
|
import type { AbortOptions } from '@libp2p/interface'
|
|
6
6
|
import type { ProgressOptions } from 'progress-events'
|
|
7
7
|
|
|
8
|
-
export interface
|
|
8
|
+
export interface IPNSRoutingPutOptions extends AbortOptions, ProgressOptions {
|
|
9
9
|
metadata?: IPNSPublishMetadata
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export interface
|
|
12
|
+
export interface IPNSRoutingGetOptions extends AbortOptions, ProgressOptions {
|
|
13
13
|
/**
|
|
14
14
|
* Pass false to not perform validation actions
|
|
15
15
|
*
|
|
@@ -19,8 +19,8 @@ export interface GetOptions extends AbortOptions, ProgressOptions {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface IPNSRouting {
|
|
22
|
-
put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?:
|
|
23
|
-
get(routingKey: Uint8Array, options?:
|
|
22
|
+
put(routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: IPNSRoutingPutOptions): Promise<void>
|
|
23
|
+
get(routingKey: Uint8Array, options?: IPNSRoutingGetOptions): Promise<Uint8Array>
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export type { DatastoreProgressEvents }
|
|
@@ -32,6 +32,6 @@ export type IPNSRoutingProgressEvents =
|
|
|
32
32
|
HeliaRoutingProgressEvents |
|
|
33
33
|
PubSubProgressEvents
|
|
34
34
|
|
|
35
|
-
export {
|
|
36
|
-
export {
|
|
37
|
-
export type { PubsubRoutingComponents, PubSub,
|
|
35
|
+
export { heliaIPNSRouting } from './helia.ts'
|
|
36
|
+
export { pubSubIPNSRouting } from './pubsub.ts'
|
|
37
|
+
export type { PubsubRoutingComponents, PubSub, PubSubMessage, PublishResult, PubSubEvents, PubSubSubscription, PubSubSubscriptionChangeData } from './pubsub.ts'
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { LocalStore } from '../local-store.ts'
|
|
2
|
-
import type { IPNSRouting,
|
|
2
|
+
import type { IPNSRouting, IPNSRoutingPutOptions, IPNSRoutingGetOptions } from './index.ts'
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class LocalStoreIPNSRouting {
|
|
5
5
|
private localStore: LocalStore
|
|
6
6
|
|
|
7
7
|
constructor (localStore: LocalStore) {
|
|
8
8
|
this.localStore = localStore
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options?:
|
|
11
|
+
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options?: IPNSRoutingPutOptions): Promise<void> {
|
|
12
12
|
await this.localStore.put(routingKey, marshaledRecord, options)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async get (routingKey: Uint8Array, options?:
|
|
15
|
+
async get (routingKey: Uint8Array, options?: IPNSRoutingGetOptions): Promise<Uint8Array<ArrayBuffer>> {
|
|
16
16
|
const { record } = await this.localStore.get(routingKey, options)
|
|
17
17
|
|
|
18
18
|
return record
|
|
@@ -26,6 +26,6 @@ class LocalStoreRouting {
|
|
|
26
26
|
/**
|
|
27
27
|
* Returns an IPNSRouting implementation that reads/writes to the local store
|
|
28
28
|
*/
|
|
29
|
-
export function
|
|
30
|
-
return new
|
|
29
|
+
export function localStoreIPNSRouting (localStore: LocalStore): IPNSRouting {
|
|
30
|
+
return new LocalStoreIPNSRouting(localStore)
|
|
31
31
|
}
|
package/src/routing/pubsub.ts
CHANGED
|
@@ -9,13 +9,13 @@ import { raceSignal } from 'race-signal'
|
|
|
9
9
|
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
10
10
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
11
11
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
12
|
+
import { withArrayBuffer } from 'uint8arrays/with-array-buffer'
|
|
12
13
|
import { InvalidTopicError } from '../errors.ts'
|
|
13
14
|
import { localStore } from '../local-store.ts'
|
|
14
|
-
import { multihashToIPNSRoutingKey } from '../records.ts'
|
|
15
15
|
import { ipnsSelector } from '../selector.ts'
|
|
16
|
-
import { IPNS_STRING_PREFIX,
|
|
16
|
+
import { IPNS_STRING_PREFIX, multihashToIPNSRoutingKey } from '../utils.ts'
|
|
17
17
|
import { ipnsValidator } from '../validator.ts'
|
|
18
|
-
import type {
|
|
18
|
+
import type { IPNSRoutingGetOptions, IPNSRouting, IPNSRoutingPutOptions } from './index.ts'
|
|
19
19
|
import type { LocalStore } from '../local-store.ts'
|
|
20
20
|
import type { Keychain } from '@helia/interface'
|
|
21
21
|
import type { Fetch } from '@libp2p/fetch'
|
|
@@ -26,26 +26,26 @@ import type { ProgressEvent } from 'progress-events'
|
|
|
26
26
|
|
|
27
27
|
const log = logger('helia:ipns:routing:pubsub')
|
|
28
28
|
|
|
29
|
-
export interface
|
|
29
|
+
export interface PubSubMessage {
|
|
30
30
|
type: 'signed' | 'unsigned'
|
|
31
31
|
from: PeerId
|
|
32
32
|
topic: string
|
|
33
33
|
data: Uint8Array
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
export interface
|
|
36
|
+
export interface PubSubSubscription {
|
|
37
37
|
topic: string
|
|
38
38
|
subscribe: boolean
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export interface
|
|
41
|
+
export interface PubSubSubscriptionChangeData {
|
|
42
42
|
peerId: PeerId
|
|
43
|
-
subscriptions:
|
|
43
|
+
subscriptions: PubSubSubscription[]
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export interface PubSubEvents {
|
|
47
|
-
'subscription-change': CustomEvent<
|
|
48
|
-
message: CustomEvent<
|
|
47
|
+
'subscription-change': CustomEvent<PubSubSubscriptionChangeData>
|
|
48
|
+
message: CustomEvent<PubSubMessage>
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export interface PublishResult {
|
|
@@ -96,7 +96,7 @@ export type PubSubProgressEvents =
|
|
|
96
96
|
ProgressEvent<'ipns:pubsub:subscribe', { topic: string }> |
|
|
97
97
|
ProgressEvent<'ipns:pubsub:error', Error>
|
|
98
98
|
|
|
99
|
-
export class
|
|
99
|
+
export class PubSubIPNSRouting implements IPNSRouting, Startable {
|
|
100
100
|
private readonly subscriptions: Set<string>
|
|
101
101
|
private readonly localStore: LocalStore
|
|
102
102
|
private readonly libp2p: Pick<Libp2p<{ pubsub: PubSub, fetch?: Fetch }>, 'peerId' | 'register' | 'unregister' | 'services'>
|
|
@@ -196,7 +196,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
async #processPubSubMessage (message:
|
|
199
|
+
async #processPubSubMessage (message: PubSubMessage, options?: AbortOptions): Promise<void> {
|
|
200
200
|
log('message received for topic', message.topic)
|
|
201
201
|
|
|
202
202
|
if (message.type !== 'signed') {
|
|
@@ -212,7 +212,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
212
212
|
await this.#handleRecord(message.topic, topicToKey(message.topic), message.data, false, options)
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
async #fetchFromPeer (topic: string, routingKey: Uint8Array, peerId: PeerId, options?: AbortOptions): Promise<Uint8Array
|
|
215
|
+
async #fetchFromPeer (topic: string, routingKey: Uint8Array, peerId: PeerId, options?: AbortOptions): Promise<Uint8Array<ArrayBuffer>> {
|
|
216
216
|
const marshalledRecord = await this.fetchQueue.add(async ({ signal }) => {
|
|
217
217
|
log('fetching ipns record for %m from peer %s', routingKey, peerId)
|
|
218
218
|
|
|
@@ -238,18 +238,17 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
238
238
|
return this.#handleRecord(topic, routingKey, marshalledRecord, true, options)
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
async #handleRecord (topic: string, routingKey: Uint8Array, marshalledRecord: Uint8Array, publish: boolean, options?: AbortOptions): Promise<Uint8Array
|
|
242
|
-
const record = await
|
|
243
|
-
await ipnsValidator(record, options)
|
|
241
|
+
async #handleRecord (topic: string, routingKey: Uint8Array, marshalledRecord: Uint8Array, publish: boolean, options?: AbortOptions): Promise<Uint8Array<ArrayBuffer>> {
|
|
242
|
+
const record = await ipnsValidator(routingKey, marshalledRecord, this.keychain, options)
|
|
244
243
|
this.shutdownController.signal.throwIfAborted()
|
|
245
244
|
|
|
246
245
|
if (await this.localStore.has(routingKey)) {
|
|
247
246
|
const { record: marshaledCurrentRecord } = await this.localStore.get(routingKey, options)
|
|
248
|
-
const currentRecord = await
|
|
247
|
+
const currentRecord = await ipnsValidator(routingKey, marshaledCurrentRecord, this.keychain, options)
|
|
249
248
|
|
|
250
|
-
if (uint8ArrayEquals(marshaledCurrentRecord,
|
|
249
|
+
if (uint8ArrayEquals(marshaledCurrentRecord, marshalledRecord)) {
|
|
251
250
|
log.trace('found identical record for %m', routingKey)
|
|
252
|
-
return
|
|
251
|
+
return marshaledCurrentRecord
|
|
253
252
|
}
|
|
254
253
|
|
|
255
254
|
const records = [currentRecord, record]
|
|
@@ -257,7 +256,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
257
256
|
|
|
258
257
|
if (index === 0) {
|
|
259
258
|
log.trace('found old record for %m', routingKey)
|
|
260
|
-
return
|
|
259
|
+
return marshaledCurrentRecord
|
|
261
260
|
}
|
|
262
261
|
}
|
|
263
262
|
|
|
@@ -276,13 +275,13 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
276
275
|
}
|
|
277
276
|
}
|
|
278
277
|
|
|
279
|
-
return marshalledRecord
|
|
278
|
+
return withArrayBuffer(marshalledRecord)
|
|
280
279
|
}
|
|
281
280
|
|
|
282
281
|
/**
|
|
283
282
|
* Put a value to the pubsub datastore indexed by the received key properly encoded
|
|
284
283
|
*/
|
|
285
|
-
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options:
|
|
284
|
+
async put (routingKey: Uint8Array, marshaledRecord: Uint8Array, options: IPNSRoutingPutOptions = {}): Promise<void> {
|
|
286
285
|
try {
|
|
287
286
|
const topic = keyToTopic(routingKey)
|
|
288
287
|
|
|
@@ -303,7 +302,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
303
302
|
* Also, the identifier topic is subscribed to and the pubsub datastore records will be
|
|
304
303
|
* updated once new publishes occur
|
|
305
304
|
*/
|
|
306
|
-
async get (routingKey: Uint8Array, options:
|
|
305
|
+
async get (routingKey: Uint8Array, options: IPNSRoutingGetOptions = {}): Promise<Uint8Array<ArrayBuffer>> {
|
|
307
306
|
const topic = keyToTopic(routingKey)
|
|
308
307
|
|
|
309
308
|
try {
|
|
@@ -324,7 +323,7 @@ export class PubSubRouting implements IPNSRouting, Startable {
|
|
|
324
323
|
await raceSignal(delay(this.fetchDelay), this.shutdownController.signal)
|
|
325
324
|
|
|
326
325
|
const fetchController = new AbortController()
|
|
327
|
-
const promises: Array<Promise<Uint8Array
|
|
326
|
+
const promises: Array<Promise<Uint8Array<ArrayBuffer>>> = []
|
|
328
327
|
|
|
329
328
|
for (const peerId of this.libp2p.services.pubsub.getSubscribers(topic)) {
|
|
330
329
|
const signal = anySignal([
|
|
@@ -447,6 +446,6 @@ function topicToKey (topic: string): Uint8Array {
|
|
|
447
446
|
* updated records, so the first call to `.get` should be expected
|
|
448
447
|
* to fail!
|
|
449
448
|
*/
|
|
450
|
-
export function
|
|
451
|
-
return new
|
|
449
|
+
export function pubSubIPNSRouting (components: PubsubRoutingComponents, init: PubsubRoutingInit = {}): IPNSRouting {
|
|
450
|
+
return new PubSubIPNSRouting(components, init)
|
|
452
451
|
}
|
package/src/selector.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import NanoDate from 'timestamp-nano'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
3
|
+
import { IPNSEntry } from './pb/ipns.ts'
|
|
4
|
+
import { decodeExtensibleData } from './utils.ts'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Selects the latest valid IPNS record from an array of marshalled IPNS records.
|
|
@@ -13,19 +14,21 @@ import type { IPNSRecord } from './records.ts'
|
|
|
13
14
|
* @param data - Array of marshalled IPNS records to select from
|
|
14
15
|
* @returns The index of the most valid record from the input array
|
|
15
16
|
*/
|
|
16
|
-
export function ipnsSelector (key: Uint8Array, data:
|
|
17
|
+
export function ipnsSelector (key: Uint8Array, data: IPNSEntry[]): number {
|
|
17
18
|
const entries = data.map((record, index) => ({
|
|
18
19
|
record,
|
|
19
20
|
index
|
|
20
21
|
}))
|
|
21
22
|
|
|
22
23
|
entries.sort((a, b) => {
|
|
23
|
-
// Before we'd sort based on the signature version.
|
|
24
|
-
//
|
|
25
|
-
// records
|
|
24
|
+
// Before we'd sort based on the signature version. Validation fails if a
|
|
25
|
+
// record does not have SignatureV2, so that is no longer needed. V1-only
|
|
26
|
+
// records will have all expired by this point.
|
|
27
|
+
const aData = decodeExtensibleData(a.record.data)
|
|
28
|
+
const bData = decodeExtensibleData(b.record.data)
|
|
26
29
|
|
|
27
|
-
const aSeq =
|
|
28
|
-
const bSeq =
|
|
30
|
+
const aSeq = aData.Sequence
|
|
31
|
+
const bSeq = bData.Sequence
|
|
29
32
|
|
|
30
33
|
// choose later sequence number
|
|
31
34
|
if (aSeq > bSeq) {
|
|
@@ -34,10 +37,10 @@ export function ipnsSelector (key: Uint8Array, data: IPNSRecord[]): number {
|
|
|
34
37
|
return 1
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
if (
|
|
40
|
+
if (aData.ValidityType === IPNSEntry.ValidityType.EOL && bData.ValidityType === IPNSEntry.ValidityType.EOL) {
|
|
38
41
|
// choose longer lived record if sequence numbers the same
|
|
39
|
-
const recordAValidityDate = NanoDate.fromString(
|
|
40
|
-
const recordBValidityDate = NanoDate.fromString(
|
|
42
|
+
const recordAValidityDate = NanoDate.fromString(uint8ArrayToString(aData.Validity)).toDate()
|
|
43
|
+
const recordBValidityDate = NanoDate.fromString(uint8ArrayToString(bData.Validity)).toDate()
|
|
41
44
|
|
|
42
45
|
if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
|
|
43
46
|
return -1
|