@atproto-labs/handle-resolver-node 0.0.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @atproto-labs/handle-resolver-node
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e134c79a0`](https://github.com/bluesky-social/atproto/commit/e134c79a0ffb000b2cb36437815673fa6bda664b) Thanks [@devinivy](https://github.com/devinivy)! - Initial publish of experimental oauth packages to @atproto-labs
8
+
9
+ - Updated dependencies [[`e134c79a0`](https://github.com/bluesky-social/atproto/commit/e134c79a0ffb000b2cb36437815673fa6bda664b)]:
10
+ - @atproto-labs/handle-resolver@0.0.1
11
+ - @atproto-labs/fetch-node@0.0.1
12
+ - @atproto-labs/fetch@0.0.1
13
+ - @atproto-labs/did@0.0.1
package/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Dual MIT/Apache-2.0 License
2
+
3
+ Copyright (c) 2022-2024 Bluesky PBC, and Contributors
4
+
5
+ Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
6
+
7
+ Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ import { resolveTxt } from 'node:dns/promises';
3
+ import { HandleResolveOptions, HandleResolver, ResolvedHandle } from '@atproto-labs/handle-resolver';
4
+ export type DnsHandleResolverOptions = {
5
+ /**
6
+ * Nameservers to use in place of the system's default nameservers.
7
+ */
8
+ nameservers?: string[];
9
+ };
10
+ export declare class DnsHandleResolver implements HandleResolver {
11
+ protected resolveTxt: typeof resolveTxt;
12
+ constructor(options?: DnsHandleResolverOptions);
13
+ resolve(handle: string, _options?: HandleResolveOptions): Promise<ResolvedHandle>;
14
+ }
15
+ //# sourceMappingURL=dns-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-handle-resolver.d.ts","sourceRoot":"","sources":["../src/dns-handle-resolver.ts"],"names":[],"mappings":";AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EAEf,MAAM,+BAA+B,CAAA;AAKtC,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,SAAS,CAAC,UAAU,EAAE,OAAO,UAAU,CAAA;gBAE3B,OAAO,CAAC,EAAE,wBAAwB;IAMjC,OAAO,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;CAO3B"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DnsHandleResolver = void 0;
4
+ const promises_1 = require("node:dns/promises");
5
+ const handle_resolver_1 = require("@atproto-labs/handle-resolver");
6
+ const SUBDOMAIN = '_atproto';
7
+ const PREFIX = 'did=';
8
+ class DnsHandleResolver {
9
+ constructor(options) {
10
+ Object.defineProperty(this, "resolveTxt", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: void 0
15
+ });
16
+ this.resolveTxt = options?.nameservers
17
+ ? buildResolveTxt(options.nameservers)
18
+ : promises_1.resolveTxt.bind(null);
19
+ }
20
+ async resolve(handle, _options) {
21
+ try {
22
+ return parseDnsResult(await this.resolveTxt(`${SUBDOMAIN}.${handle}`));
23
+ }
24
+ catch (err) {
25
+ return null;
26
+ }
27
+ }
28
+ }
29
+ exports.DnsHandleResolver = DnsHandleResolver;
30
+ function buildResolveTxt(nameservers) {
31
+ // Build the resolver asynchronously
32
+ const resolverPromise = Promise.allSettled(nameservers.map((h) => (0, promises_1.lookup)(h)))
33
+ .then((responses) => responses.flatMap((r) => r.status === 'fulfilled' ? [r.value.address] : []))
34
+ .then((backupIps) => {
35
+ if (!backupIps.length)
36
+ return null;
37
+ const resolver = new promises_1.Resolver();
38
+ resolver.setServers(backupIps);
39
+ return resolver;
40
+ });
41
+ resolverPromise.catch(() => {
42
+ // Should never happen
43
+ });
44
+ return async (hostname) => {
45
+ const resolver = await resolverPromise;
46
+ return resolver ? resolver.resolveTxt(hostname) : [];
47
+ };
48
+ }
49
+ function parseDnsResult(chunkedResults) {
50
+ const results = chunkedResults.map((chunks) => chunks.join(''));
51
+ const found = results.filter((i) => i.startsWith(PREFIX));
52
+ if (found.length !== 1) {
53
+ return null;
54
+ }
55
+ const did = found[0].slice(PREFIX.length);
56
+ return (0, handle_resolver_1.isResolvedHandle)(did) ? did : null;
57
+ }
58
+ //# sourceMappingURL=dns-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-handle-resolver.js","sourceRoot":"","sources":["../src/dns-handle-resolver.ts"],"names":[],"mappings":";;;AAAA,gDAAgE;AAEhE,mEAKsC;AAEtC,MAAM,SAAS,GAAG,UAAU,CAAA;AAC5B,MAAM,MAAM,GAAG,MAAM,CAAA;AASrB,MAAa,iBAAiB;IAG5B,YAAY,OAAkC;QAFpC;;;;;WAA6B;QAGrC,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,WAAW;YACpC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC;YACtC,CAAC,CAAC,qBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAc,EACd,QAA+B;QAE/B,IAAI,CAAC;YACH,OAAO,cAAc,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC,CAAA;QACxE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;CACF;AAnBD,8CAmBC;AAED,SAAS,eAAe,CAAC,WAAqB;IAC5C,oCAAoC;IACpC,MAAM,eAAe,GAA6B,OAAO,CAAC,UAAU,CAClE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,iBAAM,EAAC,CAAC,CAAC,CAAC,CAClC;SACE,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAClB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAClD,CACF;SACA,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;QAClB,IAAI,CAAC,SAAS,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAClC,MAAM,QAAQ,GAAG,IAAI,mBAAQ,EAAE,CAAA;QAC/B,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAC9B,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAC,CAAA;IAEJ,eAAe,CAAC,KAAK,CAAC,GAAG,EAAE;QACzB,sBAAsB;IACxB,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAA;QACtC,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACtD,CAAC,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,cAA0B;IAChD,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACzC,OAAO,IAAA,kCAAgB,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3C,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './node-handle-resolver.js';
2
+ export { NodeHandleResolver as default } from './node-handle-resolver.js';
3
+ export * from './dns-handle-resolver.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAA;AACzC,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGzE,cAAc,0BAA0B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.default = void 0;
18
+ // Main export
19
+ __exportStar(require("./node-handle-resolver.js"), exports);
20
+ var node_handle_resolver_js_1 = require("./node-handle-resolver.js");
21
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return node_handle_resolver_js_1.NodeHandleResolver; } });
22
+ // Utilities
23
+ __exportStar(require("./dns-handle-resolver.js"), exports);
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,cAAc;AACd,4DAAyC;AACzC,qEAAyE;AAAhE,kHAAA,kBAAkB,OAAW;AAEtC,YAAY;AACZ,2DAAwC"}
@@ -0,0 +1,29 @@
1
+ import { Fetch } from '@atproto-labs/fetch';
2
+ import { SafeFetchWrapOptions } from '@atproto-labs/fetch-node';
3
+ import { CachedHandleResolver, CachedHandleResolverOptions, HandleResolver } from '@atproto-labs/handle-resolver';
4
+ import { DnsHandleResolverOptions } from './dns-handle-resolver.js';
5
+ export type NodeHandleResolverOptions = {
6
+ /**
7
+ * List of domain names that are forbidden to be resolved using the
8
+ * well-known/atproto-did method.
9
+ */
10
+ wellKnownExclude?: SafeFetchWrapOptions['forbiddenDomainNames'];
11
+ /**
12
+ * List of backup nameservers to use for DNS resolution.
13
+ */
14
+ fallbackNameservers?: DnsHandleResolverOptions['nameservers'];
15
+ cache?: CachedHandleResolverOptions['cache'];
16
+ /**
17
+ * Fetch function to use for HTTP requests. Allows customizing the request
18
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc. The
19
+ * provided fetch function will be wrapped with a safeFetchWrap function that
20
+ * adds SSRF protection.
21
+ *
22
+ * @default `globalThis.fetch`
23
+ */
24
+ fetch?: Fetch;
25
+ };
26
+ export declare class NodeHandleResolver extends CachedHandleResolver implements HandleResolver {
27
+ constructor({ cache, fetch, fallbackNameservers, wellKnownExclude, }?: NodeHandleResolverOptions);
28
+ }
29
+ //# sourceMappingURL=node-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-handle-resolver.d.ts","sourceRoot":"","sources":["../src/node-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,oBAAoB,EAAiB,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EAEf,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EAEL,wBAAwB,EACzB,MAAM,0BAA0B,CAAA;AAEjC,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,oBAAoB,CAAC,sBAAsB,CAAC,CAAA;IAE/D;;OAEG;IACH,mBAAmB,CAAC,EAAE,wBAAwB,CAAC,aAAa,CAAC,CAAA;IAE7D,KAAK,CAAC,EAAE,2BAA2B,CAAC,OAAO,CAAC,CAAA;IAE5C;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAED,qBAAa,kBACX,SAAQ,oBACR,YAAW,cAAc;gBAEb,EACV,KAAK,EACL,KAAwB,EACxB,mBAAmB,EACnB,gBAAgB,GACjB,GAAE,yBAA8B;CA+ClC"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeHandleResolver = void 0;
4
+ const fetch_node_1 = require("@atproto-labs/fetch-node");
5
+ const handle_resolver_1 = require("@atproto-labs/handle-resolver");
6
+ const dns_handle_resolver_js_1 = require("./dns-handle-resolver.js");
7
+ class NodeHandleResolver extends handle_resolver_1.CachedHandleResolver {
8
+ constructor({ cache, fetch = globalThis.fetch, fallbackNameservers, wellKnownExclude, } = {}) {
9
+ const safeFetch = (0, fetch_node_1.safeFetchWrap)({
10
+ fetch,
11
+ timeout: 3000, // 3 seconds
12
+ forbiddenDomainNames: wellKnownExclude,
13
+ ssrfProtection: true,
14
+ responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety
15
+ });
16
+ const httpResolver = new handle_resolver_1.WellKnownHandleResolver({
17
+ fetch: safeFetch,
18
+ });
19
+ const dnsResolver = new dns_handle_resolver_js_1.DnsHandleResolver();
20
+ const fallbackResolver = new dns_handle_resolver_js_1.DnsHandleResolver({
21
+ nameservers: fallbackNameservers,
22
+ });
23
+ super({
24
+ cache,
25
+ resolver: {
26
+ resolve: async (handle) => {
27
+ const abortController = new AbortController();
28
+ const dnsPromise = dnsResolver.resolve(handle);
29
+ const httpPromise = httpResolver.resolve(handle, {
30
+ signal: abortController.signal,
31
+ });
32
+ // Will be awaited later
33
+ httpPromise.catch(() => { });
34
+ const dnsRes = await dnsPromise;
35
+ if (dnsRes) {
36
+ abortController.abort();
37
+ return dnsRes;
38
+ }
39
+ const res = await httpPromise;
40
+ if (res)
41
+ return res;
42
+ return fallbackResolver.resolve(handle);
43
+ },
44
+ },
45
+ });
46
+ }
47
+ }
48
+ exports.NodeHandleResolver = NodeHandleResolver;
49
+ //# sourceMappingURL=node-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-handle-resolver.js","sourceRoot":"","sources":["../src/node-handle-resolver.ts"],"names":[],"mappings":";;;AACA,yDAA8E;AAC9E,mEAKsC;AAEtC,qEAGiC;AA2BjC,MAAa,kBACX,SAAQ,sCAAoB;IAG5B,YAAY,EACV,KAAK,EACL,KAAK,GAAG,UAAU,CAAC,KAAK,EACxB,mBAAmB,EACnB,gBAAgB,MACa,EAAE;QAC/B,MAAM,SAAS,GAAG,IAAA,0BAAa,EAAC;YAC9B,KAAK;YACL,OAAO,EAAE,IAAI,EAAE,YAAY;YAC3B,oBAAoB,EAAE,gBAAgB;YACtC,cAAc,EAAE,IAAI;YACpB,eAAe,EAAE,EAAE,GAAG,IAAI,EAAE,+CAA+C;SAC5E,CAAC,CAAA;QAEF,MAAM,YAAY,GAAG,IAAI,yCAAuB,CAAC;YAC/C,KAAK,EAAE,SAAS;SACjB,CAAC,CAAA;QAEF,MAAM,WAAW,GAAG,IAAI,0CAAiB,EAAE,CAAA;QAE3C,MAAM,gBAAgB,GAAG,IAAI,0CAAiB,CAAC;YAC7C,WAAW,EAAE,mBAAmB;SACjC,CAAC,CAAA;QAEF,KAAK,CAAC;YACJ,KAAK;YACL,QAAQ,EAAE;gBACR,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;oBACxB,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAA;oBAE7C,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBAC9C,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE;wBAC/C,MAAM,EAAE,eAAe,CAAC,MAAM;qBAC/B,CAAC,CAAA;oBAEF,wBAAwB;oBACxB,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;oBAE3B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAA;oBAC/B,IAAI,MAAM,EAAE,CAAC;wBACX,eAAe,CAAC,KAAK,EAAE,CAAA;wBACvB,OAAO,MAAM,CAAA;oBACf,CAAC;oBAED,MAAM,GAAG,GAAG,MAAM,WAAW,CAAA;oBAC7B,IAAI,GAAG;wBAAE,OAAO,GAAG,CAAA;oBAEnB,OAAO,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;gBACzC,CAAC;aACF;SACF,CAAC,CAAA;IACJ,CAAC;CACF;AAxDD,gDAwDC"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@atproto-labs/handle-resolver-node",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "description": "Node specific ATProto handle to DID resolver",
6
+ "keywords": [
7
+ "atproto",
8
+ "oauth",
9
+ "handle",
10
+ "identity",
11
+ "node"
12
+ ],
13
+ "homepage": "https://atproto.com",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/bluesky-social/atproto",
17
+ "directory": "packages/handle-resolver-node"
18
+ },
19
+ "type": "commonjs",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "default": "./dist/index.js"
26
+ }
27
+ },
28
+ "dependencies": {
29
+ "@atproto-labs/handle-resolver": "0.0.1",
30
+ "@atproto-labs/fetch": "0.0.1",
31
+ "@atproto-labs/fetch-node": "0.0.1",
32
+ "@atproto-labs/did": "0.0.1"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.3.3"
36
+ },
37
+ "scripts": {
38
+ "build": "tsc --build tsconfig.build.json"
39
+ }
40
+ }
@@ -0,0 +1,76 @@
1
+ import { Resolver, lookup, resolveTxt } from 'node:dns/promises'
2
+
3
+ import {
4
+ HandleResolveOptions,
5
+ HandleResolver,
6
+ ResolvedHandle,
7
+ isResolvedHandle,
8
+ } from '@atproto-labs/handle-resolver'
9
+
10
+ const SUBDOMAIN = '_atproto'
11
+ const PREFIX = 'did='
12
+
13
+ export type DnsHandleResolverOptions = {
14
+ /**
15
+ * Nameservers to use in place of the system's default nameservers.
16
+ */
17
+ nameservers?: string[]
18
+ }
19
+
20
+ export class DnsHandleResolver implements HandleResolver {
21
+ protected resolveTxt: typeof resolveTxt
22
+
23
+ constructor(options?: DnsHandleResolverOptions) {
24
+ this.resolveTxt = options?.nameservers
25
+ ? buildResolveTxt(options.nameservers)
26
+ : resolveTxt.bind(null)
27
+ }
28
+
29
+ public async resolve(
30
+ handle: string,
31
+ _options?: HandleResolveOptions,
32
+ ): Promise<ResolvedHandle> {
33
+ try {
34
+ return parseDnsResult(await this.resolveTxt(`${SUBDOMAIN}.${handle}`))
35
+ } catch (err) {
36
+ return null
37
+ }
38
+ }
39
+ }
40
+
41
+ function buildResolveTxt(nameservers: string[]): typeof resolveTxt {
42
+ // Build the resolver asynchronously
43
+ const resolverPromise: Promise<Resolver | null> = Promise.allSettled(
44
+ nameservers.map((h) => lookup(h)),
45
+ )
46
+ .then((responses) =>
47
+ responses.flatMap((r) =>
48
+ r.status === 'fulfilled' ? [r.value.address] : [],
49
+ ),
50
+ )
51
+ .then((backupIps) => {
52
+ if (!backupIps.length) return null
53
+ const resolver = new Resolver()
54
+ resolver.setServers(backupIps)
55
+ return resolver
56
+ })
57
+
58
+ resolverPromise.catch(() => {
59
+ // Should never happen
60
+ })
61
+
62
+ return async (hostname) => {
63
+ const resolver = await resolverPromise
64
+ return resolver ? resolver.resolveTxt(hostname) : []
65
+ }
66
+ }
67
+
68
+ function parseDnsResult(chunkedResults: string[][]): ResolvedHandle {
69
+ const results = chunkedResults.map((chunks) => chunks.join(''))
70
+ const found = results.filter((i) => i.startsWith(PREFIX))
71
+ if (found.length !== 1) {
72
+ return null
73
+ }
74
+ const did = found[0].slice(PREFIX.length)
75
+ return isResolvedHandle(did) ? did : null
76
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Main export
2
+ export * from './node-handle-resolver.js'
3
+ export { NodeHandleResolver as default } from './node-handle-resolver.js'
4
+
5
+ // Utilities
6
+ export * from './dns-handle-resolver.js'
@@ -0,0 +1,96 @@
1
+ import { Fetch } from '@atproto-labs/fetch'
2
+ import { SafeFetchWrapOptions, safeFetchWrap } from '@atproto-labs/fetch-node'
3
+ import {
4
+ CachedHandleResolver,
5
+ CachedHandleResolverOptions,
6
+ HandleResolver,
7
+ WellKnownHandleResolver,
8
+ } from '@atproto-labs/handle-resolver'
9
+
10
+ import {
11
+ DnsHandleResolver,
12
+ DnsHandleResolverOptions,
13
+ } from './dns-handle-resolver.js'
14
+
15
+ export type NodeHandleResolverOptions = {
16
+ /**
17
+ * List of domain names that are forbidden to be resolved using the
18
+ * well-known/atproto-did method.
19
+ */
20
+ wellKnownExclude?: SafeFetchWrapOptions['forbiddenDomainNames']
21
+
22
+ /**
23
+ * List of backup nameservers to use for DNS resolution.
24
+ */
25
+ fallbackNameservers?: DnsHandleResolverOptions['nameservers']
26
+
27
+ cache?: CachedHandleResolverOptions['cache']
28
+
29
+ /**
30
+ * Fetch function to use for HTTP requests. Allows customizing the request
31
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc. The
32
+ * provided fetch function will be wrapped with a safeFetchWrap function that
33
+ * adds SSRF protection.
34
+ *
35
+ * @default `globalThis.fetch`
36
+ */
37
+ fetch?: Fetch
38
+ }
39
+
40
+ export class NodeHandleResolver
41
+ extends CachedHandleResolver
42
+ implements HandleResolver
43
+ {
44
+ constructor({
45
+ cache,
46
+ fetch = globalThis.fetch,
47
+ fallbackNameservers,
48
+ wellKnownExclude,
49
+ }: NodeHandleResolverOptions = {}) {
50
+ const safeFetch = safeFetchWrap({
51
+ fetch,
52
+ timeout: 3000, // 3 seconds
53
+ forbiddenDomainNames: wellKnownExclude,
54
+ ssrfProtection: true,
55
+ responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety
56
+ })
57
+
58
+ const httpResolver = new WellKnownHandleResolver({
59
+ fetch: safeFetch,
60
+ })
61
+
62
+ const dnsResolver = new DnsHandleResolver()
63
+
64
+ const fallbackResolver = new DnsHandleResolver({
65
+ nameservers: fallbackNameservers,
66
+ })
67
+
68
+ super({
69
+ cache,
70
+ resolver: {
71
+ resolve: async (handle) => {
72
+ const abortController = new AbortController()
73
+
74
+ const dnsPromise = dnsResolver.resolve(handle)
75
+ const httpPromise = httpResolver.resolve(handle, {
76
+ signal: abortController.signal,
77
+ })
78
+
79
+ // Will be awaited later
80
+ httpPromise.catch(() => {})
81
+
82
+ const dnsRes = await dnsPromise
83
+ if (dnsRes) {
84
+ abortController.abort()
85
+ return dnsRes
86
+ }
87
+
88
+ const res = await httpPromise
89
+ if (res) return res
90
+
91
+ return fallbackResolver.resolve(handle)
92
+ },
93
+ },
94
+ })
95
+ }
96
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig/node.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src",
5
+ "outDir": "./dist"
6
+ },
7
+ "include": ["./src"]
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "include": [],
3
+ "references": [{ "path": "./tsconfig.build.json" }]
4
+ }