@atproto/lex-resolver 0.0.0
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lex-resolver-error.d.ts +8 -0
- package/dist/lex-resolver-error.d.ts.map +1 -0
- package/dist/lex-resolver-error.js +15 -0
- package/dist/lex-resolver-error.js.map +1 -0
- package/dist/lex-resolver.d.ts +20 -0
- package/dist/lex-resolver.d.ts.map +1 -0
- package/dist/lex-resolver.js +106 -0
- package/dist/lex-resolver.js.map +1 -0
- package/package.json +44 -0
- package/src/index.ts +2 -0
- package/src/lex-resolver-error.ts +13 -0
- package/src/lex-resolver.ts +171 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAA;AACjC,cAAc,yBAAyB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
tslib_1.__exportStar(require("./lex-resolver.js"), exports);
|
|
5
|
+
tslib_1.__exportStar(require("./lex-resolver-error.js"), exports);
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,4DAAiC;AACjC,kEAAuC","sourcesContent":["export * from './lex-resolver.js'\nexport * from './lex-resolver-error.js'\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NSID } from '@atproto/syntax';
|
|
2
|
+
export declare class LexResolverError extends Error {
|
|
3
|
+
readonly nsid: NSID;
|
|
4
|
+
readonly description: string;
|
|
5
|
+
name: string;
|
|
6
|
+
constructor(nsid: NSID, description?: string, options?: ErrorOptions);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=lex-resolver-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lex-resolver-error.d.ts","sourceRoot":"","sources":["../src/lex-resolver-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAEtC,qBAAa,gBAAiB,SAAQ,KAAK;aAIvB,IAAI,EAAE,IAAI;aACV,WAAW;IAJ7B,IAAI,SAAqB;gBAGP,IAAI,EAAE,IAAI,EACV,WAAW,SAAuC,EAClE,OAAO,CAAC,EAAE,YAAY;CAIzB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LexResolverError = void 0;
|
|
4
|
+
class LexResolverError extends Error {
|
|
5
|
+
nsid;
|
|
6
|
+
description;
|
|
7
|
+
name = 'LexResolverError';
|
|
8
|
+
constructor(nsid, description = `Could not resolve Lexicon for NSID`, options) {
|
|
9
|
+
super(`${description} (${nsid})`, options);
|
|
10
|
+
this.nsid = nsid;
|
|
11
|
+
this.description = description;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.LexResolverError = LexResolverError;
|
|
15
|
+
//# sourceMappingURL=lex-resolver-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lex-resolver-error.js","sourceRoot":"","sources":["../src/lex-resolver-error.ts"],"names":[],"mappings":";;;AAEA,MAAa,gBAAiB,SAAQ,KAAK;IAIvB;IACA;IAJlB,IAAI,GAAG,kBAAkB,CAAA;IAEzB,YACkB,IAAU,EACV,cAAc,oCAAoC,EAClE,OAAsB;QAEtB,KAAK,CAAC,GAAG,WAAW,KAAK,IAAI,GAAG,EAAE,OAAO,CAAC,CAAA;QAJ1B,SAAI,GAAJ,IAAI,CAAM;QACV,gBAAW,GAAX,WAAW,CAAuC;IAIpE,CAAC;CACF;AAVD,4CAUC","sourcesContent":["import { NSID } from '@atproto/syntax'\n\nexport class LexResolverError extends Error {\n name = 'LexResolverError'\n\n constructor(\n public readonly nsid: NSID,\n public readonly description = `Could not resolve Lexicon for NSID`,\n options?: ErrorOptions,\n ) {\n super(`${description} (${nsid})`, options)\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LexiconDocument } from '@atproto/lex-document';
|
|
2
|
+
import { AtUri, NSID } from '@atproto/syntax';
|
|
3
|
+
import { CreateDidResolverOptions, Did, DidResolver, ResolveDidOptions } from '@atproto-labs/did-resolver';
|
|
4
|
+
export type LexResolverOptions = CreateDidResolverOptions & {
|
|
5
|
+
didAuthority?: Did;
|
|
6
|
+
};
|
|
7
|
+
export { AtUri, NSID };
|
|
8
|
+
export type { LexiconDocument, ResolveDidOptions };
|
|
9
|
+
export declare class LexResolver {
|
|
10
|
+
protected readonly options: LexResolverOptions;
|
|
11
|
+
protected readonly didResolver: DidResolver<'plc' | 'web'>;
|
|
12
|
+
constructor(options: LexResolverOptions);
|
|
13
|
+
get(nsidStr: NSID | string, options?: ResolveDidOptions): Promise<{
|
|
14
|
+
uri: AtUri;
|
|
15
|
+
document: LexiconDocument;
|
|
16
|
+
}>;
|
|
17
|
+
resolve(nsidStr: NSID | string): Promise<AtUri>;
|
|
18
|
+
fetch(uriStr: AtUri | string, options?: ResolveDidOptions): Promise<LexiconDocument>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=lex-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lex-resolver.d.ts","sourceRoot":"","sources":["../src/lex-resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAyB,MAAM,uBAAuB,CAAA;AAC9E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,EACL,wBAAwB,EACxB,GAAG,EACH,WAAW,EACX,iBAAiB,EAIlB,MAAM,4BAA4B,CAAA;AAGnC,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,GAAG;IAC1D,YAAY,CAAC,EAAE,GAAG,CAAA;CACnB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,CAAA;AAElD,qBAAa,WAAW;IAGV,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB;IAF1D,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,KAAK,GAAG,KAAK,CAAC,CAAA;gBAE3B,OAAO,EAAE,kBAAkB;IAIpD,GAAG,CACP,OAAO,EAAE,IAAI,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC;QACT,GAAG,EAAE,KAAK,CAAA;QACV,QAAQ,EAAE,eAAe,CAAA;KAC1B,CAAC;IAMI,OAAO,CAAC,OAAO,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAe/C,KAAK,CACT,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,eAAe,CAAC;CAmE5B"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LexResolver = exports.NSID = exports.AtUri = void 0;
|
|
4
|
+
const promises_1 = require("node:dns/promises");
|
|
5
|
+
const lex_client_1 = require("@atproto/lex-client");
|
|
6
|
+
const lex_document_1 = require("@atproto/lex-document");
|
|
7
|
+
const syntax_1 = require("@atproto/syntax");
|
|
8
|
+
Object.defineProperty(exports, "AtUri", { enumerable: true, get: function () { return syntax_1.AtUri; } });
|
|
9
|
+
Object.defineProperty(exports, "NSID", { enumerable: true, get: function () { return syntax_1.NSID; } });
|
|
10
|
+
const did_resolver_1 = require("@atproto-labs/did-resolver");
|
|
11
|
+
const lex_resolver_error_js_1 = require("./lex-resolver-error.js");
|
|
12
|
+
class LexResolver {
|
|
13
|
+
options;
|
|
14
|
+
didResolver;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.didResolver = (0, did_resolver_1.createDidResolver)(options);
|
|
18
|
+
}
|
|
19
|
+
async get(nsidStr, options) {
|
|
20
|
+
const uri = await this.resolve(nsidStr);
|
|
21
|
+
const document = await this.fetch(uri, options);
|
|
22
|
+
return { uri, document };
|
|
23
|
+
}
|
|
24
|
+
async resolve(nsidStr) {
|
|
25
|
+
const nsid = syntax_1.NSID.from(nsidStr);
|
|
26
|
+
const did = this.options.didAuthority ??
|
|
27
|
+
(await resolveLexiconDidAuthority(nsid).catch((cause) => {
|
|
28
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID authority for Lexicon`, { cause });
|
|
29
|
+
}));
|
|
30
|
+
return syntax_1.AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString());
|
|
31
|
+
}
|
|
32
|
+
async fetch(uriStr, options) {
|
|
33
|
+
const uri = typeof uriStr === 'string' ? new syntax_1.AtUri(uriStr) : uriStr;
|
|
34
|
+
const { did, nsid } = parseLexiconUri(uri);
|
|
35
|
+
if (this.options.didAuthority && this.options.didAuthority !== did) {
|
|
36
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`);
|
|
37
|
+
}
|
|
38
|
+
const didDocument = await this.didResolver
|
|
39
|
+
.resolve(did, options)
|
|
40
|
+
.catch((cause) => {
|
|
41
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve DID document for ${uri}`, { cause });
|
|
42
|
+
});
|
|
43
|
+
let service;
|
|
44
|
+
try {
|
|
45
|
+
service = (0, did_resolver_1.extractPdsUrl)(didDocument);
|
|
46
|
+
}
|
|
47
|
+
catch (cause) {
|
|
48
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `No PDS service endpoint found in DID document for ${uri}`, { cause });
|
|
49
|
+
}
|
|
50
|
+
const agent = (0, lex_client_1.buildAgent)({
|
|
51
|
+
service,
|
|
52
|
+
fetch: this.options.fetch,
|
|
53
|
+
});
|
|
54
|
+
// TODO: use com.atproto.sync.getRecord and check signature using
|
|
55
|
+
// DID document key
|
|
56
|
+
const response = await new lex_client_1.Client(agent)
|
|
57
|
+
.getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })
|
|
58
|
+
.catch((cause) => {
|
|
59
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to fetch Lexicon document at ${uri}`, { cause });
|
|
60
|
+
});
|
|
61
|
+
const result = lex_document_1.lexiconDocumentSchema.validate(response.body.value);
|
|
62
|
+
if (!result.success) {
|
|
63
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
|
|
64
|
+
cause: result.error,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const document = result.value;
|
|
68
|
+
if (document.id !== nsid.toString()) {
|
|
69
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Invalid document id "${document.id}" for ${uri}`);
|
|
70
|
+
}
|
|
71
|
+
return document;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.LexResolver = LexResolver;
|
|
75
|
+
function parseLexiconUri(uri) {
|
|
76
|
+
// Validate input URI
|
|
77
|
+
const nsid = syntax_1.NSID.from(uri.rkey);
|
|
78
|
+
const did = uri.host;
|
|
79
|
+
(0, did_resolver_1.assertDid)(did);
|
|
80
|
+
return { did, nsid };
|
|
81
|
+
}
|
|
82
|
+
async function resolveLexiconDidAuthority(nsid) {
|
|
83
|
+
try {
|
|
84
|
+
return await getDomainTxtDid(`_lexicon.${nsid.authority}`);
|
|
85
|
+
}
|
|
86
|
+
catch (cause) {
|
|
87
|
+
throw new lex_resolver_error_js_1.LexResolverError(nsid, `Failed to resolve lexicon DID authority`, { cause });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getDomainTxtDid(domain) {
|
|
91
|
+
return parseDnsResult(await (0, promises_1.resolveTxt)(domain));
|
|
92
|
+
}
|
|
93
|
+
function parseDnsResult(chunkedResults) {
|
|
94
|
+
const didDefs = chunkedResults
|
|
95
|
+
.map((chunks) => chunks.join(''))
|
|
96
|
+
.filter((i) => i.startsWith('did='));
|
|
97
|
+
if (didDefs.length === 1) {
|
|
98
|
+
const did = didDefs[0].slice(4);
|
|
99
|
+
(0, did_resolver_1.assertDid)(did);
|
|
100
|
+
return did;
|
|
101
|
+
}
|
|
102
|
+
throw didDefs.length > 1
|
|
103
|
+
? new Error('Multiple DIDs found in DNS TXT records')
|
|
104
|
+
: new Error('No DID found in DNS TXT records');
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=lex-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lex-resolver.js","sourceRoot":"","sources":["../src/lex-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { resolveTxt } from 'node:dns/promises'\nimport { Client, buildAgent } from '@atproto/lex-client'\nimport { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'\nimport { AtUri, NSID } from '@atproto/syntax'\nimport {\n CreateDidResolverOptions,\n Did,\n DidResolver,\n ResolveDidOptions,\n assertDid,\n createDidResolver,\n extractPdsUrl,\n} from '@atproto-labs/did-resolver'\nimport { LexResolverError } from './lex-resolver-error.js'\n\nexport type LexResolverOptions = CreateDidResolverOptions & {\n didAuthority?: Did\n}\n\nexport { AtUri, NSID }\nexport type { LexiconDocument, ResolveDidOptions }\n\nexport class LexResolver {\n protected readonly didResolver: DidResolver<'plc' | 'web'>\n\n constructor(protected readonly options: LexResolverOptions) {\n this.didResolver = createDidResolver(options)\n }\n\n async get(\n nsidStr: NSID | string,\n options?: ResolveDidOptions,\n ): Promise<{\n uri: AtUri\n document: LexiconDocument\n }> {\n const uri = await this.resolve(nsidStr)\n const document = await this.fetch(uri, options)\n return { uri, document }\n }\n\n async resolve(nsidStr: NSID | string): Promise<AtUri> {\n const nsid = NSID.from(nsidStr)\n const did =\n this.options.didAuthority ??\n (await resolveLexiconDidAuthority(nsid).catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to resolve DID authority for Lexicon`,\n { cause },\n )\n }))\n\n return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())\n }\n\n async fetch(\n uriStr: AtUri | string,\n options?: ResolveDidOptions,\n ): Promise<LexiconDocument> {\n const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr\n const { did, nsid } = parseLexiconUri(uri)\n\n if (this.options.didAuthority && this.options.didAuthority !== did) {\n throw new LexResolverError(\n nsid,\n `DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`,\n )\n }\n\n const didDocument = await this.didResolver\n .resolve(did, options)\n .catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to resolve DID document for ${uri}`,\n { cause },\n )\n })\n\n let service: URL\n try {\n service = extractPdsUrl(didDocument)\n } catch (cause) {\n throw new LexResolverError(\n nsid,\n `No PDS service endpoint found in DID document for ${uri}`,\n { cause },\n )\n }\n\n const agent = buildAgent({\n service,\n fetch: this.options.fetch,\n })\n\n // TODO: use com.atproto.sync.getRecord and check signature using\n // DID document key\n const response = await new Client(agent)\n .getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })\n .catch((cause) => {\n throw new LexResolverError(\n nsid,\n `Failed to fetch Lexicon document at ${uri}`,\n { cause },\n )\n })\n\n const result = lexiconDocumentSchema.validate(response.body.value)\n if (!result.success) {\n throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {\n cause: result.error,\n })\n }\n\n const document = result.value\n\n if (document.id !== nsid.toString()) {\n throw new LexResolverError(\n nsid,\n `Invalid document id \"${document.id}\" for ${uri}`,\n )\n }\n\n return document\n }\n}\n\nfunction parseLexiconUri(uri: AtUri): {\n did: Did\n nsid: NSID\n} {\n // Validate input URI\n const nsid = NSID.from(uri.rkey)\n const did = uri.host\n assertDid(did)\n\n return { did, nsid }\n}\n\nasync function resolveLexiconDidAuthority(nsid: NSID): Promise<Did> {\n try {\n return await getDomainTxtDid(`_lexicon.${nsid.authority}`)\n } catch (cause) {\n throw new LexResolverError(\n nsid,\n `Failed to resolve lexicon DID authority`,\n { cause },\n )\n }\n}\n\nasync function getDomainTxtDid(domain: string): Promise<Did> {\n return parseDnsResult(await resolveTxt(domain))\n}\n\nfunction parseDnsResult(chunkedResults: string[][]): Did {\n const didDefs = chunkedResults\n .map((chunks) => chunks.join(''))\n .filter((i) => i.startsWith('did='))\n\n if (didDefs.length === 1) {\n const did = didDefs[0].slice(4)\n assertDid(did)\n return did\n }\n\n throw didDefs.length > 1\n ? new Error('Multiple DIDs found in DNS TXT records')\n : new Error('No DID found in DNS TXT records')\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto/lex-resolver",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Lexicon document resolver utility for AT Lexicons",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"atproto",
|
|
8
|
+
"lexicon",
|
|
9
|
+
"resolver",
|
|
10
|
+
"utility"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://atproto.com",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/bluesky-social/atproto",
|
|
16
|
+
"directory": "packages/lex/lex-resolver"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"./src",
|
|
20
|
+
"./dist"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"type": "commonjs",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@atproto-labs/did-resolver": "workspace:*",
|
|
35
|
+
"@atproto/lex-client": "workspace:*",
|
|
36
|
+
"@atproto/lex-document": "workspace:*",
|
|
37
|
+
"@atproto/syntax": "workspace:*",
|
|
38
|
+
"tslib": "^2.8.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc --build tsconfig.build.json"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NSID } from '@atproto/syntax'
|
|
2
|
+
|
|
3
|
+
export class LexResolverError extends Error {
|
|
4
|
+
name = 'LexResolverError'
|
|
5
|
+
|
|
6
|
+
constructor(
|
|
7
|
+
public readonly nsid: NSID,
|
|
8
|
+
public readonly description = `Could not resolve Lexicon for NSID`,
|
|
9
|
+
options?: ErrorOptions,
|
|
10
|
+
) {
|
|
11
|
+
super(`${description} (${nsid})`, options)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { resolveTxt } from 'node:dns/promises'
|
|
2
|
+
import { Client, buildAgent } from '@atproto/lex-client'
|
|
3
|
+
import { LexiconDocument, lexiconDocumentSchema } from '@atproto/lex-document'
|
|
4
|
+
import { AtUri, NSID } from '@atproto/syntax'
|
|
5
|
+
import {
|
|
6
|
+
CreateDidResolverOptions,
|
|
7
|
+
Did,
|
|
8
|
+
DidResolver,
|
|
9
|
+
ResolveDidOptions,
|
|
10
|
+
assertDid,
|
|
11
|
+
createDidResolver,
|
|
12
|
+
extractPdsUrl,
|
|
13
|
+
} from '@atproto-labs/did-resolver'
|
|
14
|
+
import { LexResolverError } from './lex-resolver-error.js'
|
|
15
|
+
|
|
16
|
+
export type LexResolverOptions = CreateDidResolverOptions & {
|
|
17
|
+
didAuthority?: Did
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { AtUri, NSID }
|
|
21
|
+
export type { LexiconDocument, ResolveDidOptions }
|
|
22
|
+
|
|
23
|
+
export class LexResolver {
|
|
24
|
+
protected readonly didResolver: DidResolver<'plc' | 'web'>
|
|
25
|
+
|
|
26
|
+
constructor(protected readonly options: LexResolverOptions) {
|
|
27
|
+
this.didResolver = createDidResolver(options)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async get(
|
|
31
|
+
nsidStr: NSID | string,
|
|
32
|
+
options?: ResolveDidOptions,
|
|
33
|
+
): Promise<{
|
|
34
|
+
uri: AtUri
|
|
35
|
+
document: LexiconDocument
|
|
36
|
+
}> {
|
|
37
|
+
const uri = await this.resolve(nsidStr)
|
|
38
|
+
const document = await this.fetch(uri, options)
|
|
39
|
+
return { uri, document }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async resolve(nsidStr: NSID | string): Promise<AtUri> {
|
|
43
|
+
const nsid = NSID.from(nsidStr)
|
|
44
|
+
const did =
|
|
45
|
+
this.options.didAuthority ??
|
|
46
|
+
(await resolveLexiconDidAuthority(nsid).catch((cause) => {
|
|
47
|
+
throw new LexResolverError(
|
|
48
|
+
nsid,
|
|
49
|
+
`Failed to resolve DID authority for Lexicon`,
|
|
50
|
+
{ cause },
|
|
51
|
+
)
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
return AtUri.make(did, 'com.atproto.lexicon.schema', nsid.toString())
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async fetch(
|
|
58
|
+
uriStr: AtUri | string,
|
|
59
|
+
options?: ResolveDidOptions,
|
|
60
|
+
): Promise<LexiconDocument> {
|
|
61
|
+
const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr
|
|
62
|
+
const { did, nsid } = parseLexiconUri(uri)
|
|
63
|
+
|
|
64
|
+
if (this.options.didAuthority && this.options.didAuthority !== did) {
|
|
65
|
+
throw new LexResolverError(
|
|
66
|
+
nsid,
|
|
67
|
+
`DID authority mismatch: expected ${this.options.didAuthority}, got ${did}`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const didDocument = await this.didResolver
|
|
72
|
+
.resolve(did, options)
|
|
73
|
+
.catch((cause) => {
|
|
74
|
+
throw new LexResolverError(
|
|
75
|
+
nsid,
|
|
76
|
+
`Failed to resolve DID document for ${uri}`,
|
|
77
|
+
{ cause },
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
let service: URL
|
|
82
|
+
try {
|
|
83
|
+
service = extractPdsUrl(didDocument)
|
|
84
|
+
} catch (cause) {
|
|
85
|
+
throw new LexResolverError(
|
|
86
|
+
nsid,
|
|
87
|
+
`No PDS service endpoint found in DID document for ${uri}`,
|
|
88
|
+
{ cause },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const agent = buildAgent({
|
|
93
|
+
service,
|
|
94
|
+
fetch: this.options.fetch,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// TODO: use com.atproto.sync.getRecord and check signature using
|
|
98
|
+
// DID document key
|
|
99
|
+
const response = await new Client(agent)
|
|
100
|
+
.getRecord('com.atproto.lexicon.schema', nsid.toString(), { repo: did })
|
|
101
|
+
.catch((cause) => {
|
|
102
|
+
throw new LexResolverError(
|
|
103
|
+
nsid,
|
|
104
|
+
`Failed to fetch Lexicon document at ${uri}`,
|
|
105
|
+
{ cause },
|
|
106
|
+
)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const result = lexiconDocumentSchema.validate(response.body.value)
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
throw new LexResolverError(nsid, `Invalid Lexicon document at ${uri}`, {
|
|
112
|
+
cause: result.error,
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const document = result.value
|
|
117
|
+
|
|
118
|
+
if (document.id !== nsid.toString()) {
|
|
119
|
+
throw new LexResolverError(
|
|
120
|
+
nsid,
|
|
121
|
+
`Invalid document id "${document.id}" for ${uri}`,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return document
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseLexiconUri(uri: AtUri): {
|
|
130
|
+
did: Did
|
|
131
|
+
nsid: NSID
|
|
132
|
+
} {
|
|
133
|
+
// Validate input URI
|
|
134
|
+
const nsid = NSID.from(uri.rkey)
|
|
135
|
+
const did = uri.host
|
|
136
|
+
assertDid(did)
|
|
137
|
+
|
|
138
|
+
return { did, nsid }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function resolveLexiconDidAuthority(nsid: NSID): Promise<Did> {
|
|
142
|
+
try {
|
|
143
|
+
return await getDomainTxtDid(`_lexicon.${nsid.authority}`)
|
|
144
|
+
} catch (cause) {
|
|
145
|
+
throw new LexResolverError(
|
|
146
|
+
nsid,
|
|
147
|
+
`Failed to resolve lexicon DID authority`,
|
|
148
|
+
{ cause },
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function getDomainTxtDid(domain: string): Promise<Did> {
|
|
154
|
+
return parseDnsResult(await resolveTxt(domain))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseDnsResult(chunkedResults: string[][]): Did {
|
|
158
|
+
const didDefs = chunkedResults
|
|
159
|
+
.map((chunks) => chunks.join(''))
|
|
160
|
+
.filter((i) => i.startsWith('did='))
|
|
161
|
+
|
|
162
|
+
if (didDefs.length === 1) {
|
|
163
|
+
const did = didDefs[0].slice(4)
|
|
164
|
+
assertDid(did)
|
|
165
|
+
return did
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
throw didDefs.length > 1
|
|
169
|
+
? new Error('Multiple DIDs found in DNS TXT records')
|
|
170
|
+
: new Error('No DID found in DNS TXT records')
|
|
171
|
+
}
|