~james/core

core/web/src/lib/auth/handleResolver.ts -rw-r--r-- 2.3 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import {
	CompositeHandleResolver,
	DohJsonHandleResolver,
	FailedHandleResolutionError,
	WellKnownHandleResolver,
	XrpcHandleResolver,
	type HandleResolver
} from "@atcute/identity-resolver";
import type { AtprotoDid } from "@atcute/lexicons/syntax";
import { createBobbinClient, type BobbinContext } from "#lib/api/client.ts";
import { fetchMiniDoc } from "#lib/api/identity.ts";

export const handleResolverUrls = (raw: string | undefined): string[] =>
	(raw ?? "")
		.split(",")
		.map((url) => url.trim().replace(/\/+$/, ""))
		.filter(Boolean);

const handleResolverFromTheNetwork = (bobbinUrl: () => string): HandleResolver =>
	new FallbackHandleResolver([
		new CompositeHandleResolver({
			methods: {
				dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }),
				http: new WellKnownHandleResolver()
			}
		}),
		bobbinHandleResolver(bobbinUrl)
	]);

const fetchHandleDid = async (
	ctx: BobbinContext,
	handle: string,
	signal?: AbortSignal
): Promise<AtprotoDid> => {
	const doc = await fetchMiniDoc(ctx, handle, { signal });
	return doc.did as AtprotoDid;
};

const bobbinHandleResolver = (bobbinUrl: () => string): HandleResolver => ({
	resolve: (handle, options) =>
		fetchHandleDid(
			createBobbinClient({ serviceUrl: bobbinUrl() }),
			handle,
			options?.signal
		).catch((cause: unknown) => {
			throw new FailedHandleResolutionError(handle, { cause });
		})
});

export class FallbackHandleResolver implements HandleResolver {
	readonly #resolvers: HandleResolver[];

	constructor(resolvers: HandleResolver[]) {
		this.#resolvers = resolvers;
	}

	async resolve(...args: Parameters<HandleResolver["resolve"]>) {
		let failure: unknown;
		for (const resolver of this.#resolvers) {
			try {
				return await resolver.resolve(...args);
			} catch (cause) {
				if (args[1]?.signal?.aborted) throw cause;
				failure = cause;
			}
		}
		throw failure;
	}
}

export const handleResolverFor = (urls: string[], bobbinUrl: () => string): HandleResolver =>
	urls.length === 0
		? handleResolverFromTheNetwork(bobbinUrl)
		: urls.length === 1
			? new XrpcHandleResolver({ serviceUrl: urls[0] as `${string}:${string}` })
			: new FallbackHandleResolver(
					urls.map(
						(url) =>
							new XrpcHandleResolver({ serviceUrl: url as `${string}:${string}` })
					)
				);