@apifuse/provider-sdk 2.1.0-beta.3 → 2.1.0-beta.4
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.
- package/AUTHORING.md +163 -8
- package/CHANGELOG.md +8 -1
- package/README.md +17 -16
- package/SUBMISSION.md +4 -4
- package/bin/apifuse-dev.ts +12 -5
- package/bin/apifuse-pack-check.ts +9 -2
- package/bin/apifuse-pack-smoke.ts +127 -6
- package/bin/apifuse-perf.ts +19 -15
- package/bin/apifuse-record.ts +41 -53
- package/bin/apifuse-submit-check.ts +179 -7
- package/bin/apifuse.ts +1 -1
- package/package.json +17 -8
- package/src/choice-token.ts +164 -0
- package/src/cli/commands.ts +1 -3
- package/src/cli/create.ts +159 -50
- package/src/cli/templates/provider/README.md.tpl +24 -7
- package/src/cli/templates/provider/dev.ts.tpl +1 -1
- package/src/cli/templates/provider/domain/README.md.tpl +3 -0
- package/src/cli/templates/provider/index.ts.tpl +5 -47
- package/src/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/src/cli/templates/provider/meta.ts.tpl +7 -0
- package/src/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/src/cli/templates/provider/operations/ping.ts.tpl +23 -0
- package/src/cli/templates/provider/schemas/ping.ts.tpl +16 -0
- package/src/cli/templates/provider/start.ts.tpl +1 -1
- package/src/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/src/config/loader.ts +1206 -9
- package/src/define.ts +1618 -104
- package/src/errors.ts +12 -0
- package/src/i18n/catalog.ts +121 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/keys.ts +64 -0
- package/src/index.ts +149 -8
- package/src/lint.ts +297 -42
- package/src/observability.ts +41 -0
- package/src/provider.ts +60 -3
- package/src/public-schema-field-lint.ts +237 -0
- package/src/runtime/auth-flow.ts +7 -0
- package/src/runtime/browser.ts +77 -21
- package/src/runtime/cache.ts +582 -0
- package/src/runtime/executor.ts +13 -1
- package/src/runtime/http.ts +939 -195
- package/src/runtime/insights.ts +11 -11
- package/src/runtime/instrumentation.ts +12 -4
- package/src/runtime/key-derivation.ts +1 -1
- package/src/runtime/keyring.ts +4 -3
- package/src/runtime/proxy-errors.ts +132 -0
- package/src/runtime/proxy-telemetry.ts +253 -0
- package/src/runtime/request-options.ts +66 -0
- package/src/runtime/state.ts +76 -0
- package/src/runtime/stealth.ts +1145 -0
- package/src/runtime/stt.ts +629 -0
- package/src/schema.ts +363 -1
- package/src/server/serve.ts +816 -58
- package/src/server/types.ts +35 -0
- package/src/stream.ts +210 -0
- package/src/testing/run.ts +17 -4
- package/src/types.ts +869 -50
- package/src/runtime/tls.ts +0 -434
- package/src/types/playwright-stealth.d.ts +0 -9
package/src/runtime/tls.ts
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
import { ModuleClient, SessionClient } from "tlsclientwrapper";
|
|
2
|
-
|
|
3
|
-
import type { ProxyResolutionOptions } from "../config/loader";
|
|
4
|
-
import { resolveProxyConfig } from "../config/loader";
|
|
5
|
-
import { SDKError, TransportError } from "../errors";
|
|
6
|
-
import { getStealthProfile } from "../stealth/profiles";
|
|
7
|
-
import type {
|
|
8
|
-
CookieJar,
|
|
9
|
-
TlsClient,
|
|
10
|
-
TlsFetchOptions,
|
|
11
|
-
TlsResponse,
|
|
12
|
-
TlsSession,
|
|
13
|
-
} from "../types";
|
|
14
|
-
|
|
15
|
-
const DEFAULT_PROFILE = "chrome-146";
|
|
16
|
-
|
|
17
|
-
const MISSING_PROXY_WARNING =
|
|
18
|
-
"[provider-sdk] Provider requested proxy routing, but no proxy URL was configured. Continuing without proxy.";
|
|
19
|
-
|
|
20
|
-
export type TlsClientOptions = ProxyResolutionOptions & {
|
|
21
|
-
warn?: (message: string) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Proxy-only TLS transport overrides. Use only for upstream proxy products
|
|
24
|
-
* that terminate CONNECT with a private CA instead of tunneling the origin
|
|
25
|
-
* certificate chain.
|
|
26
|
-
*/
|
|
27
|
-
proxyTls?: { insecureSkipVerify?: boolean };
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const REMOVED_CHROME_PROFILE_NAMES = new Set([
|
|
31
|
-
"chrome-120",
|
|
32
|
-
"chrome-124",
|
|
33
|
-
"chrome-129",
|
|
34
|
-
"chrome-130",
|
|
35
|
-
"chrome-131",
|
|
36
|
-
"chrome-133",
|
|
37
|
-
"chrome-144",
|
|
38
|
-
"chrome-146-psk",
|
|
39
|
-
"chrome-131-psk",
|
|
40
|
-
"chrome-130-psk",
|
|
41
|
-
"edge-131",
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
const TLS_PROFILE_MAP: Record<string, string> = {
|
|
45
|
-
"chrome-146": "chrome_146",
|
|
46
|
-
"firefox-132": "firefox_132",
|
|
47
|
-
"firefox-133": "firefox_133",
|
|
48
|
-
"firefox-135": "firefox_135",
|
|
49
|
-
"firefox-147": "firefox_147",
|
|
50
|
-
"safari-15": "safari_15_6_1",
|
|
51
|
-
"safari-16": "safari_16_0",
|
|
52
|
-
"ios-safari-17": "safari_ios_17_0",
|
|
53
|
-
"ios-safari-18": "safari_ios_18_0",
|
|
54
|
-
"ios-safari-26": "safari_ios_26_0",
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
type TlsClientWrapperResponse = {
|
|
58
|
-
status: number;
|
|
59
|
-
body: string;
|
|
60
|
-
headers?: Record<string, string>;
|
|
61
|
-
rawHeaders?: Record<string, string> | [string, string][];
|
|
62
|
-
usedProtocol?: string;
|
|
63
|
-
httpVersion?: string;
|
|
64
|
-
cookies?:
|
|
65
|
-
| Record<string, { name?: string; value?: string }>
|
|
66
|
-
| Array<{ name?: string; value?: string }>;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
class CookieJarImpl implements CookieJar {
|
|
70
|
-
private readonly cookies: Record<string, string>;
|
|
71
|
-
|
|
72
|
-
constructor(cookieStrings: string[]) {
|
|
73
|
-
this.cookies = {};
|
|
74
|
-
|
|
75
|
-
for (const cookieString of cookieStrings) {
|
|
76
|
-
const [nameValue] = cookieString.split(";");
|
|
77
|
-
if (!nameValue) {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const separatorIndex = nameValue.indexOf("=");
|
|
82
|
-
if (separatorIndex === -1) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const name = nameValue.slice(0, separatorIndex).trim();
|
|
87
|
-
const value = nameValue.slice(separatorIndex + 1).trim();
|
|
88
|
-
this.cookies[name] = value;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get(name: string): string | undefined {
|
|
93
|
-
return this.cookies[name];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
getAll(): Record<string, string> {
|
|
97
|
-
return { ...this.cookies };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
toString(): string {
|
|
101
|
-
return Object.entries(this.cookies)
|
|
102
|
-
.map(([name, value]) => `${name}=${value}`)
|
|
103
|
-
.join("; ");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
find(predicate: (cookie: string) => boolean): string | undefined {
|
|
107
|
-
for (const [name, value] of Object.entries(this.cookies)) {
|
|
108
|
-
const cookie = `${name}=${value}`;
|
|
109
|
-
if (predicate(cookie)) {
|
|
110
|
-
return cookie;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function resolveIdentifier(profileName: string): string {
|
|
119
|
-
if (REMOVED_CHROME_PROFILE_NAMES.has(profileName)) {
|
|
120
|
-
throw new SDKError(`Unknown stealth profile: ${profileName}`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const profile = getStealthProfile(profileName);
|
|
125
|
-
if (profile.tlsClientIdentifier) {
|
|
126
|
-
return profile.tlsClientIdentifier;
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
// Unknown profile name falls through to static mapping and heuristic.
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return TLS_PROFILE_MAP[profileName] ?? profileName.replaceAll("-", "_");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function resolveUrl(baseUrl: string, url: string): string {
|
|
136
|
-
return new URL(url, baseUrl).toString();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function toCookieStrings(
|
|
140
|
-
response: TlsClientWrapperResponse,
|
|
141
|
-
): string[] | undefined {
|
|
142
|
-
const cookies = response.cookies;
|
|
143
|
-
if (!cookies) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const values = Array.isArray(cookies) ? cookies : Object.values(cookies);
|
|
148
|
-
const normalized = values
|
|
149
|
-
.map((cookie) => {
|
|
150
|
-
if (!cookie?.name) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return `${cookie.name}=${cookie.value ?? ""}`;
|
|
155
|
-
})
|
|
156
|
-
.filter((cookie): cookie is string => cookie !== null);
|
|
157
|
-
|
|
158
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function normalizeRawHeaders(
|
|
162
|
-
rawHeaders: TlsClientWrapperResponse["rawHeaders"],
|
|
163
|
-
headers: Record<string, string>,
|
|
164
|
-
): [string, string][] {
|
|
165
|
-
if (Array.isArray(rawHeaders)) {
|
|
166
|
-
return rawHeaders;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (rawHeaders && typeof rawHeaders === "object") {
|
|
170
|
-
return Object.entries(rawHeaders);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return Object.entries(headers);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function normalizeResponse(
|
|
177
|
-
response: TlsClientWrapperResponse,
|
|
178
|
-
): TlsResponse {
|
|
179
|
-
const headers = response.headers ?? {};
|
|
180
|
-
const cookies = new CookieJarImpl(toCookieStrings(response) ?? []);
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
status: response.status,
|
|
184
|
-
ok: response.status >= 200 && response.status < 300,
|
|
185
|
-
headers,
|
|
186
|
-
rawHeaders: normalizeRawHeaders(response.rawHeaders, headers),
|
|
187
|
-
body: response.body,
|
|
188
|
-
httpVersion: response.httpVersion ?? response.usedProtocol,
|
|
189
|
-
cookies,
|
|
190
|
-
json<T>(): Promise<T> {
|
|
191
|
-
return Promise.resolve(JSON.parse(response.body) as T);
|
|
192
|
-
},
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function normalizeBody(body: TlsFetchOptions["body"]): string | null {
|
|
197
|
-
if (body === undefined) {
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (typeof body === "string") {
|
|
202
|
-
return body;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (Buffer.isBuffer(body)) {
|
|
206
|
-
return body.toString();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return String(body);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function sendRequest(
|
|
213
|
-
session: SessionClient,
|
|
214
|
-
method: string,
|
|
215
|
-
url: string,
|
|
216
|
-
options: TlsFetchOptions,
|
|
217
|
-
): Promise<TlsClientWrapperResponse> {
|
|
218
|
-
const requestOptions = {
|
|
219
|
-
headers: options.headers,
|
|
220
|
-
proxy: options.proxy,
|
|
221
|
-
...(options.headerOrder ? { headerOrder: options.headerOrder } : {}),
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
switch (method) {
|
|
225
|
-
case "POST":
|
|
226
|
-
return (await session.post(
|
|
227
|
-
url,
|
|
228
|
-
normalizeBody(options.body),
|
|
229
|
-
requestOptions,
|
|
230
|
-
)) as TlsClientWrapperResponse;
|
|
231
|
-
case "PUT":
|
|
232
|
-
return (await session.put(
|
|
233
|
-
url,
|
|
234
|
-
normalizeBody(options.body),
|
|
235
|
-
requestOptions,
|
|
236
|
-
)) as TlsClientWrapperResponse;
|
|
237
|
-
case "PATCH":
|
|
238
|
-
return (await session.patch(
|
|
239
|
-
url,
|
|
240
|
-
normalizeBody(options.body),
|
|
241
|
-
requestOptions,
|
|
242
|
-
)) as TlsClientWrapperResponse;
|
|
243
|
-
case "DELETE":
|
|
244
|
-
return (await session.delete(
|
|
245
|
-
url,
|
|
246
|
-
requestOptions,
|
|
247
|
-
)) as TlsClientWrapperResponse;
|
|
248
|
-
case "HEAD":
|
|
249
|
-
return (await session.head(
|
|
250
|
-
url,
|
|
251
|
-
requestOptions,
|
|
252
|
-
)) as TlsClientWrapperResponse;
|
|
253
|
-
case "OPTIONS":
|
|
254
|
-
return (await session.options(
|
|
255
|
-
url,
|
|
256
|
-
requestOptions,
|
|
257
|
-
)) as TlsClientWrapperResponse;
|
|
258
|
-
default:
|
|
259
|
-
return (await session.get(
|
|
260
|
-
url,
|
|
261
|
-
requestOptions,
|
|
262
|
-
)) as TlsClientWrapperResponse;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function createSessionFetcher(
|
|
267
|
-
baseUrl: string,
|
|
268
|
-
defaultProfile: string,
|
|
269
|
-
moduleClient: ModuleClient,
|
|
270
|
-
clientOptions: TlsClientOptions,
|
|
271
|
-
): TlsSession {
|
|
272
|
-
let sessionClient: SessionClient | null = null;
|
|
273
|
-
let activeProxy: string | undefined;
|
|
274
|
-
let activeTlsIdentifier: string | undefined;
|
|
275
|
-
let activeInsecureSkipVerify = false;
|
|
276
|
-
let hasWarnedMissingProxy = false;
|
|
277
|
-
const warn = clientOptions.warn ?? console.warn;
|
|
278
|
-
|
|
279
|
-
function resolveRequestProxy(options?: TlsFetchOptions): string | undefined {
|
|
280
|
-
const resolvedProxy = resolveProxyConfig({
|
|
281
|
-
proxy: options?.proxy ?? clientOptions.proxy,
|
|
282
|
-
upstream: clientOptions.upstream,
|
|
283
|
-
apifuseConfig: clientOptions.apifuseConfig,
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
if (resolvedProxy.shouldWarn && !hasWarnedMissingProxy) {
|
|
287
|
-
hasWarnedMissingProxy = true;
|
|
288
|
-
warn(MISSING_PROXY_WARNING);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return resolvedProxy.url;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function closeCurrentSession(): void {
|
|
295
|
-
const client = sessionClient as
|
|
296
|
-
| (SessionClient & {
|
|
297
|
-
destroySession?: () => Promise<unknown>;
|
|
298
|
-
close?: () => void;
|
|
299
|
-
})
|
|
300
|
-
| null;
|
|
301
|
-
|
|
302
|
-
void client?.destroySession?.();
|
|
303
|
-
client?.close?.();
|
|
304
|
-
sessionClient = null;
|
|
305
|
-
activeProxy = undefined;
|
|
306
|
-
activeTlsIdentifier = undefined;
|
|
307
|
-
activeInsecureSkipVerify = false;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function getSessionClient(
|
|
311
|
-
profile?: string,
|
|
312
|
-
proxy?: string,
|
|
313
|
-
ja3?: string,
|
|
314
|
-
insecureSkipVerify = false,
|
|
315
|
-
): SessionClient {
|
|
316
|
-
const tlsIdentifier = ja3 ?? resolveIdentifier(profile ?? defaultProfile);
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
!sessionClient ||
|
|
320
|
-
activeProxy !== proxy ||
|
|
321
|
-
activeTlsIdentifier !== tlsIdentifier ||
|
|
322
|
-
activeInsecureSkipVerify !== insecureSkipVerify
|
|
323
|
-
) {
|
|
324
|
-
if (sessionClient) {
|
|
325
|
-
closeCurrentSession();
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
sessionClient = new SessionClient(moduleClient, {
|
|
329
|
-
tlsClientIdentifier: tlsIdentifier,
|
|
330
|
-
...(proxy ? { proxyUrl: proxy } : {}),
|
|
331
|
-
...(insecureSkipVerify ? { insecureSkipVerify: true } : {}),
|
|
332
|
-
timeoutSeconds: 30,
|
|
333
|
-
} as ConstructorParameters<typeof SessionClient>[1]);
|
|
334
|
-
activeProxy = proxy;
|
|
335
|
-
activeTlsIdentifier = tlsIdentifier;
|
|
336
|
-
activeInsecureSkipVerify = insecureSkipVerify;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return sessionClient;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return {
|
|
343
|
-
async fetch(url, options = {}) {
|
|
344
|
-
const proxy = resolveRequestProxy(options);
|
|
345
|
-
const insecureSkipVerify = Boolean(
|
|
346
|
-
options.tls?.insecureSkipVerify ??
|
|
347
|
-
(proxy && clientOptions.proxyTls?.insecureSkipVerify),
|
|
348
|
-
);
|
|
349
|
-
const session = getSessionClient(
|
|
350
|
-
options.profile,
|
|
351
|
-
proxy,
|
|
352
|
-
options.tls?.ja3,
|
|
353
|
-
insecureSkipVerify,
|
|
354
|
-
);
|
|
355
|
-
const requestUrl = resolveUrl(baseUrl, url);
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
const method = (options.method ?? "GET").toUpperCase();
|
|
359
|
-
const response = await sendRequest(session, method, requestUrl, {
|
|
360
|
-
...options,
|
|
361
|
-
proxy,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
if (response.status >= 400 && options.throwOnHttpError !== false) {
|
|
365
|
-
throw new TransportError(
|
|
366
|
-
`Upstream request failed with status ${response.status}`,
|
|
367
|
-
{
|
|
368
|
-
status: response.status,
|
|
369
|
-
},
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
return normalizeResponse(response);
|
|
374
|
-
} catch (error) {
|
|
375
|
-
if (error instanceof SDKError || error instanceof TransportError) {
|
|
376
|
-
throw error;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
throw new TransportError("Network error", {
|
|
380
|
-
status: 0,
|
|
381
|
-
cause: error instanceof Error ? error : undefined,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
},
|
|
385
|
-
close() {
|
|
386
|
-
closeCurrentSession();
|
|
387
|
-
},
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export function createTlsClient(
|
|
392
|
-
baseUrl: string,
|
|
393
|
-
defaultProfileOrOptions: string | TlsClientOptions = DEFAULT_PROFILE,
|
|
394
|
-
clientOptions: TlsClientOptions = {},
|
|
395
|
-
): TlsClient {
|
|
396
|
-
const defaultProfile =
|
|
397
|
-
typeof defaultProfileOrOptions === "string"
|
|
398
|
-
? defaultProfileOrOptions
|
|
399
|
-
: DEFAULT_PROFILE;
|
|
400
|
-
const resolvedClientOptions =
|
|
401
|
-
typeof defaultProfileOrOptions === "string"
|
|
402
|
-
? clientOptions
|
|
403
|
-
: defaultProfileOrOptions;
|
|
404
|
-
const moduleClient = new ModuleClient();
|
|
405
|
-
let sharedSession: TlsSession | null = null;
|
|
406
|
-
|
|
407
|
-
function getSharedSession(): TlsSession {
|
|
408
|
-
if (!sharedSession) {
|
|
409
|
-
sharedSession = createSessionFetcher(
|
|
410
|
-
baseUrl,
|
|
411
|
-
defaultProfile,
|
|
412
|
-
moduleClient,
|
|
413
|
-
resolvedClientOptions,
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
return sharedSession;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
fetch(url: string, options?: TlsFetchOptions) {
|
|
422
|
-
return getSharedSession().fetch(url, options);
|
|
423
|
-
},
|
|
424
|
-
createSession(opts?: { profile?: string }) {
|
|
425
|
-
const sessionProfile = opts?.profile ?? defaultProfile;
|
|
426
|
-
return createSessionFetcher(
|
|
427
|
-
baseUrl,
|
|
428
|
-
sessionProfile,
|
|
429
|
-
moduleClient,
|
|
430
|
-
resolvedClientOptions,
|
|
431
|
-
);
|
|
432
|
-
},
|
|
433
|
-
};
|
|
434
|
-
}
|