@helia/ipns 9.2.1-ae98a4f4 → 9.2.1-ed6c3b79
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 +62 -99
- 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 +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 +24 -23
- package/src/errors.ts +40 -25
- package/src/index.ts +63 -100
- 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-ed6c3b79",
|
|
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/
|
|
81
|
-
"@libp2p/
|
|
82
|
-
"@libp2p/
|
|
83
|
-
"@libp2p/
|
|
84
|
-
"@libp2p/
|
|
85
|
-
"@libp2p/
|
|
86
|
-
"
|
|
87
|
-
"@libp2p/utils": "^7.0.15",
|
|
79
|
+
"@helia/interface": "6.2.1-ed6c3b79",
|
|
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": "^48.0.
|
|
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
|
}
|
|
@@ -257,32 +228,25 @@ export interface ResolveOptions extends AbortOptions, ProgressOptions<ResolvePro
|
|
|
257
228
|
}
|
|
258
229
|
|
|
259
230
|
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
231
|
/**
|
|
273
232
|
* The resolved record
|
|
274
233
|
*/
|
|
275
234
|
record: IPNSRecord
|
|
276
235
|
}
|
|
277
236
|
|
|
278
|
-
export interface
|
|
237
|
+
export interface PublishResult {
|
|
279
238
|
/**
|
|
280
239
|
* The published record
|
|
281
240
|
*/
|
|
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
|
}
|
|
@@ -295,7 +259,7 @@ export interface IPNSResolver {
|
|
|
295
259
|
* Ed25519, secp256k1 or RSA PeerId and recursively resolves the IPNS record
|
|
296
260
|
* corresponding to that key until a value is found.
|
|
297
261
|
*/
|
|
298
|
-
resolve(key: CID<unknown, 0x72
|
|
262
|
+
resolve(key: CID<unknown, 0x72> | MultihashDigest, options?: ResolveOptions): AsyncGenerator<ResolveResult>
|
|
299
263
|
}
|
|
300
264
|
|
|
301
265
|
export interface IPNS {
|
|
@@ -308,12 +272,14 @@ export interface IPNS {
|
|
|
308
272
|
* Creates and publishes an IPNS record that will resolve the passed value
|
|
309
273
|
* signed by a key stored in the libp2p keychain under the passed key name.
|
|
310
274
|
*
|
|
275
|
+
* If the key does not exist, a new Ed25519 key will be created. To use a
|
|
276
|
+
* different key types, ensure the key is created and stored in the keychain
|
|
277
|
+
* before invoking this method.
|
|
278
|
+
*
|
|
311
279
|
* It is possible to create a recursive IPNS record by passing:
|
|
312
280
|
*
|
|
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
|
|
281
|
+
* - A CID with the libp2p-key codec
|
|
282
|
+
* - A Multihash
|
|
317
283
|
* - A string IPNS key (e.g. `/ipns/Qmfoo`)
|
|
318
284
|
*
|
|
319
285
|
* @example
|
|
@@ -332,38 +298,36 @@ export interface IPNS {
|
|
|
332
298
|
* console.info(result) // { answer: ... }
|
|
333
299
|
* ```
|
|
334
300
|
*/
|
|
335
|
-
publish(keyName: string, value: CID | PublicKey | MultihashDigest
|
|
301
|
+
publish(keyName: string, value: CID | PublicKey | MultihashDigest | string, options?: PublishOptions): Promise<PublishResult>
|
|
336
302
|
|
|
337
303
|
/**
|
|
338
|
-
* Accepts a
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
304
|
+
* Accepts a multihash of a public key, a libp2p-key CID containing the
|
|
305
|
+
* multihash of a public key, or an IPNS name in it's string representation
|
|
306
|
+
* and recursively resolves IPNS records until a non-recursive record is found
|
|
307
|
+
* (e.g. the value can be parsed as a string that does not start with
|
|
308
|
+
* `/ipns/`).
|
|
343
309
|
*/
|
|
344
|
-
resolve(
|
|
310
|
+
resolve(name: CID<unknown, 0x72> | PublicKey | MultihashDigest | string, options?: ResolveOptions): AsyncGenerator<ResolveResult>
|
|
345
311
|
|
|
346
312
|
/**
|
|
347
313
|
* Stop republishing of an IPNS record
|
|
348
314
|
*
|
|
349
|
-
* This will delete the last signed IPNS record from the datastore
|
|
350
|
-
* key will remain in the keychain.
|
|
315
|
+
* This will delete the last signed IPNS record from the datastore.
|
|
351
316
|
*
|
|
352
317
|
* Note that the record may still be resolved by other peers until it expires
|
|
353
|
-
* or is no longer valid.
|
|
318
|
+
* or is otherwise no longer valid.
|
|
354
319
|
*/
|
|
355
320
|
unpublish(keyName: string, options?: AbortOptions): Promise<void>
|
|
356
321
|
}
|
|
357
322
|
|
|
358
323
|
export type { IPNSRouting } from './routing/index.ts'
|
|
359
|
-
|
|
360
|
-
export type { IPNSRecord } from 'ipns'
|
|
324
|
+
export type { IPNSRecord } from './records.ts'
|
|
361
325
|
|
|
362
326
|
export interface IPNSComponents {
|
|
363
327
|
datastore: Datastore
|
|
364
328
|
routing: Routing
|
|
365
329
|
logger: ComponentLogger
|
|
366
|
-
|
|
330
|
+
keychain: Keychain
|
|
367
331
|
events: TypedEventEmitter<HeliaEvents> // Helia event bus
|
|
368
332
|
}
|
|
369
333
|
|
|
@@ -414,5 +378,4 @@ export function ipnsResolver (components: IPNSResolverComponents, options: IPNSR
|
|
|
414
378
|
})
|
|
415
379
|
}
|
|
416
380
|
|
|
417
|
-
export {
|
|
418
|
-
export { ipnsSelector } from 'ipns/selector'
|
|
381
|
+
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
|
}
|