@atproto-labs/handle-resolver 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE.txt +7 -0
  3. package/dist/atproto-lexicon-handle-resolver.d.ts +36 -0
  4. package/dist/atproto-lexicon-handle-resolver.d.ts.map +1 -0
  5. package/dist/atproto-lexicon-handle-resolver.js +61 -0
  6. package/dist/atproto-lexicon-handle-resolver.js.map +1 -0
  7. package/dist/cached-handle-resolver.d.ts +17 -0
  8. package/dist/cached-handle-resolver.d.ts.map +1 -0
  9. package/dist/cached-handle-resolver.js +17 -0
  10. package/dist/cached-handle-resolver.js.map +1 -0
  11. package/dist/handle-resolver.d.ts +18 -0
  12. package/dist/handle-resolver.d.ts.map +1 -0
  13. package/dist/handle-resolver.js +9 -0
  14. package/dist/handle-resolver.js.map +1 -0
  15. package/dist/index.d.ts +8 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +28 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/serial-handle-resolver.d.ts +7 -0
  20. package/dist/serial-handle-resolver.d.ts.map +1 -0
  21. package/dist/serial-handle-resolver.js +29 -0
  22. package/dist/serial-handle-resolver.js.map +1 -0
  23. package/dist/universal-handle-resolver.d.ts +32 -0
  24. package/dist/universal-handle-resolver.d.ts.map +1 -0
  25. package/dist/universal-handle-resolver.js +25 -0
  26. package/dist/universal-handle-resolver.js.map +1 -0
  27. package/dist/well-known-handler-resolver.d.ts +11 -0
  28. package/dist/well-known-handler-resolver.d.ts.map +1 -0
  29. package/dist/well-known-handler-resolver.js +39 -0
  30. package/dist/well-known-handler-resolver.js.map +1 -0
  31. package/package.json +43 -0
  32. package/src/atproto-lexicon-handle-resolver.ts +88 -0
  33. package/src/cached-handle-resolver.ts +40 -0
  34. package/src/handle-resolver.ts +27 -0
  35. package/src/index.ts +11 -0
  36. package/src/serial-handle-resolver.ts +29 -0
  37. package/src/universal-handle-resolver.ts +58 -0
  38. package/src/well-known-handler-resolver.ts +51 -0
  39. package/tsconfig.build.json +8 -0
  40. package/tsconfig.json +4 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # @atproto-labs/handle-resolver
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/caching@0.0.1
11
+ - @atproto-labs/fetch@0.0.1
12
+ - @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,36 @@
1
+ import { Fetch } from '@atproto-labs/fetch';
2
+ import z from 'zod';
3
+ import { HandleResolveOptions, HandleResolver, ResolvedHandle } from './handle-resolver.js';
4
+ export declare const xrpcErrorSchema: z.ZodObject<{
5
+ error: z.ZodString;
6
+ message: z.ZodOptional<z.ZodString>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ error: string;
9
+ message?: string | undefined;
10
+ }, {
11
+ error: string;
12
+ message?: string | undefined;
13
+ }>;
14
+ export type AtprotoLexiconHandleResolverOptions = {
15
+ /**
16
+ * Fetch function to use for HTTP requests. Allows customizing the request
17
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc.
18
+ *
19
+ * @default globalThis.fetch
20
+ */
21
+ fetch?: Fetch;
22
+ /**
23
+ * URL of the atproto lexicon server. This is the base URL where the
24
+ * `com.atproto.identity.resolveHandle` XRPC method is located.
25
+ *
26
+ * @default 'https://bsky.social'
27
+ */
28
+ url?: URL | string;
29
+ };
30
+ export declare class AtprotoLexiconHandleResolver implements HandleResolver {
31
+ protected readonly url: URL;
32
+ protected readonly fetch: Fetch;
33
+ constructor({ url, fetch, }?: AtprotoLexiconHandleResolverOptions);
34
+ resolve(handle: string, options?: HandleResolveOptions): Promise<ResolvedHandle>;
35
+ }
36
+ //# sourceMappingURL=atproto-lexicon-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atproto-lexicon-handle-resolver.d.ts","sourceRoot":"","sources":["../src/atproto-lexicon-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,CAAC,MAAM,KAAK,CAAA;AAEnB,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EAEf,MAAM,sBAAsB,CAAA;AAE7B,eAAO,MAAM,eAAe;;;;;;;;;EAG1B,CAAA;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;IAEb;;;;;OAKG;IACH,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,CAAA;CACnB,CAAA;AAED,qBAAa,4BAA6B,YAAW,cAAc;IACjE,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IAC3B,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;gBAEnB,EACV,GAA4B,EAC5B,KAAwB,GACzB,GAAE,mCAAwC;IAK9B,OAAO,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,cAAc,CAAC;CAuC3B"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AtprotoLexiconHandleResolver = exports.xrpcErrorSchema = void 0;
7
+ const zod_1 = __importDefault(require("zod"));
8
+ const handle_resolver_js_1 = require("./handle-resolver.js");
9
+ exports.xrpcErrorSchema = zod_1.default.object({
10
+ error: zod_1.default.string(),
11
+ message: zod_1.default.string().optional(),
12
+ });
13
+ class AtprotoLexiconHandleResolver {
14
+ constructor({ url = 'https://bsky.social/', fetch = globalThis.fetch, } = {}) {
15
+ Object.defineProperty(this, "url", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: void 0
20
+ });
21
+ Object.defineProperty(this, "fetch", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: void 0
26
+ });
27
+ this.url = new URL(url);
28
+ this.fetch = fetch;
29
+ }
30
+ async resolve(handle, options) {
31
+ const url = new URL('/xrpc/com.atproto.identity.resolveHandle', this.url);
32
+ url.searchParams.set('handle', handle);
33
+ const headers = new Headers();
34
+ if (options?.noCache)
35
+ headers.set('cache-control', 'no-cache');
36
+ const request = new Request(url, { headers, signal: options?.signal });
37
+ const response = await this.fetch.call(null, request);
38
+ const payload = await response.json();
39
+ // The response should either be
40
+ // - 400 Bad Request with { error: 'InvalidRequest', message: 'Unable to resolve handle' }
41
+ // - 200 OK with { did: NonNullable<ResolvedHandle> }
42
+ // Any other response is considered unexpected behavior an should throw an error.
43
+ if (response.status === 400) {
44
+ const data = exports.xrpcErrorSchema.parse(payload);
45
+ if (data.error === 'InvalidRequest' &&
46
+ data.message === 'Unable to resolve handle') {
47
+ return null;
48
+ }
49
+ }
50
+ if (!response.ok) {
51
+ throw new Error('Invalid response from resolveHandle method');
52
+ }
53
+ const value = payload?.did;
54
+ if (!value || !(0, handle_resolver_js_1.isResolvedHandle)(value)) {
55
+ throw new Error('Invalid DID returned from resolveHandle method');
56
+ }
57
+ return value;
58
+ }
59
+ }
60
+ exports.AtprotoLexiconHandleResolver = AtprotoLexiconHandleResolver;
61
+ //# sourceMappingURL=atproto-lexicon-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atproto-lexicon-handle-resolver.js","sourceRoot":"","sources":["../src/atproto-lexicon-handle-resolver.ts"],"names":[],"mappings":";;;;;;AACA,8CAAmB;AAEnB,6DAK6B;AAEhB,QAAA,eAAe,GAAG,aAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,aAAC,CAAC,MAAM,EAAE;IACjB,OAAO,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAA;AAoBF,MAAa,4BAA4B;IAIvC,YAAY,EACV,GAAG,GAAG,sBAAsB,EAC5B,KAAK,GAAG,UAAU,CAAC,KAAK,MACe,EAAE;QANxB;;;;;WAAQ;QACR;;;;;WAAY;QAM7B,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAA;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAc,EACd,OAA8B;QAE9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,0CAA0C,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;QACzE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QAEtC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;QAC7B,IAAI,OAAO,EAAE,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAA;QAE9D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAEtE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACrD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;QAErC,gCAAgC;QAChC,0FAA0F;QAC1F,qDAAqD;QACrD,iFAAiF;QAEjF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,uBAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YAC3C,IACE,IAAI,CAAC,KAAK,KAAK,gBAAgB;gBAC/B,IAAI,CAAC,OAAO,KAAK,0BAA0B,EAC3C,CAAC;gBACD,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;QAC/D,CAAC;QAED,MAAM,KAAK,GAAY,OAAO,EAAE,GAAG,CAAA;QAEnC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAA,qCAAgB,EAAC,KAAK,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;QACnE,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC;CACF;AAtDD,oEAsDC"}
@@ -0,0 +1,17 @@
1
+ import { CachedGetter, GenericStore } from '@atproto-labs/caching';
2
+ import { HandleResolveOptions, HandleResolver, ResolvedHandle } from './handle-resolver.js';
3
+ export type CachedHandleResolverOptions = {
4
+ /**
5
+ * The resolver that will be used to resolve handles.
6
+ */
7
+ resolver: HandleResolver;
8
+ /**
9
+ * A store that will be used to cache resolved values.
10
+ */
11
+ cache?: GenericStore<string, ResolvedHandle>;
12
+ };
13
+ export declare class CachedHandleResolver extends CachedGetter<string, ResolvedHandle> implements HandleResolver {
14
+ constructor({ resolver, cache, }: CachedHandleResolverOptions);
15
+ resolve(handle: string, options?: HandleResolveOptions): Promise<ResolvedHandle>;
16
+ }
17
+ //# sourceMappingURL=cached-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-handle-resolver.d.ts","sourceRoot":"","sources":["../src/cached-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAe,MAAM,uBAAuB,CAAA;AAC/E,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EACf,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,2BAA2B,GAAG;IACxC;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAA;IAExB;;OAEG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;CAC7C,CAAA;AAED,qBAAa,oBACX,SAAQ,YAAY,CAAC,MAAM,EAAE,cAAc,CAC3C,YAAW,cAAc;gBAEb,EACV,QAAQ,EACR,KAGE,GACH,EAAE,2BAA2B;IAIxB,OAAO,CACX,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,cAAc,CAAC;CAG3B"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CachedHandleResolver = void 0;
4
+ const caching_1 = require("@atproto-labs/caching");
5
+ class CachedHandleResolver extends caching_1.CachedGetter {
6
+ constructor({ resolver, cache = new caching_1.MemoryStore({
7
+ max: 1000,
8
+ ttl: 10 * 60e3,
9
+ }), }) {
10
+ super((handle, options) => resolver.resolve(handle, options), cache);
11
+ }
12
+ async resolve(handle, options) {
13
+ return this.get(handle, options);
14
+ }
15
+ }
16
+ exports.CachedHandleResolver = CachedHandleResolver;
17
+ //# sourceMappingURL=cached-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cached-handle-resolver.js","sourceRoot":"","sources":["../src/cached-handle-resolver.ts"],"names":[],"mappings":";;;AAAA,mDAA+E;AAmB/E,MAAa,oBACX,SAAQ,sBAAoC;IAG5C,YAAY,EACV,QAAQ,EACR,KAAK,GAAG,IAAI,qBAAW,CAAyB;QAC9C,GAAG,EAAE,IAAI;QACT,GAAG,EAAE,EAAE,GAAG,IAAI;KACf,CAAC,GAC0B;QAC5B,KAAK,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAA;IACtE,CAAC;IAED,KAAK,CAAC,OAAO,CACX,MAAc,EACd,OAA8B;QAE9B,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,CAAC;CACF;AApBD,oDAoBC"}
@@ -0,0 +1,18 @@
1
+ import { Did } from '@atproto-labs/did';
2
+ export type HandleResolveOptions = {
3
+ signal?: AbortSignal;
4
+ noCache?: boolean;
5
+ };
6
+ export type ResolvedHandle = null | Did;
7
+ export declare function isResolvedHandle<T = unknown>(value: T): value is T & ResolvedHandle;
8
+ export interface HandleResolver {
9
+ /**
10
+ * @returns the DID that corresponds to the given handle, or `null` if no DID
11
+ * is found. `null` should only be returned if no unexpected behavior occurred
12
+ * during the resolution process.
13
+ * @throws Error if the resolution method fails due to an unexpected error, or
14
+ * if the resolution is aborted ({@link HandleResolveOptions.signal}).
15
+ */
16
+ resolve(handle: string, options?: HandleResolveOptions): Promise<ResolvedHandle>;
17
+ }
18
+ //# sourceMappingURL=handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-resolver.d.ts","sourceRoot":"","sources":["../src/handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAS,MAAM,mBAAmB,CAAA;AAE9C,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AACD,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,GAAG,CAAA;AAEvC,wBAAgB,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1C,KAAK,EAAE,CAAC,GACP,KAAK,IAAI,CAAC,GAAG,cAAc,CAE7B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,OAAO,CACL,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,cAAc,CAAC,CAAA;CAC3B"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isResolvedHandle = void 0;
4
+ const did_1 = require("@atproto-labs/did");
5
+ function isResolvedHandle(value) {
6
+ return value === null || (typeof value === 'string' && (0, did_1.isDid)(value));
7
+ }
8
+ exports.isResolvedHandle = isResolvedHandle;
9
+ //# sourceMappingURL=handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle-resolver.js","sourceRoot":"","sources":["../src/handle-resolver.ts"],"names":[],"mappings":";;;AAAA,2CAA8C;AAQ9C,SAAgB,gBAAgB,CAC9B,KAAQ;IAER,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,IAAA,WAAK,EAAC,KAAK,CAAC,CAAC,CAAA;AACtE,CAAC;AAJD,4CAIC"}
@@ -0,0 +1,8 @@
1
+ export * from './handle-resolver.js';
2
+ export * from './universal-handle-resolver.js';
3
+ export { UniversalHandleResolver as default } from './universal-handle-resolver.js';
4
+ export * from './cached-handle-resolver.js';
5
+ export * from './atproto-lexicon-handle-resolver.js';
6
+ export * from './serial-handle-resolver.js';
7
+ export * from './well-known-handler-resolver.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA;AAGpC,cAAc,gCAAgC,CAAA;AAC9C,OAAO,EAAE,uBAAuB,IAAI,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAGnF,cAAc,6BAA6B,CAAA;AAC3C,cAAc,sCAAsC,CAAA;AACpD,cAAc,6BAA6B,CAAA;AAC3C,cAAc,kCAAkC,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
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
+ __exportStar(require("./handle-resolver.js"), exports);
19
+ // Main export
20
+ __exportStar(require("./universal-handle-resolver.js"), exports);
21
+ var universal_handle_resolver_js_1 = require("./universal-handle-resolver.js");
22
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return universal_handle_resolver_js_1.UniversalHandleResolver; } });
23
+ // Utilities
24
+ __exportStar(require("./cached-handle-resolver.js"), exports);
25
+ __exportStar(require("./atproto-lexicon-handle-resolver.js"), exports);
26
+ __exportStar(require("./serial-handle-resolver.js"), exports);
27
+ __exportStar(require("./well-known-handler-resolver.js"), exports);
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,uDAAoC;AAEpC,cAAc;AACd,iEAA8C;AAC9C,+EAAmF;AAA1E,uHAAA,uBAAuB,OAAW;AAE3C,YAAY;AACZ,8DAA2C;AAC3C,uEAAoD;AACpD,8DAA2C;AAC3C,mEAAgD"}
@@ -0,0 +1,7 @@
1
+ import { HandleResolveOptions, HandleResolver, ResolvedHandle } from './handle-resolver.js';
2
+ export declare class SerialHandleResolver implements HandleResolver {
3
+ protected readonly resolvers: readonly HandleResolver[];
4
+ constructor(resolvers: readonly HandleResolver[]);
5
+ resolve(handle: string, options?: HandleResolveOptions): Promise<ResolvedHandle>;
6
+ }
7
+ //# sourceMappingURL=serial-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serial-handle-resolver.d.ts","sourceRoot":"","sources":["../src/serial-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EACf,MAAM,sBAAsB,CAAA;AAE7B,qBAAa,oBAAqB,YAAW,cAAc;IAC7C,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE;gBAApC,SAAS,EAAE,SAAS,cAAc,EAAE;IAMtD,OAAO,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,cAAc,CAAC;CAY3B"}
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SerialHandleResolver = void 0;
4
+ class SerialHandleResolver {
5
+ constructor(resolvers) {
6
+ Object.defineProperty(this, "resolvers", {
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true,
10
+ value: resolvers
11
+ });
12
+ if (!resolvers.length) {
13
+ throw new TypeError('At least one resolver is required');
14
+ }
15
+ }
16
+ async resolve(handle, options) {
17
+ for (const resolver of this.resolvers) {
18
+ options?.signal?.throwIfAborted();
19
+ const value = await resolver.resolve(handle, options);
20
+ if (value)
21
+ return value;
22
+ }
23
+ // If no resolver was able to resolve the handle, assume there is no DID
24
+ // corresponding to the handle.
25
+ return null;
26
+ }
27
+ }
28
+ exports.SerialHandleResolver = SerialHandleResolver;
29
+ //# sourceMappingURL=serial-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serial-handle-resolver.js","sourceRoot":"","sources":["../src/serial-handle-resolver.ts"],"names":[],"mappings":";;;AAMA,MAAa,oBAAoB;IAC/B,YAA+B,SAAoC;QAAvD;;;;mBAAmB,SAAS;WAA2B;QACjE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;YACtB,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAA;QAC1D,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAc,EACd,OAA8B;QAE9B,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;YAEjC,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YACrD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAA;QACzB,CAAC;QAED,wEAAwE;QACxE,+BAA+B;QAC/B,OAAO,IAAI,CAAA;IACb,CAAC;CACF;AAtBD,oDAsBC"}
@@ -0,0 +1,32 @@
1
+ import { GenericStore } from '@atproto-labs/caching';
2
+ import { Fetch } from '@atproto-labs/fetch';
3
+ import { CachedHandleResolver } from './cached-handle-resolver.js';
4
+ import { HandleResolver, ResolvedHandle } from './handle-resolver.js';
5
+ import { AtprotoLexiconHandleResolverOptions } from './atproto-lexicon-handle-resolver.js';
6
+ export type HandleResolverCache = GenericStore<string, ResolvedHandle>;
7
+ export type UniversalHandleResolverOptions = {
8
+ cache?: HandleResolverCache;
9
+ /**
10
+ * Fetch function to use for HTTP requests. Allows customizing the request
11
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc.
12
+ *
13
+ * When using this library from a Node.js environment, you may want to use
14
+ * `safeFetchWrap()` from `@atproto-labs/fetch-node` to add SSRF protection.
15
+ *
16
+ * @default `globalThis.fetch`
17
+ */
18
+ fetch?: Fetch;
19
+ /**
20
+ * @see {@link AtprotoLexiconHandleResolverOptions.url}
21
+ */
22
+ atprotoLexiconUrl?: AtprotoLexiconHandleResolverOptions['url'];
23
+ };
24
+ /**
25
+ * A handle resolver that works in any environment that supports `fetch()`. This
26
+ * relies on the a public XRPC implementing "com.atproto.identity.resolveHandle"
27
+ * to resolve handles.
28
+ */
29
+ export declare class UniversalHandleResolver extends CachedHandleResolver implements HandleResolver {
30
+ constructor({ fetch, cache, atprotoLexiconUrl, }?: UniversalHandleResolverOptions);
31
+ }
32
+ //# sourceMappingURL=universal-handle-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universal-handle-resolver.d.ts","sourceRoot":"","sources":["../src/universal-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAE3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,EAEL,mCAAmC,EACpC,MAAM,sCAAsC,CAAA;AAI7C,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;AAEtE,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,CAAC,EAAE,mBAAmB,CAAA;IAE3B;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;IAEb;;OAEG;IACH,iBAAiB,CAAC,EAAE,mCAAmC,CAAC,KAAK,CAAC,CAAA;CAC/D,CAAA;AAED;;;;GAIG;AACH,qBAAa,uBACX,SAAQ,oBACR,YAAW,cAAc;gBAEb,EACV,KAAwB,EACxB,KAAK,EACL,iBAAiB,GAClB,GAAE,8BAAmC;CAUvC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UniversalHandleResolver = void 0;
4
+ const cached_handle_resolver_js_1 = require("./cached-handle-resolver.js");
5
+ const atproto_lexicon_handle_resolver_js_1 = require("./atproto-lexicon-handle-resolver.js");
6
+ const serial_handle_resolver_js_1 = require("./serial-handle-resolver.js");
7
+ const well_known_handler_resolver_js_1 = require("./well-known-handler-resolver.js");
8
+ /**
9
+ * A handle resolver that works in any environment that supports `fetch()`. This
10
+ * relies on the a public XRPC implementing "com.atproto.identity.resolveHandle"
11
+ * to resolve handles.
12
+ */
13
+ class UniversalHandleResolver extends cached_handle_resolver_js_1.CachedHandleResolver {
14
+ constructor({ fetch = globalThis.fetch, cache, atprotoLexiconUrl, } = {}) {
15
+ const resolver = new serial_handle_resolver_js_1.SerialHandleResolver([
16
+ // Try the well-known method first, allowing to reduce the load on the
17
+ // XRPC.
18
+ new well_known_handler_resolver_js_1.WellKnownHandleResolver({ fetch }),
19
+ new atproto_lexicon_handle_resolver_js_1.AtprotoLexiconHandleResolver({ fetch, url: atprotoLexiconUrl }),
20
+ ]);
21
+ super({ resolver, cache });
22
+ }
23
+ }
24
+ exports.UniversalHandleResolver = UniversalHandleResolver;
25
+ //# sourceMappingURL=universal-handle-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"universal-handle-resolver.js","sourceRoot":"","sources":["../src/universal-handle-resolver.ts"],"names":[],"mappings":";;;AAGA,2EAAkE;AAElE,6FAG6C;AAC7C,2EAAkE;AAClE,qFAA0E;AAwB1E;;;;GAIG;AACH,MAAa,uBACX,SAAQ,gDAAoB;IAG5B,YAAY,EACV,KAAK,GAAG,UAAU,CAAC,KAAK,EACxB,KAAK,EACL,iBAAiB,MACiB,EAAE;QACpC,MAAM,QAAQ,GAAG,IAAI,gDAAoB,CAAC;YACxC,sEAAsE;YACtE,QAAQ;YACR,IAAI,wDAAuB,CAAC,EAAE,KAAK,EAAE,CAAC;YACtC,IAAI,iEAA4B,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC;SACpE,CAAC,CAAA;QAEF,KAAK,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5B,CAAC;CACF;AAlBD,0DAkBC"}
@@ -0,0 +1,11 @@
1
+ import { Fetch } from '@atproto-labs/fetch';
2
+ import { HandleResolveOptions, HandleResolver, ResolvedHandle } from './handle-resolver.js';
3
+ export type WellKnownHandleResolverOptions = {
4
+ fetch?: Fetch;
5
+ };
6
+ export declare class WellKnownHandleResolver implements HandleResolver {
7
+ protected readonly fetch: Fetch;
8
+ constructor({ fetch, }?: WellKnownHandleResolverOptions);
9
+ resolve(handle: string, options?: HandleResolveOptions): Promise<ResolvedHandle>;
10
+ }
11
+ //# sourceMappingURL=well-known-handler-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known-handler-resolver.d.ts","sourceRoot":"","sources":["../src/well-known-handler-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAE3C,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EAEf,MAAM,sBAAsB,CAAA;AAE7B,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAED,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;gBAEnB,EACV,KAAwB,GACzB,GAAE,8BAAmC;IAIzB,OAAO,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,cAAc,CAAC;CAyB3B"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WellKnownHandleResolver = void 0;
4
+ const handle_resolver_js_1 = require("./handle-resolver.js");
5
+ class WellKnownHandleResolver {
6
+ constructor({ fetch = globalThis.fetch, } = {}) {
7
+ Object.defineProperty(this, "fetch", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: void 0
12
+ });
13
+ this.fetch = fetch;
14
+ }
15
+ async resolve(handle, options) {
16
+ const url = new URL('/.well-known/atproto-did', `https://${handle}`);
17
+ const headers = new Headers();
18
+ if (options?.noCache)
19
+ headers.set('cache-control', 'no-cache');
20
+ const request = new Request(url, { headers, signal: options?.signal });
21
+ try {
22
+ const response = await (0, this.fetch)(request);
23
+ const text = await response.text();
24
+ const firstLine = text.split('\n')[0].trim();
25
+ if ((0, handle_resolver_js_1.isResolvedHandle)(firstLine))
26
+ return firstLine;
27
+ return null;
28
+ }
29
+ catch (err) {
30
+ // The the request failed, assume the handle does not resolve to a DID,
31
+ // unless the failure was due to the signal being aborted.
32
+ options?.signal?.throwIfAborted();
33
+ // TODO: propagate some errors as-is (?)
34
+ return null;
35
+ }
36
+ }
37
+ }
38
+ exports.WellKnownHandleResolver = WellKnownHandleResolver;
39
+ //# sourceMappingURL=well-known-handler-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"well-known-handler-resolver.js","sourceRoot":"","sources":["../src/well-known-handler-resolver.ts"],"names":[],"mappings":";;;AAEA,6DAK6B;AAM7B,MAAa,uBAAuB;IAGlC,YAAY,EACV,KAAK,GAAG,UAAU,CAAC,KAAK,MACU,EAAE;QAJnB;;;;;WAAY;QAK7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAc,EACd,OAA8B;QAE9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,0BAA0B,EAAE,WAAW,MAAM,EAAE,CAAC,CAAA;QAEpE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;QAC7B,IAAI,OAAO,EAAE,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAA;QAC9D,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAA;QAEtE,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAA;YAC/C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;YAClC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;YAE7C,IAAI,IAAA,qCAAgB,EAAC,SAAS,CAAC;gBAAE,OAAO,SAAS,CAAA;YAEjD,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uEAAuE;YACvE,0DAA0D;YAC1D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;YAEjC,wCAAwC;YAExC,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;CACF;AArCD,0DAqCC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@atproto-labs/handle-resolver",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "description": "Isomorphic ATProto handle to DID resolver",
6
+ "keywords": [
7
+ "atproto",
8
+ "oauth",
9
+ "handle",
10
+ "identity",
11
+ "browser",
12
+ "node",
13
+ "isomorphic"
14
+ ],
15
+ "homepage": "https://atproto.com",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/bluesky-social/atproto",
19
+ "directory": "packages/handle-resolver"
20
+ },
21
+ "type": "commonjs",
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "dependencies": {
31
+ "lru-cache": "^10.2.0",
32
+ "zod": "^3.22.4",
33
+ "@atproto-labs/caching": "0.0.1",
34
+ "@atproto-labs/did": "0.0.1",
35
+ "@atproto-labs/fetch": "0.0.1"
36
+ },
37
+ "devDependencies": {
38
+ "typescript": "^5.3.3"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc --build tsconfig.build.json"
42
+ }
43
+ }
@@ -0,0 +1,88 @@
1
+ import { Fetch } from '@atproto-labs/fetch'
2
+ import z from 'zod'
3
+
4
+ import {
5
+ HandleResolveOptions,
6
+ HandleResolver,
7
+ ResolvedHandle,
8
+ isResolvedHandle,
9
+ } from './handle-resolver.js'
10
+
11
+ export const xrpcErrorSchema = z.object({
12
+ error: z.string(),
13
+ message: z.string().optional(),
14
+ })
15
+
16
+ export type AtprotoLexiconHandleResolverOptions = {
17
+ /**
18
+ * Fetch function to use for HTTP requests. Allows customizing the request
19
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc.
20
+ *
21
+ * @default globalThis.fetch
22
+ */
23
+ fetch?: Fetch
24
+
25
+ /**
26
+ * URL of the atproto lexicon server. This is the base URL where the
27
+ * `com.atproto.identity.resolveHandle` XRPC method is located.
28
+ *
29
+ * @default 'https://bsky.social'
30
+ */
31
+ url?: URL | string
32
+ }
33
+
34
+ export class AtprotoLexiconHandleResolver implements HandleResolver {
35
+ protected readonly url: URL
36
+ protected readonly fetch: Fetch
37
+
38
+ constructor({
39
+ url = 'https://bsky.social/',
40
+ fetch = globalThis.fetch,
41
+ }: AtprotoLexiconHandleResolverOptions = {}) {
42
+ this.url = new URL(url)
43
+ this.fetch = fetch
44
+ }
45
+
46
+ public async resolve(
47
+ handle: string,
48
+ options?: HandleResolveOptions,
49
+ ): Promise<ResolvedHandle> {
50
+ const url = new URL('/xrpc/com.atproto.identity.resolveHandle', this.url)
51
+ url.searchParams.set('handle', handle)
52
+
53
+ const headers = new Headers()
54
+ if (options?.noCache) headers.set('cache-control', 'no-cache')
55
+
56
+ const request = new Request(url, { headers, signal: options?.signal })
57
+
58
+ const response = await this.fetch.call(null, request)
59
+ const payload = await response.json()
60
+
61
+ // The response should either be
62
+ // - 400 Bad Request with { error: 'InvalidRequest', message: 'Unable to resolve handle' }
63
+ // - 200 OK with { did: NonNullable<ResolvedHandle> }
64
+ // Any other response is considered unexpected behavior an should throw an error.
65
+
66
+ if (response.status === 400) {
67
+ const data = xrpcErrorSchema.parse(payload)
68
+ if (
69
+ data.error === 'InvalidRequest' &&
70
+ data.message === 'Unable to resolve handle'
71
+ ) {
72
+ return null
73
+ }
74
+ }
75
+
76
+ if (!response.ok) {
77
+ throw new Error('Invalid response from resolveHandle method')
78
+ }
79
+
80
+ const value: unknown = payload?.did
81
+
82
+ if (!value || !isResolvedHandle(value)) {
83
+ throw new Error('Invalid DID returned from resolveHandle method')
84
+ }
85
+
86
+ return value
87
+ }
88
+ }
@@ -0,0 +1,40 @@
1
+ import { CachedGetter, GenericStore, MemoryStore } from '@atproto-labs/caching'
2
+ import {
3
+ HandleResolveOptions,
4
+ HandleResolver,
5
+ ResolvedHandle,
6
+ } from './handle-resolver.js'
7
+
8
+ export type CachedHandleResolverOptions = {
9
+ /**
10
+ * The resolver that will be used to resolve handles.
11
+ */
12
+ resolver: HandleResolver
13
+
14
+ /**
15
+ * A store that will be used to cache resolved values.
16
+ */
17
+ cache?: GenericStore<string, ResolvedHandle>
18
+ }
19
+
20
+ export class CachedHandleResolver
21
+ extends CachedGetter<string, ResolvedHandle>
22
+ implements HandleResolver
23
+ {
24
+ constructor({
25
+ resolver,
26
+ cache = new MemoryStore<string, ResolvedHandle>({
27
+ max: 1000,
28
+ ttl: 10 * 60e3,
29
+ }),
30
+ }: CachedHandleResolverOptions) {
31
+ super((handle, options) => resolver.resolve(handle, options), cache)
32
+ }
33
+
34
+ async resolve(
35
+ handle: string,
36
+ options?: HandleResolveOptions,
37
+ ): Promise<ResolvedHandle> {
38
+ return this.get(handle, options)
39
+ }
40
+ }
@@ -0,0 +1,27 @@
1
+ import { Did, isDid } from '@atproto-labs/did'
2
+
3
+ export type HandleResolveOptions = {
4
+ signal?: AbortSignal
5
+ noCache?: boolean
6
+ }
7
+ export type ResolvedHandle = null | Did
8
+
9
+ export function isResolvedHandle<T = unknown>(
10
+ value: T,
11
+ ): value is T & ResolvedHandle {
12
+ return value === null || (typeof value === 'string' && isDid(value))
13
+ }
14
+
15
+ export interface HandleResolver {
16
+ /**
17
+ * @returns the DID that corresponds to the given handle, or `null` if no DID
18
+ * is found. `null` should only be returned if no unexpected behavior occurred
19
+ * during the resolution process.
20
+ * @throws Error if the resolution method fails due to an unexpected error, or
21
+ * if the resolution is aborted ({@link HandleResolveOptions.signal}).
22
+ */
23
+ resolve(
24
+ handle: string,
25
+ options?: HandleResolveOptions,
26
+ ): Promise<ResolvedHandle>
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './handle-resolver.js'
2
+
3
+ // Main export
4
+ export * from './universal-handle-resolver.js'
5
+ export { UniversalHandleResolver as default } from './universal-handle-resolver.js'
6
+
7
+ // Utilities
8
+ export * from './cached-handle-resolver.js'
9
+ export * from './atproto-lexicon-handle-resolver.js'
10
+ export * from './serial-handle-resolver.js'
11
+ export * from './well-known-handler-resolver.js'
@@ -0,0 +1,29 @@
1
+ import {
2
+ HandleResolveOptions,
3
+ HandleResolver,
4
+ ResolvedHandle,
5
+ } from './handle-resolver.js'
6
+
7
+ export class SerialHandleResolver implements HandleResolver {
8
+ constructor(protected readonly resolvers: readonly HandleResolver[]) {
9
+ if (!resolvers.length) {
10
+ throw new TypeError('At least one resolver is required')
11
+ }
12
+ }
13
+
14
+ public async resolve(
15
+ handle: string,
16
+ options?: HandleResolveOptions,
17
+ ): Promise<ResolvedHandle> {
18
+ for (const resolver of this.resolvers) {
19
+ options?.signal?.throwIfAborted()
20
+
21
+ const value = await resolver.resolve(handle, options)
22
+ if (value) return value
23
+ }
24
+
25
+ // If no resolver was able to resolve the handle, assume there is no DID
26
+ // corresponding to the handle.
27
+ return null
28
+ }
29
+ }
@@ -0,0 +1,58 @@
1
+ import { GenericStore } from '@atproto-labs/caching'
2
+ import { Fetch } from '@atproto-labs/fetch'
3
+
4
+ import { CachedHandleResolver } from './cached-handle-resolver.js'
5
+ import { HandleResolver, ResolvedHandle } from './handle-resolver.js'
6
+ import {
7
+ AtprotoLexiconHandleResolver,
8
+ AtprotoLexiconHandleResolverOptions,
9
+ } from './atproto-lexicon-handle-resolver.js'
10
+ import { SerialHandleResolver } from './serial-handle-resolver.js'
11
+ import { WellKnownHandleResolver } from './well-known-handler-resolver.js'
12
+
13
+ export type HandleResolverCache = GenericStore<string, ResolvedHandle>
14
+
15
+ export type UniversalHandleResolverOptions = {
16
+ cache?: HandleResolverCache
17
+
18
+ /**
19
+ * Fetch function to use for HTTP requests. Allows customizing the request
20
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc.
21
+ *
22
+ * When using this library from a Node.js environment, you may want to use
23
+ * `safeFetchWrap()` from `@atproto-labs/fetch-node` to add SSRF protection.
24
+ *
25
+ * @default `globalThis.fetch`
26
+ */
27
+ fetch?: Fetch
28
+
29
+ /**
30
+ * @see {@link AtprotoLexiconHandleResolverOptions.url}
31
+ */
32
+ atprotoLexiconUrl?: AtprotoLexiconHandleResolverOptions['url']
33
+ }
34
+
35
+ /**
36
+ * A handle resolver that works in any environment that supports `fetch()`. This
37
+ * relies on the a public XRPC implementing "com.atproto.identity.resolveHandle"
38
+ * to resolve handles.
39
+ */
40
+ export class UniversalHandleResolver
41
+ extends CachedHandleResolver
42
+ implements HandleResolver
43
+ {
44
+ constructor({
45
+ fetch = globalThis.fetch,
46
+ cache,
47
+ atprotoLexiconUrl,
48
+ }: UniversalHandleResolverOptions = {}) {
49
+ const resolver = new SerialHandleResolver([
50
+ // Try the well-known method first, allowing to reduce the load on the
51
+ // XRPC.
52
+ new WellKnownHandleResolver({ fetch }),
53
+ new AtprotoLexiconHandleResolver({ fetch, url: atprotoLexiconUrl }),
54
+ ])
55
+
56
+ super({ resolver, cache })
57
+ }
58
+ }
@@ -0,0 +1,51 @@
1
+ import { Fetch } from '@atproto-labs/fetch'
2
+
3
+ import {
4
+ HandleResolveOptions,
5
+ HandleResolver,
6
+ ResolvedHandle,
7
+ isResolvedHandle,
8
+ } from './handle-resolver.js'
9
+
10
+ export type WellKnownHandleResolverOptions = {
11
+ fetch?: Fetch
12
+ }
13
+
14
+ export class WellKnownHandleResolver implements HandleResolver {
15
+ protected readonly fetch: Fetch
16
+
17
+ constructor({
18
+ fetch = globalThis.fetch,
19
+ }: WellKnownHandleResolverOptions = {}) {
20
+ this.fetch = fetch
21
+ }
22
+
23
+ public async resolve(
24
+ handle: string,
25
+ options?: HandleResolveOptions,
26
+ ): Promise<ResolvedHandle> {
27
+ const url = new URL('/.well-known/atproto-did', `https://${handle}`)
28
+
29
+ const headers = new Headers()
30
+ if (options?.noCache) headers.set('cache-control', 'no-cache')
31
+ const request = new Request(url, { headers, signal: options?.signal })
32
+
33
+ try {
34
+ const response = await (0, this.fetch)(request)
35
+ const text = await response.text()
36
+ const firstLine = text.split('\n')[0]!.trim()
37
+
38
+ if (isResolvedHandle(firstLine)) return firstLine
39
+
40
+ return null
41
+ } catch (err) {
42
+ // The the request failed, assume the handle does not resolve to a DID,
43
+ // unless the failure was due to the signal being aborted.
44
+ options?.signal?.throwIfAborted()
45
+
46
+ // TODO: propagate some errors as-is (?)
47
+
48
+ return null
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig/isomorphic.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
+ }