Skip to content

Commit 7edd1e0

Browse files
committed
refactor: centralize secure clob header resolution
1 parent 4d3998c commit 7edd1e0

7 files changed

Lines changed: 180 additions & 189 deletions

File tree

packages/client/src/ServiceClient.ts

Lines changed: 103 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,25 @@ import { ResultAsync } from '@polymarket/types';
22
import ky, { type KyInstance } from 'ky';
33
import { RateLimitError, RequestRejectedError, TransportError } from './errors';
44

5+
/** @internal */
6+
export type ServiceRequest = {
7+
method: 'DELETE' | 'GET' | 'POST';
8+
path: string;
9+
body?: string;
10+
headers?: HeadersInit;
11+
json?: unknown;
12+
params?: URLSearchParams;
13+
};
14+
15+
/** @internal */
16+
export type RequestHeadersResolver = (
17+
request: ServiceRequest,
18+
) => Promise<HeadersInit>;
19+
520
export type ServiceClientConfig = {
621
root: string;
22+
/** @internal */
23+
resolveHeaders?: RequestHeadersResolver;
724
};
825

926
export type ServiceClientGetOptions = {
@@ -26,9 +43,11 @@ export type ServiceClientDeleteOptions = {
2643
*/
2744
export class ServiceClient {
2845
readonly #client: KyInstance;
46+
readonly #resolveHeaders?: RequestHeadersResolver;
2947

30-
constructor({ root }: ServiceClientConfig) {
48+
constructor({ root, resolveHeaders }: ServiceClientConfig) {
3149
this.#client = ky.create({ prefixUrl: root, throwHttpErrors: false });
50+
this.#resolveHeaders = resolveHeaders;
3251
}
3352

3453
get(
@@ -38,12 +57,7 @@ export class ServiceClient {
3857
Response,
3958
RateLimitError | RequestRejectedError | TransportError
4059
> {
41-
return this.#toResult(
42-
this.#client.get(this.#normalizePath(path), {
43-
headers: options.headers,
44-
searchParams: options.params,
45-
}),
46-
);
60+
return this.#request('GET', path, options);
4761
}
4862

4963
post(
@@ -53,12 +67,7 @@ export class ServiceClient {
5367
Response,
5468
RateLimitError | RequestRejectedError | TransportError
5569
> {
56-
return this.#toResult(
57-
this.#client.post(this.#normalizePath(path), {
58-
headers: options.headers,
59-
json: options.json,
60-
}),
61-
);
70+
return this.#request('POST', path, options);
6271
}
6372

6473
del(
@@ -68,18 +77,93 @@ export class ServiceClient {
6877
Response,
6978
RateLimitError | RequestRejectedError | TransportError
7079
> {
71-
return this.#toResult(
72-
this.#client.delete(this.#normalizePath(path), {
73-
headers: options.headers,
74-
json: options.json,
75-
}),
76-
);
80+
return this.#request('DELETE', path, options);
7781
}
7882

7983
#normalizePath(path: string) {
8084
return path.startsWith('/') ? path.slice(1) : path;
8185
}
8286

87+
#request(
88+
method: ServiceRequest['method'],
89+
path: string,
90+
options:
91+
| ServiceClientDeleteOptions
92+
| ServiceClientGetOptions
93+
| ServiceClientPostOptions,
94+
): ResultAsync<
95+
Response,
96+
RateLimitError | RequestRejectedError | TransportError
97+
> {
98+
return this.#toResult(this.#send(method, path, options));
99+
}
100+
101+
async #send(
102+
method: ServiceRequest['method'],
103+
path: string,
104+
options:
105+
| ServiceClientDeleteOptions
106+
| ServiceClientGetOptions
107+
| ServiceClientPostOptions,
108+
): Promise<Response> {
109+
const request = this.#createRequest(method, path, options);
110+
const resolvedHeaders = await this.#resolveHeaders?.(request);
111+
const headers = this.#mergeHeaders(request.headers, resolvedHeaders);
112+
113+
if (request.body !== undefined && !headers.has('content-type')) {
114+
headers.set('content-type', 'application/json');
115+
}
116+
117+
return this.#client(this.#normalizePath(path), {
118+
body: request.body,
119+
headers,
120+
method,
121+
searchParams: request.params,
122+
});
123+
}
124+
125+
#createRequest(
126+
method: ServiceRequest['method'],
127+
path: string,
128+
options:
129+
| ServiceClientDeleteOptions
130+
| ServiceClientGetOptions
131+
| ServiceClientPostOptions,
132+
): ServiceRequest {
133+
return {
134+
body: 'json' in options ? this.#serializeJson(options.json) : undefined,
135+
headers: options.headers,
136+
json: 'json' in options ? options.json : undefined,
137+
method,
138+
params: 'params' in options ? options.params : undefined,
139+
path,
140+
};
141+
}
142+
143+
#serializeJson(json: unknown): string | undefined {
144+
if (json === undefined) {
145+
return undefined;
146+
}
147+
148+
return JSON.stringify(json);
149+
}
150+
151+
#mergeHeaders(...sources: Array<HeadersInit | undefined>): Headers {
152+
const headers = new Headers();
153+
154+
for (const source of sources) {
155+
if (source === undefined) {
156+
continue;
157+
}
158+
159+
for (const [key, value] of new Headers(source).entries()) {
160+
headers.set(key, value);
161+
}
162+
}
163+
164+
return headers;
165+
}
166+
83167
#toResult(
84168
promise: Promise<Response>,
85169
): ResultAsync<

0 commit comments

Comments
 (0)