@apifuse/provider-sdk 2.1.0-beta.9 → 2.2.0-beta.10
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 +240 -0
- package/CHANGELOG.md +93 -0
- package/README.md +26 -10
- package/SUBMISSION.md +11 -12
- package/bin/apifuse-check.ts +44 -59
- package/bin/apifuse-create.ts +1 -1
- package/bin/apifuse-dev.ts +27 -52
- package/bin/apifuse-pack-check.ts +36 -0
- package/bin/apifuse-pack-smoke.ts +22 -81
- package/bin/apifuse-pack-types.ts +266 -0
- package/bin/apifuse-perf.ts +45 -127
- package/bin/apifuse-record.ts +53 -70
- package/bin/apifuse-submit-check.ts +2177 -353
- package/bin/apifuse-sync-assets.ts +117 -0
- package/bin/apifuse.ts +1 -1
- package/bin/submit-check-delimited-text.ts +50 -0
- package/bin/submit-check-xml-semantics.ts +204 -0
- package/bin/submit-check-xml.ts +134 -0
- package/dist/auth-turn/auth-turn.v1.schema.json +89 -0
- package/dist/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/dist/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/dist/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/dist/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/dist/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/dist/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/dist/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/dist/auth-turn/fixtures/valid/abort.json +8 -0
- package/dist/auth-turn/fixtures/valid/challenge.json +17 -0
- package/dist/auth-turn/fixtures/valid/complete.json +13 -0
- package/dist/auth-turn/fixtures/valid/form.json +14 -0
- package/dist/auth-turn/fixtures/valid/message.json +13 -0
- package/dist/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/dist/auth-turn/fixtures/valid/pending.json +5 -0
- package/dist/auth-turn/fixtures/valid/poll.json +9 -0
- package/dist/auth-turn/fixtures/valid/redirect.json +16 -0
- package/dist/auth-turn/fixtures/valid/retry.json +8 -0
- package/dist/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/dist/auth-turn/index.d.ts +195 -0
- package/dist/auth-turn/index.js +133 -0
- package/dist/auth.d.ts +76 -0
- package/dist/auth.js +427 -0
- package/dist/ceremonies/index.d.ts +1 -1
- package/dist/ceremonies/index.js +14 -48
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +8 -0
- package/dist/cli/create.d.ts +3 -0
- package/dist/cli/create.js +47 -33
- package/dist/cli/prompt-assets.d.ts +80 -0
- package/dist/cli/prompt-assets.js +743 -0
- package/dist/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/dist/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/dist/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/dist/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/dist/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/dist/cli/templates/provider/.dockerignore.tpl +22 -0
- package/dist/cli/templates/provider/.gitignore.tpl +22 -0
- package/dist/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/dist/cli/templates/provider/Dockerfile.tpl +7 -0
- package/dist/cli/templates/provider/README.md.tpl +163 -0
- package/dist/cli/templates/provider/dev.ts.tpl +5 -0
- package/dist/cli/templates/provider/domain/README.md.tpl +3 -0
- package/dist/cli/templates/provider/index.test.ts.tpl +13 -0
- package/dist/cli/templates/provider/index.ts.tpl +15 -0
- package/dist/cli/templates/provider/mappers/README.md.tpl +3 -0
- package/dist/cli/templates/provider/meta.ts.tpl +7 -0
- package/dist/cli/templates/provider/operations/index.ts.tpl +5 -0
- package/dist/cli/templates/provider/operations/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/schemas/ping.ts.tpl +24 -0
- package/dist/cli/templates/provider/start.ts.tpl +5 -0
- package/dist/cli/templates/provider/upstream/README.md.tpl +3 -0
- package/dist/config/loader.d.ts +149 -8
- package/dist/config/loader.js +378 -89
- package/dist/contract-serialization.d.ts +2 -2
- package/dist/contract-serialization.js +3 -6
- package/dist/contract-types.d.ts +2 -2
- package/dist/contract.d.ts +3 -3
- package/dist/contract.js +5 -6
- package/dist/define.d.ts +13 -1
- package/dist/define.js +245 -178
- package/dist/dev.d.ts +1 -1
- package/dist/dev.js +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +48 -0
- package/dist/i18n/catalog.d.ts +2 -2
- package/dist/i18n/catalog.js +4 -10
- package/dist/i18n/index.d.ts +2 -2
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/keys.d.ts +2 -2
- package/dist/index.d.ts +44 -41
- package/dist/index.js +39 -36
- package/dist/lint.d.ts +1 -0
- package/dist/lint.js +35 -15
- package/dist/provider.d.ts +11 -9
- package/dist/provider.js +9 -8
- package/dist/public-schema-field-lint.d.ts +1 -1
- package/dist/recipes/gov-api.js +1 -1
- package/dist/runtime/auth-flow.d.ts +1 -1
- package/dist/runtime/auth-flow.js +4 -2
- package/dist/runtime/browser.d.ts +1 -1
- package/dist/runtime/browser.js +214 -25
- package/dist/runtime/cache.d.ts +1 -1
- package/dist/runtime/cache.js +4 -8
- package/dist/runtime/choice.d.ts +1 -1
- package/dist/runtime/choice.js +31 -35
- package/dist/runtime/credential.d.ts +1 -1
- package/dist/runtime/credential.js +1 -1
- package/dist/runtime/env.d.ts +1 -1
- package/dist/runtime/executor.d.ts +1 -1
- package/dist/runtime/executor.js +15 -3
- package/dist/runtime/http.d.ts +2 -2
- package/dist/runtime/http.js +160 -344
- package/dist/runtime/insights.d.ts +1 -1
- package/dist/runtime/insights.js +6 -13
- package/dist/runtime/instrumentation.d.ts +2 -2
- package/dist/runtime/instrumentation.js +56 -19
- package/dist/runtime/keyring.js +1 -1
- package/dist/runtime/namespace.js +1 -1
- package/dist/runtime/otlp.d.ts +1 -1
- package/dist/runtime/perf.d.ts +1 -1
- package/dist/runtime/provider.d.ts +1 -1
- package/dist/runtime/provider.js +1 -2
- package/dist/runtime/proxy-errors.d.ts +1 -1
- package/dist/runtime/proxy-errors.js +9 -7
- package/dist/runtime/proxy-nodemaven.d.ts +35 -0
- package/dist/runtime/proxy-nodemaven.js +128 -0
- package/dist/runtime/proxy-retry-policy.d.ts +40 -0
- package/dist/runtime/proxy-retry-policy.js +326 -0
- package/dist/runtime/proxy-telemetry.d.ts +2 -1
- package/dist/runtime/proxy-telemetry.js +55 -52
- package/dist/runtime/redis.d.ts +1 -1
- package/dist/runtime/redis.js +2 -4
- package/dist/runtime/request-options.d.ts +1 -1
- package/dist/runtime/secrets.d.ts +27 -0
- package/dist/runtime/secrets.js +51 -0
- package/dist/runtime/state.d.ts +2 -2
- package/dist/runtime/state.js +15 -4
- package/dist/runtime/stealth.d.ts +7 -4
- package/dist/runtime/stealth.js +257 -215
- package/dist/runtime/stt.d.ts +1 -1
- package/dist/runtime/stt.js +11 -15
- package/dist/runtime/trace.d.ts +2 -2
- package/dist/runtime/trace.js +2 -4
- package/dist/runtime/waterfall.d.ts +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +7 -15
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +1 -1
- package/dist/server/index.d.ts +7 -3
- package/dist/server/index.js +6 -2
- package/dist/server/self-test-input-tokens.d.ts +1 -0
- package/dist/server/self-test-input-tokens.js +37 -0
- package/dist/server/self-test-redaction.d.ts +20 -0
- package/dist/server/self-test-redaction.js +70 -0
- package/dist/server/self-test-token.d.ts +30 -0
- package/dist/server/self-test-token.js +50 -0
- package/dist/server/self-test.d.ts +199 -0
- package/dist/server/self-test.js +1113 -0
- package/dist/server/serve.d.ts +14 -3
- package/dist/server/serve.js +135 -64
- package/dist/server/types.d.ts +10 -9
- package/dist/server/types.js +3 -7
- package/dist/stealth/profiles.d.ts +1 -1
- package/dist/stealth/profiles.js +5 -14
- package/dist/stream.d.ts +1 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/testing/run.d.ts +1 -1
- package/dist/testing/run.js +12 -15
- package/dist/types.d.ts +237 -1
- package/dist/user-input.d.ts +30 -0
- package/dist/user-input.js +66 -0
- package/package.json +16 -5
- package/src/auth-turn/auth-turn.v1.schema.json +89 -0
- package/src/auth-turn/fixtures/invalid/empty-kind.json +4 -0
- package/src/auth-turn/fixtures/invalid/expires-at-not-string.json +5 -0
- package/src/auth-turn/fixtures/invalid/missing-kind.json +3 -0
- package/src/auth-turn/fixtures/invalid/missing-turn-id.json +3 -0
- package/src/auth-turn/fixtures/invalid/timing-unknown-field.json +7 -0
- package/src/auth-turn/fixtures/invalid/turn-id-snake-case.json +4 -0
- package/src/auth-turn/fixtures/invalid/unknown-top-level-field.json +5 -0
- package/src/auth-turn/fixtures/valid/abort.json +8 -0
- package/src/auth-turn/fixtures/valid/challenge.json +17 -0
- package/src/auth-turn/fixtures/valid/complete.json +13 -0
- package/src/auth-turn/fixtures/valid/form.json +14 -0
- package/src/auth-turn/fixtures/valid/message.json +13 -0
- package/src/auth-turn/fixtures/valid/multi_choice.json +15 -0
- package/src/auth-turn/fixtures/valid/pending.json +5 -0
- package/src/auth-turn/fixtures/valid/poll.json +9 -0
- package/src/auth-turn/fixtures/valid/redirect.json +16 -0
- package/src/auth-turn/fixtures/valid/retry.json +8 -0
- package/src/auth-turn/fixtures/valid/unknown-kind.json +7 -0
- package/src/auth-turn/index.ts +177 -0
- package/src/auth.ts +728 -0
- package/src/ceremonies/index.ts +33 -121
- package/src/cli/commands.ts +10 -0
- package/src/cli/create.ts +69 -99
- package/src/cli/prompt-assets.ts +865 -0
- package/src/cli/templates/provider/.agents/skills/fixtures-and-recording/SKILL.md.tpl +58 -0
- package/src/cli/templates/provider/.agents/skills/health-checks-and-fail-closed/SKILL.md.tpl +65 -0
- package/src/cli/templates/provider/.agents/skills/normalization-standards/SKILL.md.tpl +57 -0
- package/src/cli/templates/provider/.agents/skills/pagination-and-counts/SKILL.md.tpl +52 -0
- package/src/cli/templates/provider/.agents/skills/upstream-contract-verification/SKILL.md.tpl +45 -0
- package/src/cli/templates/provider/.agents/skills/upstream-notes/README.md.tpl +13 -0
- package/src/cli/templates/provider/AGENTS.md.tpl +96 -0
- package/src/cli/templates/provider/README.md.tpl +7 -4
- package/src/config/loader.ts +543 -208
- package/src/contract-serialization.ts +5 -11
- package/src/contract-types.ts +2 -2
- package/src/contract.ts +13 -28
- package/src/define.ts +397 -528
- package/src/dev.ts +4 -9
- package/src/errors.ts +58 -5
- package/src/i18n/catalog.ts +10 -32
- package/src/i18n/index.ts +2 -2
- package/src/i18n/keys.ts +5 -11
- package/src/index.ts +64 -41
- package/src/lint.ts +122 -159
- package/src/provider.ts +40 -9
- package/src/public-schema-field-lint.ts +7 -33
- package/src/recipes/gov-api.ts +2 -5
- package/src/runtime/auth-flow.ts +6 -6
- package/src/runtime/browser.ts +320 -151
- package/src/runtime/cache.ts +20 -67
- package/src/runtime/choice.ts +79 -132
- package/src/runtime/credential.ts +2 -2
- package/src/runtime/env.ts +1 -1
- package/src/runtime/executor.ts +23 -20
- package/src/runtime/http.ts +216 -539
- package/src/runtime/insights.ts +15 -53
- package/src/runtime/instrumentation.ts +78 -65
- package/src/runtime/keyring.ts +7 -19
- package/src/runtime/namespace.ts +2 -7
- package/src/runtime/otlp.ts +12 -23
- package/src/runtime/perf.ts +1 -1
- package/src/runtime/provider.ts +4 -9
- package/src/runtime/proxy-errors.ts +29 -42
- package/src/runtime/proxy-nodemaven.ts +178 -0
- package/src/runtime/proxy-retry-policy.ts +469 -0
- package/src/runtime/proxy-telemetry.ts +79 -77
- package/src/runtime/redis.ts +4 -12
- package/src/runtime/request-options.ts +4 -13
- package/src/runtime/secrets.ts +64 -0
- package/src/runtime/state.ts +41 -110
- package/src/runtime/stealth.ts +331 -369
- package/src/runtime/stt.ts +38 -94
- package/src/runtime/trace.ts +14 -44
- package/src/runtime/waterfall.ts +5 -18
- package/src/schema.ts +23 -84
- package/src/serve.ts +1 -1
- package/src/server/index.ts +44 -3
- package/src/server/self-test-input-tokens.ts +46 -0
- package/src/server/self-test-redaction.ts +97 -0
- package/src/server/self-test-token.ts +70 -0
- package/src/server/self-test.ts +1450 -0
- package/src/server/serve.ts +206 -216
- package/src/server/types.ts +7 -19
- package/src/stealth/profiles.ts +10 -26
- package/src/stream.ts +8 -19
- package/src/testing/index.ts +2 -2
- package/src/testing/run.ts +24 -64
- package/src/types.ts +274 -1
- package/src/user-input.ts +118 -0
package/src/runtime/browser.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import type { Frame, LaunchOptions, Locator, Page } from "playwright";
|
|
2
|
+
import type { Frame, LaunchOptions, Locator, Page, Request, Route } from "playwright";
|
|
3
3
|
|
|
4
|
-
import { ProviderError } from "../errors";
|
|
4
|
+
import { ProviderError } from "../errors.js";
|
|
5
5
|
import type {
|
|
6
6
|
BrowserChallengeRequest,
|
|
7
7
|
BrowserChallengeResult,
|
|
@@ -11,20 +11,25 @@ import type {
|
|
|
11
11
|
BrowserLocator,
|
|
12
12
|
BrowserOptions,
|
|
13
13
|
BrowserPage,
|
|
14
|
-
|
|
14
|
+
BrowserResourceBody,
|
|
15
|
+
BrowserResourceDecision,
|
|
16
|
+
BrowserResourceMethod,
|
|
17
|
+
BrowserResourcePolicy,
|
|
18
|
+
BrowserResourceRequest,
|
|
19
|
+
} from "../types.js";
|
|
15
20
|
|
|
16
21
|
const require = createRequire(import.meta.url);
|
|
17
22
|
const DEFAULT_WAIT_TIMEOUT_MS = 30_000;
|
|
18
23
|
const SELECTOR_POLL_INTERVAL_MS = 100;
|
|
24
|
+
const RESOURCE_POLICY_ROUTE_PATTERN = "**/*";
|
|
25
|
+
const DEFAULT_RESOURCE_METHODS = ["GET", "HEAD"] as const;
|
|
19
26
|
|
|
20
27
|
type PlaywrightModule = typeof import("playwright");
|
|
21
28
|
type PlaywrightExtraModule = {
|
|
22
29
|
chromium: PlaywrightModule["chromium"] & { use(plugin: unknown): unknown };
|
|
23
30
|
};
|
|
24
31
|
|
|
25
|
-
type StealthPluginFactory = (options?: {
|
|
26
|
-
enabledEvasions?: Set<string>;
|
|
27
|
-
}) => unknown;
|
|
32
|
+
type StealthPluginFactory = (options?: { enabledEvasions?: Set<string> }) => unknown;
|
|
28
33
|
|
|
29
34
|
type PoolAcquireResponse = {
|
|
30
35
|
browserContextId?: string;
|
|
@@ -62,8 +67,147 @@ type CdpFrameTreeNode = {
|
|
|
62
67
|
};
|
|
63
68
|
};
|
|
64
69
|
|
|
70
|
+
type CdpFetchFulfillParams = {
|
|
71
|
+
readonly requestId: string;
|
|
72
|
+
readonly responseCode: number;
|
|
73
|
+
readonly responseHeaders?: readonly {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly value: string;
|
|
76
|
+
}[];
|
|
77
|
+
readonly body?: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
65
80
|
type BrowserPageContract = BrowserPage;
|
|
66
81
|
|
|
82
|
+
function toResourceBody(body: BrowserResourceBody | undefined): Buffer | string | undefined {
|
|
83
|
+
if (body === undefined || typeof body === "string" || Buffer.isBuffer(body)) {
|
|
84
|
+
return body;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (body instanceof ArrayBuffer) {
|
|
88
|
+
return Buffer.from(new Uint8Array(body));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Buffer.from(body);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isResourceMethod(method: string): method is BrowserResourceMethod {
|
|
95
|
+
return method === "GET" || method === "HEAD";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function toResourceRequest(request: Request): Promise<BrowserResourceRequest | null> {
|
|
99
|
+
const method = request.method().toUpperCase();
|
|
100
|
+
if (!isResourceMethod(method)) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
headers: await request.allHeaders(),
|
|
106
|
+
method,
|
|
107
|
+
resourceType: request.resourceType(),
|
|
108
|
+
url: request.url(),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toCdpResourceRequest(
|
|
113
|
+
params: unknown,
|
|
114
|
+
): { requestId: string; request: BrowserResourceRequest } | null {
|
|
115
|
+
if (!isRecord(params)) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const requestId = params.requestId;
|
|
120
|
+
const rawRequest = params.request;
|
|
121
|
+
if (typeof requestId !== "string" || !isRecord(rawRequest)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const url = rawRequest.url;
|
|
126
|
+
const method = String(rawRequest.method ?? "").toUpperCase();
|
|
127
|
+
if (typeof url !== "string" || !isResourceMethod(method)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
requestId,
|
|
133
|
+
request: {
|
|
134
|
+
headers: toCdpResourceHeaders(rawRequest.headers),
|
|
135
|
+
method,
|
|
136
|
+
resourceType: typeof params.resourceType === "string" ? params.resourceType : undefined,
|
|
137
|
+
url,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getCdpPausedRequestId(params: unknown): string | null {
|
|
143
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return params.requestId;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function toCdpResourceHeaders(value: unknown): Record<string, string> {
|
|
151
|
+
if (!isRecord(value)) {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const headers: Record<string, string> = {};
|
|
156
|
+
for (const [name, headerValue] of Object.entries(value)) {
|
|
157
|
+
if (typeof headerValue === "string") {
|
|
158
|
+
headers[name] = headerValue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return headers;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function matchesResourceRoute(
|
|
166
|
+
match: BrowserResourcePolicy["routes"][number]["match"],
|
|
167
|
+
request: BrowserResourceRequest,
|
|
168
|
+
): boolean {
|
|
169
|
+
if (typeof match === "string") {
|
|
170
|
+
return request.url === match;
|
|
171
|
+
}
|
|
172
|
+
if (match instanceof RegExp) {
|
|
173
|
+
return match.test(request.url);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return match(request);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function toCdpFulfillParams(
|
|
180
|
+
requestId: string,
|
|
181
|
+
decision: Extract<BrowserResourceDecision, { readonly action: "fulfill" }>,
|
|
182
|
+
): CdpFetchFulfillParams {
|
|
183
|
+
const body = toResourceBody(decision.body);
|
|
184
|
+
return {
|
|
185
|
+
...(body === undefined ? {} : { body: Buffer.from(body).toString("base64") }),
|
|
186
|
+
...(decision.headers === undefined
|
|
187
|
+
? {}
|
|
188
|
+
: {
|
|
189
|
+
responseHeaders: Object.entries(decision.headers).map(([name, value]) => ({
|
|
190
|
+
name,
|
|
191
|
+
value,
|
|
192
|
+
})),
|
|
193
|
+
}),
|
|
194
|
+
requestId,
|
|
195
|
+
responseCode: decision.status ?? 200,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function fulfillResourceRoute(
|
|
200
|
+
route: Route,
|
|
201
|
+
decision: Extract<BrowserResourceDecision, { readonly action: "fulfill" }>,
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const body = toResourceBody(decision.body);
|
|
204
|
+
await route.fulfill({
|
|
205
|
+
...(body === undefined ? {} : { body }),
|
|
206
|
+
...(decision.headers === undefined ? {} : { headers: decision.headers }),
|
|
207
|
+
status: decision.status ?? 200,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
67
211
|
export type BrowserClientOptions = BrowserOptions & {
|
|
68
212
|
allowedHosts?: string[];
|
|
69
213
|
cdpUrl?: string;
|
|
@@ -76,18 +220,14 @@ type SupportedBrowserClient = {
|
|
|
76
220
|
close(): Promise<void>;
|
|
77
221
|
newPage(): Promise<BrowserPageContract>;
|
|
78
222
|
rawPage(): Promise<BrowserPageContract>;
|
|
79
|
-
withIsolatedContext<T>(
|
|
80
|
-
handler: (page: BrowserPageContract) => Promise<T>,
|
|
81
|
-
): Promise<T>;
|
|
223
|
+
withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T>;
|
|
82
224
|
};
|
|
83
225
|
|
|
84
226
|
function getDefaultCdpPoolUrl(env = process.env): string | undefined {
|
|
85
227
|
return env.APIFUSE__CDP_POOL__URL;
|
|
86
228
|
}
|
|
87
229
|
|
|
88
|
-
async function importOptionalModule<T extends object>(
|
|
89
|
-
moduleName: string,
|
|
90
|
-
): Promise<T> {
|
|
230
|
+
async function importOptionalModule<T extends object>(moduleName: string): Promise<T> {
|
|
91
231
|
return (await import(moduleName)) as T;
|
|
92
232
|
}
|
|
93
233
|
|
|
@@ -217,9 +357,7 @@ async function loadPlaywright(): Promise<PlaywrightModule> {
|
|
|
217
357
|
}
|
|
218
358
|
|
|
219
359
|
try {
|
|
220
|
-
return unwrapModuleDefault(
|
|
221
|
-
await importOptionalModule<PlaywrightModule>("playwright"),
|
|
222
|
-
);
|
|
360
|
+
return unwrapModuleDefault(await importOptionalModule<PlaywrightModule>("playwright"));
|
|
223
361
|
} catch (error) {
|
|
224
362
|
if (isModuleNotFoundError(error)) {
|
|
225
363
|
throw new ProviderError("Playwright is not installed", {
|
|
@@ -267,19 +405,14 @@ async function loadPlaywrightExtra(): Promise<PlaywrightExtraModule> {
|
|
|
267
405
|
async function loadStealthPluginFactory(): Promise<StealthPluginFactory> {
|
|
268
406
|
try {
|
|
269
407
|
return unwrapModuleDefault(
|
|
270
|
-
await importOptionalModule<StealthPluginFactory>(
|
|
271
|
-
"puppeteer-extra-plugin-stealth",
|
|
272
|
-
),
|
|
408
|
+
await importOptionalModule<StealthPluginFactory>("puppeteer-extra-plugin-stealth"),
|
|
273
409
|
);
|
|
274
410
|
} catch (error) {
|
|
275
411
|
if (isModuleNotFoundError(error)) {
|
|
276
|
-
throw new ProviderError(
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
fix: "Run: bun add playwright-extra puppeteer-extra-plugin-stealth",
|
|
281
|
-
},
|
|
282
|
-
);
|
|
412
|
+
throw new ProviderError("puppeteer-extra-plugin-stealth is not installed", {
|
|
413
|
+
cause: error instanceof Error ? error : undefined,
|
|
414
|
+
fix: "Run: bun add playwright-extra puppeteer-extra-plugin-stealth",
|
|
415
|
+
});
|
|
283
416
|
}
|
|
284
417
|
|
|
285
418
|
throw error;
|
|
@@ -351,10 +484,7 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
351
484
|
return await this.page.evaluate(fn);
|
|
352
485
|
}
|
|
353
486
|
|
|
354
|
-
async waitForSelector(
|
|
355
|
-
selector: string,
|
|
356
|
-
options?: { timeout?: number },
|
|
357
|
-
): Promise<void> {
|
|
487
|
+
async waitForSelector(selector: string, options?: { timeout?: number }): Promise<void> {
|
|
358
488
|
await this.page.waitForSelector(selector, options);
|
|
359
489
|
}
|
|
360
490
|
|
|
@@ -398,6 +528,42 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
398
528
|
async close(): Promise<void> {
|
|
399
529
|
await this.page.close();
|
|
400
530
|
}
|
|
531
|
+
|
|
532
|
+
async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
|
|
533
|
+
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
534
|
+
const handler = async (route: Route): Promise<void> => {
|
|
535
|
+
const request = await toResourceRequest(route.request());
|
|
536
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
537
|
+
await route.abort("blockedbyclient");
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
for (const resourceRoute of policy.routes) {
|
|
542
|
+
if (!matchesResourceRoute(resourceRoute.match, request)) {
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const decision = await resourceRoute.handle(request);
|
|
547
|
+
switch (decision.action) {
|
|
548
|
+
case "fulfill":
|
|
549
|
+
await fulfillResourceRoute(route, decision);
|
|
550
|
+
return;
|
|
551
|
+
case "block":
|
|
552
|
+
await route.abort("blockedbyclient");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await route.abort("blockedbyclient");
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
561
|
+
try {
|
|
562
|
+
return await run();
|
|
563
|
+
} finally {
|
|
564
|
+
await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
401
567
|
}
|
|
402
568
|
|
|
403
569
|
class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
@@ -430,9 +596,7 @@ class PlaywrightBrowserClient implements SupportedBrowserClient {
|
|
|
430
596
|
});
|
|
431
597
|
}
|
|
432
598
|
|
|
433
|
-
async withIsolatedContext<T>(
|
|
434
|
-
handler: (page: BrowserPageContract) => Promise<T>,
|
|
435
|
-
): Promise<T> {
|
|
599
|
+
async withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T> {
|
|
436
600
|
const browser = await this.ensureBrowser();
|
|
437
601
|
const context = await browser.newContext();
|
|
438
602
|
const page = await context.newPage();
|
|
@@ -480,10 +644,7 @@ function normalizeWebSocketEndpoint(endpoint: string): string {
|
|
|
480
644
|
class JsonRpcWebSocketClient {
|
|
481
645
|
private nextId = 1;
|
|
482
646
|
private readonly endpoint: string;
|
|
483
|
-
private readonly listeners = new Map<
|
|
484
|
-
string,
|
|
485
|
-
Set<(params: unknown) => void>
|
|
486
|
-
>();
|
|
647
|
+
private readonly listeners = new Map<string, Set<(params: unknown) => void>>();
|
|
487
648
|
private readonly pending = new Map<
|
|
488
649
|
JsonRpcId,
|
|
489
650
|
{
|
|
@@ -577,9 +738,7 @@ class JsonRpcWebSocketClient {
|
|
|
577
738
|
this.pending.delete(payload.id);
|
|
578
739
|
|
|
579
740
|
if (payload.error) {
|
|
580
|
-
pending.reject(
|
|
581
|
-
new Error(payload.error.message ?? "JSON-RPC command failed"),
|
|
582
|
-
);
|
|
741
|
+
pending.reject(new Error(payload.error.message ?? "JSON-RPC command failed"));
|
|
583
742
|
return;
|
|
584
743
|
}
|
|
585
744
|
|
|
@@ -607,11 +766,7 @@ class JsonRpcWebSocketClient {
|
|
|
607
766
|
});
|
|
608
767
|
|
|
609
768
|
socket.addEventListener("error", () => {
|
|
610
|
-
reject(
|
|
611
|
-
new Error(
|
|
612
|
-
`Unable to connect to WebSocket endpoint: ${this.endpoint}`,
|
|
613
|
-
),
|
|
614
|
-
);
|
|
769
|
+
reject(new Error(`Unable to connect to WebSocket endpoint: ${this.endpoint}`));
|
|
615
770
|
});
|
|
616
771
|
});
|
|
617
772
|
|
|
@@ -650,19 +805,14 @@ function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
|
|
|
650
805
|
});
|
|
651
806
|
}
|
|
652
807
|
|
|
653
|
-
if (
|
|
654
|
-
value.browserContextId !== undefined &&
|
|
655
|
-
typeof value.browserContextId !== "string"
|
|
656
|
-
) {
|
|
808
|
+
if (value.browserContextId !== undefined && typeof value.browserContextId !== "string") {
|
|
657
809
|
throw new ProviderError("CDP Pool returned an invalid acquire response", {
|
|
658
810
|
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
659
811
|
});
|
|
660
812
|
}
|
|
661
813
|
|
|
662
814
|
return {
|
|
663
|
-
...(value.browserContextId
|
|
664
|
-
? { browserContextId: value.browserContextId }
|
|
665
|
-
: {}),
|
|
815
|
+
...(value.browserContextId ? { browserContextId: value.browserContextId } : {}),
|
|
666
816
|
pageId: value.pageId,
|
|
667
817
|
wsEndpoint: value.wsEndpoint,
|
|
668
818
|
};
|
|
@@ -688,10 +838,7 @@ function parseCdpFrameTreeNode(value: unknown): CdpFrameTreeNode | undefined {
|
|
|
688
838
|
frame: {
|
|
689
839
|
id: frameId,
|
|
690
840
|
name: typeof value.frame.name === "string" ? value.frame.name : undefined,
|
|
691
|
-
parentId:
|
|
692
|
-
typeof value.frame.parentId === "string"
|
|
693
|
-
? value.frame.parentId
|
|
694
|
-
: undefined,
|
|
841
|
+
parentId: typeof value.frame.parentId === "string" ? value.frame.parentId : undefined,
|
|
695
842
|
url: typeof value.frame.url === "string" ? value.frame.url : undefined,
|
|
696
843
|
},
|
|
697
844
|
...(childFrames ? { childFrames } : {}),
|
|
@@ -709,10 +856,7 @@ function getCdpExecutionContext(params: unknown): {
|
|
|
709
856
|
const contextId = params.context.id;
|
|
710
857
|
const auxData = params.context.auxData;
|
|
711
858
|
return {
|
|
712
|
-
frameId:
|
|
713
|
-
isRecord(auxData) && typeof auxData.frameId === "string"
|
|
714
|
-
? auxData.frameId
|
|
715
|
-
: undefined,
|
|
859
|
+
frameId: isRecord(auxData) && typeof auxData.frameId === "string" ? auxData.frameId : undefined,
|
|
716
860
|
id: typeof contextId === "number" ? contextId : undefined,
|
|
717
861
|
};
|
|
718
862
|
}
|
|
@@ -721,10 +865,7 @@ class CdpBrowserLocator implements BrowserLocator {
|
|
|
721
865
|
constructor(
|
|
722
866
|
private readonly frame: {
|
|
723
867
|
evaluate<T>(fn: string | (() => T)): Promise<T>;
|
|
724
|
-
waitForSelector?(
|
|
725
|
-
selector: string,
|
|
726
|
-
options?: { timeout?: number },
|
|
727
|
-
): Promise<void>;
|
|
868
|
+
waitForSelector?(selector: string, options?: { timeout?: number }): Promise<void>;
|
|
728
869
|
},
|
|
729
870
|
private readonly selector: string,
|
|
730
871
|
) {}
|
|
@@ -796,9 +937,7 @@ class CdpBrowserFrame implements BrowserFrame {
|
|
|
796
937
|
) {}
|
|
797
938
|
|
|
798
939
|
async url(): Promise<string> {
|
|
799
|
-
const evaluatedUrl = await this.evaluate<string | undefined>(
|
|
800
|
-
"window.location.href",
|
|
801
|
-
);
|
|
940
|
+
const evaluatedUrl = await this.evaluate<string | undefined>("window.location.href");
|
|
802
941
|
return evaluatedUrl || this.initialUrl;
|
|
803
942
|
}
|
|
804
943
|
|
|
@@ -818,10 +957,7 @@ class CdpBrowserFrame implements BrowserFrame {
|
|
|
818
957
|
return new CdpBrowserLocator(this, selector);
|
|
819
958
|
}
|
|
820
959
|
|
|
821
|
-
async waitForSelector(
|
|
822
|
-
selector: string,
|
|
823
|
-
options?: { timeout?: number },
|
|
824
|
-
): Promise<void> {
|
|
960
|
+
async waitForSelector(selector: string, options?: { timeout?: number }): Promise<void> {
|
|
825
961
|
await this.page.waitForSelectorInFrame(this.id, selector, options);
|
|
826
962
|
}
|
|
827
963
|
|
|
@@ -856,10 +992,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
856
992
|
|
|
857
993
|
try {
|
|
858
994
|
await this.pageClient.send("Page.navigate", { url });
|
|
859
|
-
await this.waitForDocumentReady(
|
|
860
|
-
startedAt + DEFAULT_WAIT_TIMEOUT_MS,
|
|
861
|
-
() => loadEventSeen,
|
|
862
|
-
);
|
|
995
|
+
await this.waitForDocumentReady(startedAt + DEFAULT_WAIT_TIMEOUT_MS, () => loadEventSeen);
|
|
863
996
|
} finally {
|
|
864
997
|
unsubscribe();
|
|
865
998
|
}
|
|
@@ -870,10 +1003,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
870
1003
|
return await this.evaluateWithContext<T>(fn);
|
|
871
1004
|
}
|
|
872
1005
|
|
|
873
|
-
async evaluateInFrame<T>(
|
|
874
|
-
frameId: string,
|
|
875
|
-
fn: string | (() => T),
|
|
876
|
-
): Promise<T> {
|
|
1006
|
+
async evaluateInFrame<T>(frameId: string, fn: string | (() => T)): Promise<T> {
|
|
877
1007
|
await this.initialize();
|
|
878
1008
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
879
1009
|
return await this.evaluateWithContext<T>(fn, contextId);
|
|
@@ -903,10 +1033,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
903
1033
|
throw new Error(`Timed out waiting for selector: ${selector}`);
|
|
904
1034
|
}
|
|
905
1035
|
|
|
906
|
-
private async evaluateWithContext<T>(
|
|
907
|
-
fn: string | (() => T),
|
|
908
|
-
contextId?: number,
|
|
909
|
-
): Promise<T> {
|
|
1036
|
+
private async evaluateWithContext<T>(fn: string | (() => T), contextId?: number): Promise<T> {
|
|
910
1037
|
const result = await this.pageClient.send("Runtime.evaluate", {
|
|
911
1038
|
awaitPromise: true,
|
|
912
1039
|
...(contextId === undefined ? {} : { contextId }),
|
|
@@ -916,20 +1043,14 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
916
1043
|
|
|
917
1044
|
if (result.exceptionDetails) {
|
|
918
1045
|
throw new Error(
|
|
919
|
-
String(
|
|
920
|
-
(result.exceptionDetails as { text?: string }).text ??
|
|
921
|
-
"Browser evaluation failed",
|
|
922
|
-
),
|
|
1046
|
+
String((result.exceptionDetails as { text?: string }).text ?? "Browser evaluation failed"),
|
|
923
1047
|
);
|
|
924
1048
|
}
|
|
925
1049
|
|
|
926
1050
|
return (result.result as { value?: T } | undefined)?.value as T;
|
|
927
1051
|
}
|
|
928
1052
|
|
|
929
|
-
async waitForSelector(
|
|
930
|
-
selector: string,
|
|
931
|
-
options?: { timeout?: number },
|
|
932
|
-
): Promise<void> {
|
|
1053
|
+
async waitForSelector(selector: string, options?: { timeout?: number }): Promise<void> {
|
|
933
1054
|
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
934
1055
|
const deadline = Date.now() + timeout;
|
|
935
1056
|
|
|
@@ -995,14 +1116,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
995
1116
|
const result = await this.pageClient.send("Page.getFrameTree");
|
|
996
1117
|
const frames = flattenCdpFrameTree(parseCdpFrameTreeNode(result.frameTree));
|
|
997
1118
|
return frames.map(
|
|
998
|
-
(frame) =>
|
|
999
|
-
new CdpBrowserFrame(
|
|
1000
|
-
frame.id,
|
|
1001
|
-
this,
|
|
1002
|
-
frame.url ?? "",
|
|
1003
|
-
frame.name,
|
|
1004
|
-
frame.parentId,
|
|
1005
|
-
),
|
|
1119
|
+
(frame) => new CdpBrowserFrame(frame.id, this, frame.url ?? "", frame.name, frame.parentId),
|
|
1006
1120
|
);
|
|
1007
1121
|
}
|
|
1008
1122
|
|
|
@@ -1016,8 +1130,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1016
1130
|
|
|
1017
1131
|
async url(): Promise<string> {
|
|
1018
1132
|
const [mainFrame] = await this.frames();
|
|
1019
|
-
const frameUrl =
|
|
1020
|
-
mainFrame instanceof CdpBrowserFrame ? mainFrame.fallbackUrl() : "";
|
|
1133
|
+
const frameUrl = mainFrame instanceof CdpBrowserFrame ? mainFrame.fallbackUrl() : "";
|
|
1021
1134
|
return frameUrl || (await this.evaluate<string>("window.location.href"));
|
|
1022
1135
|
}
|
|
1023
1136
|
|
|
@@ -1045,9 +1158,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1045
1158
|
|
|
1046
1159
|
try {
|
|
1047
1160
|
await this.release({
|
|
1048
|
-
...(this.browserContextId
|
|
1049
|
-
? { browserContextId: this.browserContextId }
|
|
1050
|
-
: {}),
|
|
1161
|
+
...(this.browserContextId ? { browserContextId: this.browserContextId } : {}),
|
|
1051
1162
|
pageId: this.pageId,
|
|
1052
1163
|
});
|
|
1053
1164
|
} finally {
|
|
@@ -1055,6 +1166,93 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1055
1166
|
}
|
|
1056
1167
|
}
|
|
1057
1168
|
|
|
1169
|
+
async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
|
|
1170
|
+
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
1171
|
+
const handlePausedRequest = (params: unknown): void => {
|
|
1172
|
+
void this.handleResourcePolicyPausedRequest(params, policy, allowedMethods);
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
|
|
1176
|
+
|
|
1177
|
+
try {
|
|
1178
|
+
await this.pageClient.send("Fetch.enable", {
|
|
1179
|
+
patterns: [{ requestStage: "Request", urlPattern: "*" }],
|
|
1180
|
+
});
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
unsubscribe();
|
|
1183
|
+
throw new ProviderError(
|
|
1184
|
+
"CDP browser target does not support BrowserPage.withResourcePolicy()",
|
|
1185
|
+
{
|
|
1186
|
+
cause: error instanceof Error ? error : undefined,
|
|
1187
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1188
|
+
fix: "Use a Chromium CDP target with the Fetch domain enabled, or use the local Playwright browser runtime.",
|
|
1189
|
+
},
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
try {
|
|
1194
|
+
return await run();
|
|
1195
|
+
} finally {
|
|
1196
|
+
unsubscribe();
|
|
1197
|
+
await this.pageClient.send("Fetch.disable");
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
private async handleResourcePolicyPausedRequest(
|
|
1202
|
+
params: unknown,
|
|
1203
|
+
policy: BrowserResourcePolicy,
|
|
1204
|
+
allowedMethods: ReadonlySet<BrowserResourceMethod>,
|
|
1205
|
+
): Promise<void> {
|
|
1206
|
+
const requestId = getCdpPausedRequestId(params);
|
|
1207
|
+
if (requestId === null) {
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
try {
|
|
1212
|
+
const parsed = toCdpResourceRequest(params);
|
|
1213
|
+
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
1214
|
+
await this.failCdpResourceRequest(requestId);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
for (const resourceRoute of policy.routes) {
|
|
1219
|
+
if (!matchesResourceRoute(resourceRoute.match, parsed.request)) {
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const decision = await resourceRoute.handle(parsed.request);
|
|
1224
|
+
switch (decision.action) {
|
|
1225
|
+
case "fulfill":
|
|
1226
|
+
await this.pageClient.send(
|
|
1227
|
+
"Fetch.fulfillRequest",
|
|
1228
|
+
toCdpFulfillParams(parsed.requestId, decision),
|
|
1229
|
+
);
|
|
1230
|
+
return;
|
|
1231
|
+
case "block":
|
|
1232
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
await this.failCdpResourceRequest(parsed.requestId);
|
|
1238
|
+
} catch {
|
|
1239
|
+
await this.failCdpResourceRequest(requestId);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
private async failCdpResourceRequest(requestId: string): Promise<void> {
|
|
1244
|
+
try {
|
|
1245
|
+
await this.pageClient.send("Fetch.failRequest", {
|
|
1246
|
+
errorReason: "BlockedByClient",
|
|
1247
|
+
requestId,
|
|
1248
|
+
});
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (error instanceof Error) {
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1058
1256
|
private async initialize(): Promise<void> {
|
|
1059
1257
|
if (this.initialized) {
|
|
1060
1258
|
return;
|
|
@@ -1084,9 +1282,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1084
1282
|
});
|
|
1085
1283
|
const contextId = result.executionContextId;
|
|
1086
1284
|
if (typeof contextId !== "number") {
|
|
1087
|
-
throw new Error(
|
|
1088
|
-
`Unable to resolve execution context for frame: ${frameId}`,
|
|
1089
|
-
);
|
|
1285
|
+
throw new Error(`Unable to resolve execution context for frame: ${frameId}`);
|
|
1090
1286
|
}
|
|
1091
1287
|
|
|
1092
1288
|
this.frameExecutionContexts.set(frameId, contextId);
|
|
@@ -1132,17 +1328,11 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
|
|
|
1132
1328
|
return await this.acquirePage({ isolatedContext: true });
|
|
1133
1329
|
}
|
|
1134
1330
|
|
|
1135
|
-
private async acquirePage(options?: {
|
|
1136
|
-
isolatedContext?: boolean;
|
|
1137
|
-
}): Promise<BrowserPageContract> {
|
|
1331
|
+
private async acquirePage(options?: { isolatedContext?: boolean }): Promise<BrowserPageContract> {
|
|
1138
1332
|
const acquireResult = parsePoolAcquireResponse(
|
|
1139
1333
|
await this.poolClient.send("acquire", {
|
|
1140
|
-
...(this.allowedHosts.length > 0
|
|
1141
|
-
|
|
1142
|
-
: {}),
|
|
1143
|
-
...(options?.isolatedContext
|
|
1144
|
-
? { isolationMode: "browserContext" }
|
|
1145
|
-
: {}),
|
|
1334
|
+
...(this.allowedHosts.length > 0 ? { allowedHosts: this.allowedHosts } : {}),
|
|
1335
|
+
...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
|
|
1146
1336
|
}),
|
|
1147
1337
|
);
|
|
1148
1338
|
const pageClient = new JsonRpcWebSocketClient(acquireResult.wsEndpoint);
|
|
@@ -1171,9 +1361,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
|
|
|
1171
1361
|
return await this.newPage();
|
|
1172
1362
|
}
|
|
1173
1363
|
|
|
1174
|
-
async withIsolatedContext<T>(
|
|
1175
|
-
handler: (page: BrowserPageContract) => Promise<T>,
|
|
1176
|
-
): Promise<T> {
|
|
1364
|
+
async withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T> {
|
|
1177
1365
|
const page = await this.acquirePage({ isolatedContext: true });
|
|
1178
1366
|
|
|
1179
1367
|
try {
|
|
@@ -1189,9 +1377,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
|
|
|
1189
1377
|
}
|
|
1190
1378
|
|
|
1191
1379
|
class UnsupportedBrowserEngineClient implements SupportedBrowserClient {
|
|
1192
|
-
constructor(
|
|
1193
|
-
readonly engine: Extract<BrowserEngine, "nodriver" | "selenium-uc">,
|
|
1194
|
-
) {}
|
|
1380
|
+
constructor(readonly engine: Extract<BrowserEngine, "nodriver" | "selenium-uc">) {}
|
|
1195
1381
|
|
|
1196
1382
|
async newPage(): Promise<never> {
|
|
1197
1383
|
if (this.engine === "nodriver") {
|
|
@@ -1218,15 +1404,11 @@ class UnsupportedBrowserEngineClient implements SupportedBrowserClient {
|
|
|
1218
1404
|
async close(): Promise<void> {}
|
|
1219
1405
|
}
|
|
1220
1406
|
|
|
1221
|
-
function createPlaywrightStealthClient(
|
|
1222
|
-
options: BrowserClientOptions = {},
|
|
1223
|
-
): SupportedBrowserClient {
|
|
1407
|
+
function createPlaywrightStealthClient(options: BrowserClientOptions = {}): SupportedBrowserClient {
|
|
1224
1408
|
return new PlaywrightBrowserClient(options);
|
|
1225
1409
|
}
|
|
1226
1410
|
|
|
1227
|
-
function createCdpPoolBrowserClient(
|
|
1228
|
-
options: BrowserClientOptions,
|
|
1229
|
-
): SupportedBrowserClient {
|
|
1411
|
+
function createCdpPoolBrowserClient(options: BrowserClientOptions): SupportedBrowserClient {
|
|
1230
1412
|
return new CdpPoolBrowserClient(options);
|
|
1231
1413
|
}
|
|
1232
1414
|
|
|
@@ -1255,13 +1437,10 @@ export class BrowserClient implements BrowserClientContract {
|
|
|
1255
1437
|
this.cdpUrl = resolvedOptions.cdpUrl;
|
|
1256
1438
|
|
|
1257
1439
|
if (resolvedOptions.requireCdpPool && !resolvedOptions.cdpUrl) {
|
|
1258
|
-
throw new ProviderError(
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
fix: "Set APIFUSE__CDP_POOL__URL for deployed browser providers. Local standalone development may omit it.",
|
|
1263
|
-
},
|
|
1264
|
-
);
|
|
1440
|
+
throw new ProviderError("Managed CDP Pool is required for browser providers in production", {
|
|
1441
|
+
code: "BROWSER_CDP_POOL_REQUIRED",
|
|
1442
|
+
fix: "Set APIFUSE__CDP_POOL__URL for deployed browser providers. Local standalone development may omit it.",
|
|
1443
|
+
});
|
|
1265
1444
|
}
|
|
1266
1445
|
|
|
1267
1446
|
switch (engine) {
|
|
@@ -1299,9 +1478,7 @@ export class BrowserClient implements BrowserClientContract {
|
|
|
1299
1478
|
return this.activatePage(page);
|
|
1300
1479
|
}
|
|
1301
1480
|
|
|
1302
|
-
async withIsolatedContext<T>(
|
|
1303
|
-
handler: (page: BrowserPageContract) => Promise<T>,
|
|
1304
|
-
): Promise<T> {
|
|
1481
|
+
async withIsolatedContext<T>(handler: (page: BrowserPageContract) => Promise<T>): Promise<T> {
|
|
1305
1482
|
const previousActivePage = this.activePage;
|
|
1306
1483
|
let trackedPage: BrowserPageContract | undefined;
|
|
1307
1484
|
|
|
@@ -1346,16 +1523,11 @@ export class BrowserClient implements BrowserClientContract {
|
|
|
1346
1523
|
return trackedPage;
|
|
1347
1524
|
}
|
|
1348
1525
|
|
|
1349
|
-
async solveChallenge(
|
|
1350
|
-
request: BrowserChallengeRequest,
|
|
1351
|
-
): Promise<BrowserChallengeResult> {
|
|
1526
|
+
async solveChallenge(request: BrowserChallengeRequest): Promise<BrowserChallengeResult> {
|
|
1352
1527
|
if (request.type !== "recaptcha") {
|
|
1353
|
-
throw new ProviderError(
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1357
|
-
},
|
|
1358
|
-
);
|
|
1528
|
+
throw new ProviderError(`Unsupported browser challenge: ${request.type}`, {
|
|
1529
|
+
code: "BROWSER_RUNTIME_UNSUPPORTED",
|
|
1530
|
+
});
|
|
1359
1531
|
}
|
|
1360
1532
|
|
|
1361
1533
|
if (this.activePage) {
|
|
@@ -1417,8 +1589,7 @@ async function findRecaptchaFrame(
|
|
|
1417
1589
|
for (const frame of frames) {
|
|
1418
1590
|
const url = await frame.url();
|
|
1419
1591
|
const matchesRecaptcha =
|
|
1420
|
-
url.includes("google.com/recaptcha") ||
|
|
1421
|
-
url.includes("recaptcha.net/recaptcha");
|
|
1592
|
+
url.includes("google.com/recaptcha") || url.includes("recaptcha.net/recaptcha");
|
|
1422
1593
|
const matchesSiteKey = !siteKey || url.includes(siteKey);
|
|
1423
1594
|
if (matchesRecaptcha && matchesSiteKey) {
|
|
1424
1595
|
return frame;
|
|
@@ -1428,8 +1599,6 @@ async function findRecaptchaFrame(
|
|
|
1428
1599
|
return undefined;
|
|
1429
1600
|
}
|
|
1430
1601
|
|
|
1431
|
-
export function createBrowserClient(
|
|
1432
|
-
options: BrowserClientOptions = {},
|
|
1433
|
-
): BrowserClient {
|
|
1602
|
+
export function createBrowserClient(options: BrowserClientOptions = {}): BrowserClient {
|
|
1434
1603
|
return new BrowserClient(options);
|
|
1435
1604
|
}
|