-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathHttpTransport.ts
More file actions
116 lines (111 loc) · 3.66 KB
/
Copy pathHttpTransport.ts
File metadata and controls
116 lines (111 loc) · 3.66 KB
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import {BaseRpcTransport, type JsonRpcEnvelope} from "./BaseRpcTransport";
import {type RequestOptions, type Transport, TransportRpcError} from "./Transport";
/**
* Construction options for {@link HttpTransport}.
*/
export interface HttpTransportOptions {
/**
* Override the global `fetch`. Useful in environments without a global
* fetch (older Node, React Native edge cases) or to inject a polyfill,
* mock (msw, jest), or auth-aware wrapper (`undici`, `node-fetch`).
*/
fetch?: typeof globalThis.fetch;
/**
* Static headers added to every request. Merged with the default
* `Content-Type: application/json` (which always wins for the body type).
*/
headers?: Record<string, string>;
}
/**
* Default concrete {@link Transport}: POSTs JSON-RPC envelopes to an HTTP
* endpoint. Used by every URL-string call site once a string is normalized
* into a transport.
*
* @example
* ```ts
* const t = new HttpTransport("https://api.candide.dev/public/v3/11155111");
* const chainId = await t.request<string>({ method: "eth_chainId" });
*
* // With auth headers and a custom fetch:
* const t2 = new HttpTransport("https://...", {
* headers: { Authorization: `Bearer ${token}` },
* fetch: myFetchWithRetry,
* });
* ```
*/
export class HttpTransport extends BaseRpcTransport {
/** Endpoint URL this transport POSTs to. */
readonly url: string;
/** Options passed at construction time. */
readonly options: HttpTransportOptions;
/**
* @param url - JSON-RPC endpoint URL (bundler, paymaster, or node)
* @param options - Optional fetch override and static headers
*/
constructor(url: string, options: HttpTransportOptions = {}) {
super();
this.url = url;
this.options = options;
}
protected async send(envelope: JsonRpcEnvelope, options?: RequestOptions): Promise<unknown> {
// Content-Type is fixed by this class (body is always a JSON-RPC
// envelope), so it wins against any user-supplied header.
const headers: Record<string, string> = {
...(this.options.headers ?? {}),
"Content-Type": "application/json",
};
const body = HttpTransport.serializeEnvelope(envelope);
const fetchImpl = this.options.fetch ?? globalThis.fetch;
const response = await fetchImpl(this.url, {
method: "POST",
headers,
body,
redirect: "follow",
signal: options?.signal,
});
const responseText = await response.text();
let parsed: unknown;
try {
parsed = JSON.parse(responseText);
} catch {
// non-JSON body (HTML error page, plain text) — the HTTP status is
// the real diagnostic, so surface it instead of a JSON parse error
throw new TransportRpcError(
-32603,
`HTTP ${response.status} ${response.statusText}: response body is not JSON`.trim(),
responseText.slice(0, 1000),
);
}
if (
!response.ok &&
(typeof parsed !== "object" ||
parsed == null ||
(!("error" in parsed) && !("result" in parsed)))
) {
// HTTP failure without a JSON-RPC envelope (e.g. a gateway's own
// error object) — keep the status; a proper envelope falls through
// to parseResponse which reports the server's JSON-RPC error
throw new TransportRpcError(
-32603,
`HTTP ${response.status} ${response.statusText}`.trim(),
parsed,
);
}
return parsed;
}
}
/**
* Narrowing helper for {@link HttpTransport}. Useful for code that wants to
* read the underlying URL — e.g. when serializing a configured Bundler for
* logging or diagnostics.
*
* @example
* ```ts
* if (isHttpTransport(bundler.transport)) {
* console.log("Bundler URL:", bundler.transport.url);
* }
* ```
*/
export function isHttpTransport(transport: Transport): transport is HttpTransport {
return transport instanceof HttpTransport;
}