Suite of AT Protocol TypeScript libraries built on web standards
1import type { DidString } from "@atp/lex";
2
3export type FetchHandler = (
4 path: `/${string}`,
5 init: RequestInit,
6) => Promise<Response>;
7
8export interface Agent {
9 readonly did?: DidString;
10 fetchHandler: FetchHandler;
11}
12
13export function isAgent(value: unknown): value is Agent {
14 return (
15 typeof value === "object" &&
16 value !== null &&
17 "fetchHandler" in value &&
18 typeof value.fetchHandler === "function" &&
19 (!("did" in value) ||
20 value.did === undefined ||
21 typeof value.did === "string")
22 );
23}
24
25export type AgentConfig = {
26 did?: DidString;
27 service: string | URL;
28 headers?: HeadersInit;
29 fetch?: typeof globalThis.fetch;
30};
31
32export type AgentOptions = AgentConfig | FetchHandler | string | URL;
33
34export function buildAgent<O extends Agent | AgentOptions>(
35 options: O,
36): O extends Agent ? O : Agent;
37export function buildAgent(options: Agent | AgentOptions): Agent {
38 const config: Agent | AgentConfig = typeof options === "function"
39 ? { did: undefined, fetchHandler: options }
40 : typeof options === "string" || options instanceof URL
41 ? { did: undefined, service: options }
42 : options;
43
44 if (isAgent(config)) {
45 return config;
46 }
47
48 const { service, fetch = globalThis.fetch } = config;
49
50 if (typeof fetch !== "function") {
51 throw new TypeError("fetch() is not available in this environment");
52 }
53
54 return {
55 get did() {
56 return config.did;
57 },
58 fetchHandler(path, init) {
59 const headers = config.headers != null && init.headers != null
60 ? mergeHeaders(config.headers, init.headers)
61 : config.headers || init.headers;
62
63 return fetch(
64 new URL(path, service),
65 headers !== init.headers ? { ...init, headers } : init,
66 );
67 },
68 };
69}
70
71function mergeHeaders(
72 defaultHeaders: HeadersInit,
73 requestHeaders: HeadersInit,
74): Headers {
75 const result = new Headers(defaultHeaders);
76 const overrides = requestHeaders instanceof Headers
77 ? requestHeaders
78 : new Headers(requestHeaders);
79
80 for (const [key, value] of overrides.entries()) {
81 result.set(key, value);
82 }
83
84 return result;
85}