@helia/ipns 9.2.1 → 10.0.0-3d869e9d
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 +21 -57
- 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 +67 -105
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -60
- package/dist/src/index.js.map +1 -1
- package/dist/src/ipns/publisher.d.ts +4 -8
- 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 +24 -23
- package/src/errors.ts +40 -25
- package/src/index.ts +68 -106
- package/src/ipns/publisher.ts +33 -28
- package/src/ipns/republisher.ts +24 -13
- package/src/ipns/resolver.ts +41 -89
- 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
- package/dist/typedoc-urls.json +0 -51
|
@@ -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": "
|
|
3
|
+
"version": "10.0.0-3d869e9d",
|
|
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": "
|
|
80
|
-
"@libp2p/
|
|
81
|
-
"@libp2p/
|
|
82
|
-
"@libp2p/
|
|
83
|
-
"@libp2p/
|
|
84
|
-
"@libp2p/
|
|
85
|
-
"@libp2p/
|
|
86
|
-
"
|
|
87
|
-
"@libp2p/utils": "^7.0.15",
|
|
79
|
+
"@helia/interface": "7.0.0-3d869e9d",
|
|
80
|
+
"@libp2p/fetch": "^4.1.6",
|
|
81
|
+
"@libp2p/interface": "^3.2.3",
|
|
82
|
+
"@libp2p/kad-dht": "^16.3.2",
|
|
83
|
+
"@libp2p/logger": "^6.2.8",
|
|
84
|
+
"@libp2p/peer-collections": "^7.0.21",
|
|
85
|
+
"@libp2p/utils": "^7.2.2",
|
|
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.11",
|
|
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
|
|
@@ -104,29 +105,30 @@
|
|
|
104
105
|
* may fail to be published with "Insufficient peers" errors.
|
|
105
106
|
*
|
|
106
107
|
* ```TypeScript
|
|
107
|
-
* import { createHelia, libp2pDefaults } from 'helia'
|
|
108
108
|
* import { ipns } from '@helia/ipns'
|
|
109
109
|
* import { pubsub } from '@helia/ipns/routing'
|
|
110
|
+
* import { withLibp2p, libp2pDefaults } from '@helia/libp2p'
|
|
110
111
|
* import { unixfs } from '@helia/unixfs'
|
|
111
|
-
* import { floodsub } from '@libp2p/floodsub'
|
|
112
112
|
* import { generateKeyPair } from '@libp2p/crypto/keys'
|
|
113
|
+
* import { floodsub } from '@libp2p/floodsub'
|
|
114
|
+
* import { createHelia } from 'helia'
|
|
115
|
+
* import type { Helia } from '@helia/interface'
|
|
113
116
|
* import type { PubSub } from '@helia/ipns/routing'
|
|
117
|
+
* import type { DefaultLibp2pServices } from '@helia/libp2p'
|
|
118
|
+
* import type { FloodSub } from '@libp2p/floodsub'
|
|
114
119
|
* import type { Libp2p } from '@libp2p/interface'
|
|
115
|
-
* import type { DefaultLibp2pServices } from 'helia'
|
|
116
120
|
*
|
|
117
|
-
* const libp2pOptions = libp2pDefaults()
|
|
121
|
+
* const libp2pOptions = libp2pDefaults() as any
|
|
118
122
|
* libp2pOptions.services.pubsub = floodsub()
|
|
119
123
|
*
|
|
120
|
-
* const helia = await
|
|
121
|
-
*
|
|
122
|
-
* })
|
|
124
|
+
* const helia = await withLibp2p<Helia, { pubsub: FloodSub }>(createHelia(), libp2pOptions).start()
|
|
125
|
+
*
|
|
123
126
|
* const name = ipns(helia, {
|
|
124
127
|
* routers: [
|
|
125
128
|
* pubsub(helia)
|
|
126
129
|
* ]
|
|
127
130
|
* })
|
|
128
131
|
*
|
|
129
|
-
*
|
|
130
132
|
* // store some data to publish
|
|
131
133
|
* const fs = unixfs(helia)
|
|
132
134
|
* const cid = await fs.addBytes(Uint8Array.from([0, 1, 2, 3, 4]))
|
|
@@ -135,51 +137,12 @@
|
|
|
135
137
|
* const { publicKey } = await name.publish('key-1', cid)
|
|
136
138
|
*
|
|
137
139
|
* // 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
|
-
* )
|
|
140
|
+
* for await (const result of name.resolve(publicKey)) {
|
|
141
|
+
* console.info(result.record.value)
|
|
142
|
+
* }
|
|
179
143
|
* ```
|
|
180
144
|
*/
|
|
181
145
|
|
|
182
|
-
import { ipnsValidator } from 'ipns/validator'
|
|
183
146
|
import { CID } from 'multiformats/cid'
|
|
184
147
|
import { IPNSResolver as IPNSResolverClass } from './ipns/resolver.ts'
|
|
185
148
|
import { IPNS as IPNSClass } from './ipns.ts'
|
|
@@ -187,12 +150,12 @@ import { localStore } from './local-store.ts'
|
|
|
187
150
|
import { helia } from './routing/index.ts'
|
|
188
151
|
import { localStoreRouting } from './routing/local-store.ts'
|
|
189
152
|
import type { IPNSResolverComponents } from './ipns/resolver.ts'
|
|
153
|
+
import type { IPNSRecord } from './records.ts'
|
|
190
154
|
import type { IPNSRouting, IPNSRoutingProgressEvents } from './routing/index.ts'
|
|
191
|
-
import type { Routing, HeliaEvents } from '@helia/interface'
|
|
192
|
-
import type {
|
|
193
|
-
import type {
|
|
155
|
+
import type { Routing, HeliaEvents, Keychain, PublicKey } from '@helia/interface'
|
|
156
|
+
import type { ComponentLogger, TypedEventEmitter } from '@libp2p/interface'
|
|
157
|
+
import type { AbortOptions } from 'abort-error'
|
|
194
158
|
import type { Datastore } from 'interface-datastore'
|
|
195
|
-
import type { IPNSRecord } from 'ipns'
|
|
196
159
|
import type { MultihashDigest } from 'multiformats/hashes/interface'
|
|
197
160
|
import type { ProgressEvent, ProgressOptions } from 'progress-events'
|
|
198
161
|
|
|
@@ -214,23 +177,31 @@ export type DatastoreProgressEvents =
|
|
|
214
177
|
|
|
215
178
|
export interface PublishOptions extends AbortOptions, ProgressOptions<PublishProgressEvents | IPNSRoutingProgressEvents> {
|
|
216
179
|
/**
|
|
217
|
-
* Time duration of the signature validity in ms
|
|
180
|
+
* Time duration of the signature validity in ms
|
|
181
|
+
*
|
|
182
|
+
* @default 172_800_000
|
|
218
183
|
*/
|
|
219
184
|
lifetime?: number
|
|
220
185
|
|
|
221
186
|
/**
|
|
222
|
-
* Only publish to a local datastore
|
|
187
|
+
* Only publish to a local datastore
|
|
188
|
+
*
|
|
189
|
+
* @default false
|
|
223
190
|
*/
|
|
224
191
|
offline?: boolean
|
|
225
192
|
|
|
226
193
|
/**
|
|
227
194
|
* By default a IPNS V1 and a V2 signature is added to every record. Pass
|
|
228
|
-
* false here to only add a V2 signature.
|
|
195
|
+
* false here to only add a V2 signature.
|
|
196
|
+
*
|
|
197
|
+
* @default true
|
|
229
198
|
*/
|
|
230
199
|
v1Compatible?: boolean
|
|
231
200
|
|
|
232
201
|
/**
|
|
233
|
-
* The TTL of the record in ms
|
|
202
|
+
* The TTL of the record in ms
|
|
203
|
+
*
|
|
204
|
+
* @default 300_000
|
|
234
205
|
*/
|
|
235
206
|
ttl?: number
|
|
236
207
|
}
|
|
@@ -240,7 +211,7 @@ export interface IPNSRecordMetadata {
|
|
|
240
211
|
lifetime: number
|
|
241
212
|
}
|
|
242
213
|
|
|
243
|
-
export interface
|
|
214
|
+
export interface IPNSResolveOptions extends AbortOptions, ProgressOptions<ResolveProgressEvents | IPNSRoutingProgressEvents> {
|
|
244
215
|
/**
|
|
245
216
|
* Do not query the network for the IPNS record
|
|
246
217
|
*
|
|
@@ -256,19 +227,7 @@ export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolvePro
|
|
|
256
227
|
nocache?: boolean
|
|
257
228
|
}
|
|
258
229
|
|
|
259
|
-
export interface
|
|
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 {
|
|
230
|
+
export interface IPNSResolveResult {
|
|
272
231
|
/**
|
|
273
232
|
* The resolved record
|
|
274
233
|
*/
|
|
@@ -282,20 +241,24 @@ export interface IPNSPublishResult {
|
|
|
282
241
|
record: IPNSRecord
|
|
283
242
|
|
|
284
243
|
/**
|
|
285
|
-
* The
|
|
244
|
+
* The IPNS name that can be used to resolve this record
|
|
245
|
+
*/
|
|
246
|
+
name: string
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* The public key that was used to sign and publish the record
|
|
286
250
|
*/
|
|
287
251
|
publicKey: PublicKey
|
|
288
252
|
}
|
|
289
253
|
|
|
290
254
|
export interface IPNSResolver {
|
|
291
255
|
/**
|
|
292
|
-
* Accepts a
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* corresponding to that key until a value is found.
|
|
256
|
+
* Accepts a CID with the libp2p-key codec and either the identity hash (for
|
|
257
|
+
* Ed25519 and secp256k1 public keys) or a SHA256 hash (for RSA public keys),
|
|
258
|
+
* or the multihash of a libp2p-key encoded CID and recursively resolves the
|
|
259
|
+
* IPNS record corresponding to that key until a value is found.
|
|
297
260
|
*/
|
|
298
|
-
resolve(key: CID
|
|
261
|
+
resolve(key: CID | MultihashDigest, options?: IPNSResolveOptions): AsyncGenerator<IPNSResolveResult>
|
|
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<IPNSPublishResult>
|
|
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 | PublicKey | MultihashDigest | string, options?: IPNSResolveOptions): AsyncGenerator<IPNSResolveResult>
|
|
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'
|
|
4
|
+
import { createIPNSRecord } from '../records.ts'
|
|
5
|
+
import { marshalIPNSRecord, multihashToIPNSRoutingKey, unmarshalIPNSRecord } from '../utils.ts'
|
|
7
6
|
import type { IPNSPublishResult, 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<IPNSPublishResult> {
|
|
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
|
}
|