@helia/ipns 9.2.1-35b081c7 → 9.2.1-73a28eda
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 +12 -49
- package/dist/index.min.js +5 -16
- package/dist/index.min.js.map +4 -4
- package/dist/src/errors.d.ts +33 -5
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +33 -20
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +53 -91
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -52
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +5 -9
- package/dist/src/ipns/publisher.d.ts.map +1 -1
- package/dist/src/ipns/publisher.js +30 -22
- package/dist/src/ipns/publisher.js.map +1 -1
- package/dist/src/ipns/republisher.d.ts +3 -5
- package/dist/src/ipns/republisher.d.ts.map +1 -1
- package/dist/src/ipns/republisher.js +18 -9
- package/dist/src/ipns/republisher.js.map +1 -1
- package/dist/src/ipns/resolver.d.ts +6 -5
- package/dist/src/ipns/resolver.d.ts.map +1 -1
- package/dist/src/ipns/resolver.js +32 -78
- package/dist/src/ipns/resolver.js.map +1 -1
- package/dist/src/ipns.d.ts +6 -4
- package/dist/src/ipns.d.ts.map +1 -1
- package/dist/src/ipns.js +33 -4
- package/dist/src/ipns.js.map +1 -1
- package/dist/src/pb/ipns.d.ts +62 -0
- package/dist/src/pb/ipns.d.ts.map +1 -0
- package/dist/src/pb/ipns.js +203 -0
- package/dist/src/pb/ipns.js.map +1 -0
- package/dist/src/pb/metadata.d.ts +1 -1
- package/dist/src/pb/metadata.d.ts.map +1 -1
- package/dist/src/records.d.ts +155 -0
- package/dist/src/records.d.ts.map +1 -0
- package/dist/src/records.js +88 -0
- package/dist/src/records.js.map +1 -0
- package/dist/src/routing/pubsub.d.ts +3 -0
- package/dist/src/routing/pubsub.d.ts.map +1 -1
- package/dist/src/routing/pubsub.js +15 -10
- package/dist/src/routing/pubsub.js.map +1 -1
- package/dist/src/selector.d.ts +14 -0
- package/dist/src/selector.d.ts.map +1 -0
- package/dist/src/selector.js +47 -0
- package/dist/src/selector.js.map +1 -0
- package/dist/src/utils.d.ts +29 -2
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +308 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validator.d.ts +18 -0
- package/dist/src/validator.d.ts.map +1 -0
- package/dist/src/validator.js +58 -0
- package/dist/src/validator.js.map +1 -0
- package/package.json +18 -17
- package/src/errors.ts +40 -25
- package/src/index.ts +54 -92
- package/src/ipns/publisher.ts +34 -29
- package/src/ipns/republisher.ts +24 -13
- package/src/ipns/resolver.ts +40 -88
- package/src/ipns.ts +44 -7
- package/src/pb/ipns.proto +39 -0
- package/src/pb/ipns.ts +280 -0
- package/src/pb/metadata.ts +1 -1
- package/src/records.ts +273 -0
- package/src/routing/pubsub.ts +17 -10
- package/src/selector.ts +55 -0
- package/src/utils.ts +371 -2
- package/src/validator.ts +67 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import NanoDate from 'timestamp-nano';
|
|
2
|
+
import { InvalidEmbeddedPublicKeyError, RecordExpiredError, SignatureVerificationError, UnsupportedValidityError } from "./errors.js";
|
|
3
|
+
import { IpnsEntry } from "./pb/ipns.js";
|
|
4
|
+
import { ipnsRecordDataForV2Sig } from "./utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* Validate the given IPNS record against the given routing key.
|
|
7
|
+
*
|
|
8
|
+
* @see https://specs.ipfs.tech/ipns/ipns-record/#routing-record for the binary
|
|
9
|
+
* format of the routing key
|
|
10
|
+
*/
|
|
11
|
+
export async function ipnsValidator(record, options) {
|
|
12
|
+
if (record.publicKey == null) {
|
|
13
|
+
throw new InvalidEmbeddedPublicKeyError('The record had no public key associated with it');
|
|
14
|
+
}
|
|
15
|
+
// Validate Signature V2
|
|
16
|
+
let isValid;
|
|
17
|
+
try {
|
|
18
|
+
const dataForSignature = ipnsRecordDataForV2Sig(record.data);
|
|
19
|
+
isValid = await record.publicKey.verify(dataForSignature, record.signatureV2, options);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
isValid = false;
|
|
23
|
+
}
|
|
24
|
+
if (!isValid) {
|
|
25
|
+
throw new SignatureVerificationError('Record signature verification failed');
|
|
26
|
+
}
|
|
27
|
+
// Validate according to the validity type
|
|
28
|
+
if (record.validityType === IpnsEntry.ValidityType.EOL) {
|
|
29
|
+
if (NanoDate.fromString(record.validity).toDate().getTime() < Date.now()) {
|
|
30
|
+
throw new RecordExpiredError('record has expired');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (record.validityType != null) {
|
|
34
|
+
throw new UnsupportedValidityError('The validity type is unsupported');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the number of milliseconds until the record expires.
|
|
39
|
+
* If the record is already expired, returns 0.
|
|
40
|
+
*
|
|
41
|
+
* @param record - The IPNS record to validate.
|
|
42
|
+
* @returns The number of milliseconds until the record expires, or 0 if the record is already expired.
|
|
43
|
+
*/
|
|
44
|
+
export function validFor(record) {
|
|
45
|
+
if (record.validityType !== IpnsEntry.ValidityType.EOL) {
|
|
46
|
+
throw new UnsupportedValidityError();
|
|
47
|
+
}
|
|
48
|
+
if (record.validity == null) {
|
|
49
|
+
throw new UnsupportedValidityError();
|
|
50
|
+
}
|
|
51
|
+
const validUntil = NanoDate.fromString(record.validity).toDate().getTime();
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
if (validUntil < now) {
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
return validUntil - now;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AACrI,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAInD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAE,MAAkB,EAAE,OAAsB;IAC7E,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,6BAA6B,CAAC,iDAAiD,CAAC,CAAA;IAC5F,CAAC;IAED,wBAAwB;IACxB,IAAI,OAAO,CAAA;IAEX,IAAI,CAAC;QACH,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC5D,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,KAAK,CAAA;IACjB,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,0BAA0B,CAAC,sCAAsC,CAAC,CAAA;IAC9E,CAAC;IAED,0CAA0C;IAC1C,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACvD,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACzE,MAAM,IAAI,kBAAkB,CAAC,oBAAoB,CAAC,CAAA;QACpD,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;QACvC,MAAM,IAAI,wBAAwB,CAAC,kCAAkC,CAAC,CAAA;IACxE,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAE,MAAkB;IAC1C,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACvD,MAAM,IAAI,wBAAwB,EAAE,CAAA;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,wBAAwB,EAAE,CAAA;IACtC,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,CAAA;IAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAEtB,IAAI,UAAU,GAAG,GAAG,EAAE,CAAC;QACrB,OAAO,CAAC,CAAA;IACV,CAAC;IAED,OAAO,UAAU,GAAG,GAAG,CAAA;AACzB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helia/ipns",
|
|
3
|
-
"version": "9.2.1-
|
|
3
|
+
"version": "9.2.1-73a28eda",
|
|
4
4
|
"description": "An implementation of IPNS for Helia",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"homepage": "https://github.com/ipfs/helia/tree/main/packages/ipns#readme",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"doc-check": "aegir doc-check",
|
|
67
67
|
"build": "aegir build",
|
|
68
68
|
"docs": "aegir docs",
|
|
69
|
-
"generate": "protons ./src/pb
|
|
69
|
+
"generate": "protons ./src/pb/*.proto",
|
|
70
70
|
"test": "aegir test",
|
|
71
71
|
"test:chrome": "aegir test -t browser --cov",
|
|
72
72
|
"test:chrome-webworker": "aegir test -t webworker",
|
|
@@ -76,34 +76,35 @@
|
|
|
76
76
|
"test:electron-main": "aegir test -t electron-main"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@helia/interface": "6.2.1-
|
|
80
|
-
"@libp2p/crypto": "^5.1.15",
|
|
79
|
+
"@helia/interface": "6.2.1-73a28eda",
|
|
81
80
|
"@libp2p/fetch": "^4.1.0",
|
|
82
81
|
"@libp2p/interface": "^3.2.0",
|
|
83
82
|
"@libp2p/kad-dht": "^16.2.0",
|
|
84
|
-
"@libp2p/keychain": "^6.0.12",
|
|
85
83
|
"@libp2p/logger": "^6.2.4",
|
|
86
84
|
"@libp2p/peer-collections": "^7.0.15",
|
|
87
85
|
"@libp2p/utils": "^7.0.15",
|
|
86
|
+
"abort-error": "^1.0.2",
|
|
88
87
|
"any-signal": "^4.2.0",
|
|
88
|
+
"cborg": "^5.1.1",
|
|
89
89
|
"delay": "^7.0.0",
|
|
90
|
-
"interface-datastore": "^
|
|
91
|
-
"
|
|
92
|
-
"multiformats": "^13.4.2",
|
|
90
|
+
"interface-datastore": "^10.0.1",
|
|
91
|
+
"multiformats": "^14.0.0",
|
|
93
92
|
"progress-events": "^1.1.0",
|
|
94
|
-
"protons-runtime": "^
|
|
93
|
+
"protons-runtime": "^7.0.0",
|
|
95
94
|
"race-signal": "^2.0.0",
|
|
96
|
-
"
|
|
97
|
-
"
|
|
95
|
+
"timestamp-nano": "^1.0.1",
|
|
96
|
+
"uint8arraylist": "^3.0.2",
|
|
97
|
+
"uint8arrays": "^6.1.1"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
|
-
"@
|
|
101
|
-
"@
|
|
102
|
-
"aegir": "^
|
|
103
|
-
"datastore-core": "^
|
|
100
|
+
"@ipshipyard/crypto": "^1.1.0",
|
|
101
|
+
"@ipshipyard/keychain": "^1.0.2",
|
|
102
|
+
"aegir": "^48.0.4",
|
|
103
|
+
"datastore-core": "^12.0.1",
|
|
104
104
|
"it-drain": "^3.0.12",
|
|
105
|
-
"
|
|
106
|
-
"
|
|
105
|
+
"it-last": "^3.0.11",
|
|
106
|
+
"protons": "^9.0.1",
|
|
107
|
+
"sinon": "^22.0.0",
|
|
107
108
|
"sinon-ts": "^2.0.0"
|
|
108
109
|
},
|
|
109
110
|
"browser": {
|
package/src/errors.ts
CHANGED
|
@@ -1,49 +1,64 @@
|
|
|
1
1
|
export class RecordsFailedValidationError extends Error {
|
|
2
2
|
static name = 'RecordsFailedValidationError'
|
|
3
|
-
|
|
4
|
-
constructor (message = 'Records failed validation') {
|
|
5
|
-
super(message)
|
|
6
|
-
this.name = 'RecordsFailedValidationError'
|
|
7
|
-
}
|
|
3
|
+
name = 'RecordsFailedValidationError'
|
|
8
4
|
}
|
|
9
5
|
|
|
10
6
|
export class UnsupportedMultibasePrefixError extends Error {
|
|
11
7
|
static name = 'UnsupportedMultibasePrefixError'
|
|
12
|
-
|
|
13
|
-
constructor (message = 'Unsupported multibase prefix') {
|
|
14
|
-
super(message)
|
|
15
|
-
this.name = 'UnsupportedMultibasePrefixError'
|
|
16
|
-
}
|
|
8
|
+
name = 'UnsupportedMultibasePrefixError'
|
|
17
9
|
}
|
|
18
10
|
|
|
19
11
|
export class UnsupportedMultihashCodecError extends Error {
|
|
20
12
|
static name = 'UnsupportedMultihashCodecError'
|
|
21
|
-
|
|
22
|
-
constructor (message = 'Unsupported multihash codec') {
|
|
23
|
-
super(message)
|
|
24
|
-
this.name = 'UnsupportedMultihashCodecError'
|
|
25
|
-
}
|
|
13
|
+
name = 'UnsupportedMultihashCodecError'
|
|
26
14
|
}
|
|
27
15
|
|
|
28
16
|
export class InvalidValueError extends Error {
|
|
29
17
|
static name = 'InvalidValueError'
|
|
30
|
-
|
|
31
|
-
constructor (message = 'Invalid value') {
|
|
32
|
-
super(message)
|
|
33
|
-
this.name = 'InvalidValueError'
|
|
34
|
-
}
|
|
18
|
+
name = 'InvalidValueError'
|
|
35
19
|
}
|
|
36
20
|
|
|
37
21
|
export class InvalidTopicError extends Error {
|
|
38
22
|
static name = 'InvalidTopicError'
|
|
39
|
-
|
|
40
|
-
constructor (message = 'Invalid topic') {
|
|
41
|
-
super(message)
|
|
42
|
-
this.name = 'InvalidTopicError'
|
|
43
|
-
}
|
|
23
|
+
name = 'InvalidTopicError'
|
|
44
24
|
}
|
|
45
25
|
|
|
46
26
|
export class RecordNotFoundError extends Error {
|
|
47
27
|
static name = 'RecordNotFoundError'
|
|
48
28
|
name = 'RecordNotFoundError'
|
|
49
29
|
}
|
|
30
|
+
|
|
31
|
+
export class SignatureCreationError extends Error {
|
|
32
|
+
static name = 'SignatureCreationError'
|
|
33
|
+
name = 'SignatureCreationError'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SignatureVerificationError extends Error {
|
|
37
|
+
static name = 'SignatureVerificationError'
|
|
38
|
+
name = 'SignatureVerificationError'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class RecordExpiredError extends Error {
|
|
42
|
+
static name = 'RecordExpiredError'
|
|
43
|
+
name = 'RecordExpiredError'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class UnsupportedValidityError extends Error {
|
|
47
|
+
static name = 'UnsupportedValidityError'
|
|
48
|
+
name = 'UnsupportedValidityError'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class RecordTooLargeError extends Error {
|
|
52
|
+
static name = 'RecordTooLargeError'
|
|
53
|
+
name = 'RecordTooLargeError'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class InvalidRecordDataError extends Error {
|
|
57
|
+
static name = 'InvalidRecordDataError'
|
|
58
|
+
name = 'InvalidRecordDataError'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class InvalidEmbeddedPublicKeyError extends Error {
|
|
62
|
+
static name = 'InvalidEmbeddedPublicKeyError'
|
|
63
|
+
name = 'InvalidEmbeddedPublicKeyError'
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
* const { publicKey } = await name.publish('key-1', cid)
|
|
24
24
|
*
|
|
25
25
|
* // resolve the name
|
|
26
|
-
* const result
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* for await (const result of name.resolve(publicKey)) {
|
|
27
|
+
* console.info(result.record.value) // /ipfs/QmFoo
|
|
28
|
+
* }
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
31
|
* @example Publishing a recursive record
|
|
@@ -53,8 +53,9 @@
|
|
|
53
53
|
* const { publicKey: recursivePublicKey } = await name.publish('key-2', publicKey)
|
|
54
54
|
*
|
|
55
55
|
* // resolve the name recursively - it resolves until a CID is found
|
|
56
|
-
* const result
|
|
57
|
-
*
|
|
56
|
+
* for await (const result of name.resolve(recursivePublicKey)) {
|
|
57
|
+
* console.info(result.record.value) // /ipfs/QmFoo../foo.txt
|
|
58
|
+
* }
|
|
58
59
|
* ```
|
|
59
60
|
*
|
|
60
61
|
* @example Publishing a record with a path
|
|
@@ -82,9 +83,9 @@
|
|
|
82
83
|
* const { publicKey } = await name.publish('key-1', `/ipfs/${finalDirCid}/foo.txt`)
|
|
83
84
|
*
|
|
84
85
|
* // resolve the name
|
|
85
|
-
* const result
|
|
86
|
-
*
|
|
87
|
-
*
|
|
86
|
+
* for await (const result of name.resolve(publicKey)) {
|
|
87
|
+
* console.info(result.record.value) // /ipfs/QmFoo../foo.txt
|
|
88
|
+
* }
|
|
88
89
|
* ```
|
|
89
90
|
*
|
|
90
91
|
* @example Using custom PubSub router
|
|
@@ -135,51 +136,12 @@
|
|
|
135
136
|
* const { publicKey } = await name.publish('key-1', cid)
|
|
136
137
|
*
|
|
137
138
|
* // resolve the name
|
|
138
|
-
* const result
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* @example Republishing an existing IPNS record
|
|
142
|
-
*
|
|
143
|
-
* It is sometimes useful to be able to republish an existing IPNS record
|
|
144
|
-
* without needing the private key. This allows you to extend the availability
|
|
145
|
-
* of a record that was created elsewhere.
|
|
146
|
-
*
|
|
147
|
-
* ```TypeScript
|
|
148
|
-
* import { createHelia } from 'helia'
|
|
149
|
-
* import { ipns, ipnsValidator } from '@helia/ipns'
|
|
150
|
-
* import { delegatedRoutingV1HttpApiClient } from '@helia/delegated-routing-v1-http-api-client'
|
|
151
|
-
* import { CID } from 'multiformats/cid'
|
|
152
|
-
* import { multihashToIPNSRoutingKey, marshalIPNSRecord } from 'ipns'
|
|
153
|
-
* import { defaultLogger } from '@libp2p/logger'
|
|
154
|
-
*
|
|
155
|
-
* const helia = await createHelia()
|
|
156
|
-
* const name = ipns(helia)
|
|
157
|
-
*
|
|
158
|
-
* const ipnsName = 'k51qzi5uqu5dktsyfv7xz8h631pri4ct7osmb43nibxiojpttxzoft6hdyyzg4'
|
|
159
|
-
* const parsedCid: CID<unknown, 114, 0 | 18, 1> = CID.parse(ipnsName)
|
|
160
|
-
* const delegatedClient = delegatedRoutingV1HttpApiClient({
|
|
161
|
-
* url: 'https://delegated-ipfs.dev'
|
|
162
|
-
* })({
|
|
163
|
-
* logger: defaultLogger()
|
|
164
|
-
* })
|
|
165
|
-
* const record = await delegatedClient.getIPNS(parsedCid)
|
|
166
|
-
*
|
|
167
|
-
* const routingKey = multihashToIPNSRoutingKey(parsedCid.multihash)
|
|
168
|
-
* const marshaledRecord = marshalIPNSRecord(record)
|
|
169
|
-
*
|
|
170
|
-
* // validate that they key corresponds to the record
|
|
171
|
-
* await ipnsValidator(routingKey, marshaledRecord)
|
|
172
|
-
*
|
|
173
|
-
* // publish record to routing
|
|
174
|
-
* await Promise.all(
|
|
175
|
-
* name.routers.map(async r => {
|
|
176
|
-
* await r.put(routingKey, marshaledRecord)
|
|
177
|
-
* })
|
|
178
|
-
* )
|
|
139
|
+
* for await (const result of name.resolve(publicKey)) {
|
|
140
|
+
* console.info(result.record.value)
|
|
141
|
+
* }
|
|
179
142
|
* ```
|
|
180
143
|
*/
|
|
181
144
|
|
|
182
|
-
import { ipnsValidator } from 'ipns/validator'
|
|
183
145
|
import { CID } from 'multiformats/cid'
|
|
184
146
|
import { IPNSResolver as IPNSResolverClass } from './ipns/resolver.ts'
|
|
185
147
|
import { IPNS as IPNSClass } from './ipns.ts'
|
|
@@ -187,12 +149,12 @@ import { localStore } from './local-store.ts'
|
|
|
187
149
|
import { helia } from './routing/index.ts'
|
|
188
150
|
import { localStoreRouting } from './routing/local-store.ts'
|
|
189
151
|
import type { IPNSResolverComponents } from './ipns/resolver.ts'
|
|
152
|
+
import type { IPNSRecord } from './records.ts'
|
|
190
153
|
import type { IPNSRouting, IPNSRoutingProgressEvents } from './routing/index.ts'
|
|
191
|
-
import type { Routing, HeliaEvents } from '@helia/interface'
|
|
192
|
-
import type {
|
|
193
|
-
import type {
|
|
154
|
+
import type { Routing, HeliaEvents, Keychain, PublicKey } from '@helia/interface'
|
|
155
|
+
import type { ComponentLogger, TypedEventEmitter } from '@libp2p/interface'
|
|
156
|
+
import type { AbortOptions } from 'abort-error'
|
|
194
157
|
import type { Datastore } from 'interface-datastore'
|
|
195
|
-
import type { IPNSRecord } from 'ipns'
|
|
196
158
|
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
197
159
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
198
160
|
|
|
@@ -214,23 +176,31 @@ export type DatastoreProgressEvents =
|
|
|
214
176
|
|
|
215
177
|
export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingProgressEvents> {
|
|
216
178
|
/**
|
|
217
|
-
* Time duration of the signature validity in ms
|
|
179
|
+
* Time duration of the signature validity in ms
|
|
180
|
+
*
|
|
181
|
+
* @default 172_800_000
|
|
218
182
|
*/
|
|
219
183
|
lifetime?: number
|
|
220
184
|
|
|
221
185
|
/**
|
|
222
|
-
* Only publish to a local datastore
|
|
186
|
+
* Only publish to a local datastore
|
|
187
|
+
*
|
|
188
|
+
* @default false
|
|
223
189
|
*/
|
|
224
190
|
offline?: boolean
|
|
225
191
|
|
|
226
192
|
/**
|
|
227
193
|
* By default a IPNS V1 and a V2 signature is added to every record. Pass
|
|
228
|
-
* false here to only add a V2 signature.
|
|
194
|
+
* false here to only add a V2 signature.
|
|
195
|
+
*
|
|
196
|
+
* @default true
|
|
229
197
|
*/
|
|
230
198
|
v1Compatible?: boolean
|
|
231
199
|
|
|
232
200
|
/**
|
|
233
|
-
* The TTL of the record in ms
|
|
201
|
+
* The TTL of the record in ms
|
|
202
|
+
*
|
|
203
|
+
* @default 300_000
|
|
234
204
|
*/
|
|
235
205
|
ttl?: number
|
|
236
206
|
}
|
|
@@ -257,32 +227,25 @@ export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolvePro
|
|
|
257
227
|
}
|
|
258
228
|
|
|
259
229
|
export interface ResolveResult {
|
|
260
|
-
/**
|
|
261
|
-
* The CID that was resolved
|
|
262
|
-
*/
|
|
263
|
-
cid: CID
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Any path component that was part of the resolved record
|
|
267
|
-
*/
|
|
268
|
-
path?: string
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export interface IPNSResolveResult extends ResolveResult {
|
|
272
230
|
/**
|
|
273
231
|
* The resolved record
|
|
274
232
|
*/
|
|
275
233
|
record: IPNSRecord
|
|
276
234
|
}
|
|
277
235
|
|
|
278
|
-
export interface
|
|
236
|
+
export interface PublishResult {
|
|
279
237
|
/**
|
|
280
238
|
* The published record
|
|
281
239
|
*/
|
|
282
240
|
record: IPNSRecord
|
|
283
241
|
|
|
284
242
|
/**
|
|
285
|
-
* The
|
|
243
|
+
* The IPNS name that can be used to resolve this record
|
|
244
|
+
*/
|
|
245
|
+
name: string
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The public key that was used to sign and publish the record
|
|
286
249
|
*/
|
|
287
250
|
publicKey: PublicKey
|
|
288
251
|
}
|
|
@@ -295,7 +258,7 @@ export interface IPNSResolver {
|
|
|
295
258
|
* Ed25519, secp256k1 or RSA PeerId and recursively resolves the IPNS record
|
|
296
259
|
* corresponding to that key until a value is found.
|
|
297
260
|
*/
|
|
298
|
-
resolve(key: CID<unknown, 0x72
|
|
261
|
+
resolve(key: CID<unknown, 0x72> | MultihashDigest, options?: ResolveOptions): AsyncGenerator<ResolveResult>
|
|
299
262
|
}
|
|
300
263
|
|
|
301
264
|
export interface IPNS {
|
|
@@ -308,12 +271,14 @@ export interface IPNS {
|
|
|
308
271
|
* Creates and publishes an IPNS record that will resolve the passed value
|
|
309
272
|
* signed by a key stored in the libp2p keychain under the passed key name.
|
|
310
273
|
*
|
|
274
|
+
* If the key does not exist, a new Ed25519 key will be created. To use a
|
|
275
|
+
* different key types, ensure the key is created and stored in the keychain
|
|
276
|
+
* before invoking this method.
|
|
277
|
+
*
|
|
311
278
|
* It is possible to create a recursive IPNS record by passing:
|
|
312
279
|
*
|
|
313
|
-
* - A
|
|
314
|
-
* - A
|
|
315
|
-
* - A CID with the libp2p-key codec and Identity or SHA256 hash algorithms
|
|
316
|
-
* - A Multihash with the Identity or SHA256 hash algorithms
|
|
280
|
+
* - A CID with the libp2p-key codec
|
|
281
|
+
* - A Multihash
|
|
317
282
|
* - A string IPNS key (e.g. `/ipns/Qmfoo`)
|
|
318
283
|
*
|
|
319
284
|
* @example
|
|
@@ -332,38 +297,36 @@ export interface IPNS {
|
|
|
332
297
|
* console.info(result) // { answer: ... }
|
|
333
298
|
* ```
|
|
334
299
|
*/
|
|
335
|
-
publish(keyName: string, value: CID | PublicKey | MultihashDigest
|
|
300
|
+
publish(keyName: string, value: CID | PublicKey | MultihashDigest | string, options?: PublishOptions): Promise<PublishResult>
|
|
336
301
|
|
|
337
302
|
/**
|
|
338
|
-
* Accepts a
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
303
|
+
* Accepts a multihash of a public key, a libp2p-key CID containing the
|
|
304
|
+
* multihash of a public key, or an IPNS name in it's string representation
|
|
305
|
+
* and recursively resolves IPNS records until a non-recursive record is found
|
|
306
|
+
* (e.g. the value can be parsed as a string that does not start with
|
|
307
|
+
* `/ipns/`).
|
|
343
308
|
*/
|
|
344
|
-
resolve(
|
|
309
|
+
resolve(name: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options?: ResolveOptions): AsyncGenerator<ResolveResult>
|
|
345
310
|
|
|
346
311
|
/**
|
|
347
312
|
* Stop republishing of an IPNS record
|
|
348
313
|
*
|
|
349
|
-
* This will delete the last signed IPNS record from the datastore
|
|
350
|
-
* key will remain in the keychain.
|
|
314
|
+
* This will delete the last signed IPNS record from the datastore.
|
|
351
315
|
*
|
|
352
316
|
* Note that the record may still be resolved by other peers until it expires
|
|
353
|
-
* or is no longer valid.
|
|
317
|
+
* or is otherwise no longer valid.
|
|
354
318
|
*/
|
|
355
319
|
unpublish(keyName: string, options?: AbortOptions): Promise<void>
|
|
356
320
|
}
|
|
357
321
|
|
|
358
322
|
export type { IPNSRouting } from './routing/index.ts'
|
|
359
|
-
|
|
360
|
-
export type { IPNSRecord } from 'ipns'
|
|
323
|
+
export type { IPNSRecord } from './records.ts'
|
|
361
324
|
|
|
362
325
|
export interface IPNSComponents {
|
|
363
326
|
datastore: Datastore
|
|
364
327
|
routing: Routing
|
|
365
328
|
logger: ComponentLogger
|
|
366
|
-
|
|
329
|
+
keychain: Keychain
|
|
367
330
|
events: TypedEventEmitter<HeliaEvents> // Helia event bus
|
|
368
331
|
}
|
|
369
332
|
|
|
@@ -414,5 +377,4 @@ export function ipnsResolver (components: IPNSResolverComponents, options: IPNSR
|
|
|
414
377
|
})
|
|
415
378
|
}
|
|
416
379
|
|
|
417
|
-
export {
|
|
418
|
-
export { ipnsSelector } from 'ipns/selector'
|
|
380
|
+
export type { IPNSRoutingProgressEvents }
|
package/src/ipns/publisher.ts
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isPeerId } from '@libp2p/interface'
|
|
3
|
-
import { createIPNSRecord, marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from 'ipns'
|
|
4
|
-
import { CID } from 'multiformats/cid'
|
|
1
|
+
import { base36 } from 'multiformats/bases/base36'
|
|
5
2
|
import { CustomProgressEvent } from 'progress-events'
|
|
6
3
|
import { DEFAULT_LIFETIME_MS, DEFAULT_TTL_NS } from '../constants.ts'
|
|
7
|
-
import
|
|
4
|
+
import { createIPNSRecord } from '../records.ts'
|
|
5
|
+
import { marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from '../utils.ts'
|
|
6
|
+
import type { PublishResult, PublishOptions } from '../index.ts'
|
|
8
7
|
import type { LocalStore } from '../local-store.ts'
|
|
9
8
|
import type { IPNSRouting } from '../routing/index.ts'
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
9
|
+
import type { Keychain, PrivateKey } from '@helia/interface'
|
|
10
|
+
import type { AbortOptions, ComponentLogger } from '@libp2p/interface'
|
|
12
11
|
import type { Datastore } from 'interface-datastore'
|
|
13
|
-
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
14
12
|
|
|
15
13
|
export interface IPNSPublisherComponents {
|
|
16
14
|
datastore: Datastore
|
|
17
15
|
logger: ComponentLogger
|
|
18
|
-
|
|
16
|
+
keychain: Keychain
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export interface IPNSPublisherInit {
|
|
@@ -29,32 +27,29 @@ export class IPNSPublisher {
|
|
|
29
27
|
private readonly keychain: Keychain
|
|
30
28
|
|
|
31
29
|
constructor (components: IPNSPublisherComponents, init: IPNSPublisherInit) {
|
|
32
|
-
this.keychain = components.
|
|
30
|
+
this.keychain = components.keychain
|
|
33
31
|
this.localStore = init.localStore
|
|
34
32
|
this.routers = init.routers
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
async publish (keyName: string, value:
|
|
35
|
+
async publish (keyName: string, value: string, options: PublishOptions = {}): Promise<PublishResult> {
|
|
38
36
|
try {
|
|
39
|
-
const
|
|
37
|
+
const key = await this.#loadOrCreateKey(keyName, options)
|
|
38
|
+
const digest = key.publicKey.toMultihash()
|
|
39
|
+
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
40
40
|
let sequenceNumber = 1n
|
|
41
|
-
const routingKey = multihashToIPNSRoutingKey(privKey.publicKey.toMultihash())
|
|
42
41
|
|
|
43
42
|
if (await this.localStore.has(routingKey, options)) {
|
|
44
43
|
// if we have published under this key before, increment the sequence number
|
|
45
44
|
const { record } = await this.localStore.get(routingKey, options)
|
|
46
|
-
const existingRecord = unmarshalIPNSRecord(record)
|
|
45
|
+
const existingRecord = await unmarshalIPNSRecord(routingKey, record, this.keychain, options)
|
|
47
46
|
sequenceNumber = existingRecord.sequence + 1n
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
if (isPeerId(value)) {
|
|
51
|
-
value = value.toCID()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
49
|
// convert ttl from milliseconds to nanoseconds as createIPNSRecord expects
|
|
55
50
|
const ttlNs = options.ttl != null ? BigInt(options.ttl) * 1_000_000n : DEFAULT_TTL_NS
|
|
56
51
|
const lifetime = options.lifetime ?? DEFAULT_LIFETIME_MS
|
|
57
|
-
const record = await createIPNSRecord(
|
|
52
|
+
const record = await createIPNSRecord(key, value, sequenceNumber, lifetime, { ...options, ttlNs })
|
|
58
53
|
const marshaledRecord = marshalIPNSRecord(record)
|
|
59
54
|
|
|
60
55
|
if (options.offline === true) {
|
|
@@ -68,8 +63,12 @@ export class IPNSPublisher {
|
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
return {
|
|
71
|
-
record
|
|
72
|
-
|
|
66
|
+
record: {
|
|
67
|
+
...record,
|
|
68
|
+
publicKey: key.publicKey
|
|
69
|
+
},
|
|
70
|
+
name: `/ipns/${key.publicKey.toCID().toString(base36)}`,
|
|
71
|
+
publicKey: key.publicKey
|
|
73
72
|
}
|
|
74
73
|
} catch (err: any) {
|
|
75
74
|
options.onProgress?.(new CustomProgressEvent<Error>('ipns:publish:error', err))
|
|
@@ -77,20 +76,26 @@ export class IPNSPublisher {
|
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Create the private key if it is not in the keychain already, defaulting to
|
|
81
|
+
* Ed25519 keys
|
|
82
|
+
*/
|
|
83
|
+
async #loadOrCreateKey (keyName: string, options?: AbortOptions): Promise<PrivateKey> {
|
|
81
84
|
try {
|
|
82
|
-
return await this.keychain.exportKey(keyName)
|
|
85
|
+
return await this.keychain.exportKey(keyName, options)
|
|
83
86
|
} catch (err: any) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
if (err.name === 'NotFoundError') {
|
|
88
|
+
// create a new key
|
|
89
|
+
return this.keychain.generateKey(keyName, options)
|
|
90
|
+
} else {
|
|
91
|
+
throw err
|
|
92
|
+
}
|
|
88
93
|
}
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
async unpublish (keyName: string, options?: AbortOptions): Promise<void> {
|
|
92
|
-
const
|
|
93
|
-
const digest = publicKey.toMultihash()
|
|
97
|
+
const key = await this.keychain.exportKey(keyName, options)
|
|
98
|
+
const digest = key.publicKey.toMultihash()
|
|
94
99
|
const routingKey = multihashToIPNSRoutingKey(digest)
|
|
95
100
|
await this.localStore.delete(routingKey, options)
|
|
96
101
|
}
|