@apifuse/provider-sdk 2.2.0-beta.32 → 2.2.0-beta.33
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/CHANGELOG.md +5 -0
- package/dist/error-resolution.js +0 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/provider.d.ts +1 -1
- package/dist/provider.js +1 -1
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +344 -27
- package/dist/runtime/choice.d.ts +0 -1
- package/dist/runtime/choice.js +1 -78
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +68 -16
- package/dist/runtime/resolver-vendors/capsolver.d.ts +1 -3
- package/dist/runtime/resolver-vendors/capsolver.js +148 -24
- package/dist/runtime/resolver-vendors/twocaptcha.js +46 -13
- package/dist/runtime/resolver-vendors/types.d.ts +1 -1
- package/dist/runtime/resolver-vendors/types.js +6 -3
- package/dist/runtime/resolver.d.ts +1 -1
- package/dist/runtime/resolver.js +11 -3
- package/dist/server/serve-implementation.js +1 -2
- package/dist/types.d.ts +9 -1
- package/package.json +1 -1
- package/src/error-resolution.ts +0 -1
- package/src/index.ts +0 -1
- package/src/provider.ts +0 -1
- package/src/runtime/browser.ts +430 -31
- package/src/runtime/choice.ts +1 -91
- package/src/runtime/resolver-vendors/browser.ts +82 -15
- package/src/runtime/resolver-vendors/capsolver.ts +170 -33
- package/src/runtime/resolver-vendors/twocaptcha.ts +43 -8
- package/src/runtime/resolver-vendors/types.ts +6 -3
- package/src/runtime/resolver.ts +13 -5
- package/src/server/serve-implementation.ts +0 -2
- package/src/types.ts +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.33
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit e7d854ec3e859702b22e2b37eaa2ae6bd4545c80.
|
|
6
|
+
|
|
3
7
|
## 2.2.0-beta.32
|
|
4
8
|
|
|
5
9
|
- Release candidate for main commit 82f1c0c1de916a40c8c74b1d6470a493b134c0ed.
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
|
|
47
51
|
## Unreleased
|
|
48
52
|
|
|
53
|
+
- Server-stored provider choices now issue word-format tokens unconditionally. The runtime issuance setting and legacy issuance path were removed; parsing remains dual-read until the legacy-token TTL horizon closes.
|
|
49
54
|
- Added the `thrown-error-code-undeclared` authoring lint (warning level): `apifuse check` now statically flags literal `ProviderError`/`ValidationError` codes that are neither SDK-registered nor declared in any operation's `docs.errorCodes`, surfacing the runtime `unregistered_provider_error_code` signal at check time. The canonical SDK code→status mapping moved to `SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES` in `error-resolution.ts`, shared by the runtime status resolver and the lint.
|
|
50
55
|
|
|
51
56
|
## 2.2.0-beta.21
|
package/dist/error-resolution.js
CHANGED
|
@@ -18,7 +18,6 @@ export const SDK_OWNED_PROVIDER_ERROR_CODES = new Set([
|
|
|
18
18
|
"RUNTIME_UNSUPPORTED",
|
|
19
19
|
"PROVIDER_STATE_UNSUPPORTED",
|
|
20
20
|
"CHOICE_TOKEN_MASTER_SECRET_NOT_CONFIGURED",
|
|
21
|
-
"CHOICE_WORD_ISSUANCE_INVALID",
|
|
22
21
|
"CHOICE_STATE_PAYLOAD_TOO_LARGE",
|
|
23
22
|
"CHOICE_STATE_UNAVAILABLE",
|
|
24
23
|
"CHOICE_CONTEXT_REQUIRED",
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export * from "./recipes/rest-api.js";
|
|
|
17
17
|
export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
|
|
18
18
|
export type { BrowserClientOptions } from "./runtime/browser.js";
|
|
19
19
|
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, type ProviderCacheOptions, } from "./runtime/cache.js";
|
|
20
|
-
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
20
|
+
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
21
21
|
export { type CreateCredentialContextOptions, createCredentialContext, } from "./runtime/credential.js";
|
|
22
22
|
export { createEnvContext } from "./runtime/env.js";
|
|
23
23
|
export { executeOperation } from "./runtime/executor.js";
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export * from "./recipes/gov-api.js";
|
|
|
15
15
|
export * from "./recipes/rest-api.js";
|
|
16
16
|
export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
|
|
17
17
|
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, } from "./runtime/cache.js";
|
|
18
|
-
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
18
|
+
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
19
19
|
export { createCredentialContext, } from "./runtime/credential.js";
|
|
20
20
|
export { createEnvContext } from "./runtime/env.js";
|
|
21
21
|
export { executeOperation } from "./runtime/executor.js";
|
package/dist/provider.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderC
|
|
|
5
5
|
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define.js";
|
|
6
6
|
export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, isTransportError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors.js";
|
|
7
7
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
|
|
8
|
-
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
8
|
+
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
9
9
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, type SensitiveFieldKind, type SensitiveFieldOptions, type SensitivePath, sensitive, z, } from "./schema.js";
|
|
10
10
|
export type { AuthAbortData, AuthAbortRetry, AuthFlowTerminalContext, AuthMode, AuthSafeData, AuthSafeJson, FlowContext, HealthCheckAssertionContext, HealthCheckCase, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthScheduleRandomization, HttpRedirectFailureReason, HttpRedirectPolicy, HttpRedirectPolicyMode, HttpRetryOptions, HttpRetrySummary, InferSchemaOutput, NativeContext, NativeNetworkClient, NativeNetworkCloseReason, NativeNetworkConnection, NativeNetworkConnectInput, NativeNetworkConnectOptions, NativeNetworkDynamicGrantOptions, NativeNetworkEgressGrant, NativeProviderConfig, NativeProviderContext, NativeProxyDrainHandler, NativeProxyEgressInfo, NativeProxyExpiringEvent, NativeProxyExpiringReason, NativeTcpDynamicEgressRule, NativeTcpEgressGrant, NativeTcpEgressRule, NativeTcpPortRange, NativeTcpTlsMode, NativeTlsConnectOptions, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDocMeta, OperationErrorCode, ProviderErrorStatus, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, ProviderAccessVisibility, ProviderChoiceBindingOptions, ProviderChoiceConsumeMode, ProviderChoiceConsumeResult, ProviderChoiceContext, ProviderChoiceExplicitParseResult, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderFileRef, ProviderFilesContext, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderProxyPolicy, ProxiedOAuthConfig, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderResolvedFile, ProviderRuntimeState, ProviderStateDurationString, ProviderStateNamespace, ProviderSupportLevel, RedirectRunReason, SchemaLike, SmsOtpMatcherDefinition, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateNamespaceScope, StateValue, StateWriteOptions, } from "./types.js";
|
|
11
11
|
export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
|
package/dist/provider.js
CHANGED
|
@@ -4,7 +4,7 @@ export { assertFreshProviderChoiceIssuedAt, createProviderChoiceToken, ProviderC
|
|
|
4
4
|
export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, every, } from "./define.js";
|
|
5
5
|
export { AuthError, HttpRedirectError, isProviderError, isSessionExpiredError, isTransportError, ProviderError, SessionExpiredError, TransportError, ValidationError, } from "./errors.js";
|
|
6
6
|
export { getProviderLocalePath, providerLocaleKey, qualifyProviderLocaleKey, } from "./i18n/index.js";
|
|
7
|
-
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
7
|
+
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
8
8
|
export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, sensitive, z, } from "./schema.js";
|
|
9
9
|
export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
|
|
10
10
|
export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
|
package/dist/runtime/browser.js
CHANGED
|
@@ -15,7 +15,7 @@ function toResourceBody(body) {
|
|
|
15
15
|
return Buffer.from(body);
|
|
16
16
|
}
|
|
17
17
|
function isResourceMethod(method) {
|
|
18
|
-
return method === "GET" || method === "HEAD";
|
|
18
|
+
return method === "GET" || method === "HEAD" || method === "POST";
|
|
19
19
|
}
|
|
20
20
|
async function toResourceRequest(request) {
|
|
21
21
|
const method = request.method().toUpperCase();
|
|
@@ -59,6 +59,68 @@ function getCdpPausedRequestId(params) {
|
|
|
59
59
|
}
|
|
60
60
|
return params.requestId;
|
|
61
61
|
}
|
|
62
|
+
function isCdpResponseStage(params) {
|
|
63
|
+
return (isRecord(params) &&
|
|
64
|
+
(typeof params.responseStatusCode === "number" ||
|
|
65
|
+
typeof params.responseErrorReason === "string"));
|
|
66
|
+
}
|
|
67
|
+
function toCdpPausedDocumentResponse(params) {
|
|
68
|
+
if (!isRecord(params) ||
|
|
69
|
+
typeof params.requestId !== "string" ||
|
|
70
|
+
typeof params.responseStatusCode !== "number" ||
|
|
71
|
+
params.resourceType !== "Document" ||
|
|
72
|
+
!isRecord(params.request)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const responseCode = params.responseStatusCode;
|
|
76
|
+
const responseHeaders = Array.isArray(params.responseHeaders)
|
|
77
|
+
? params.responseHeaders.flatMap((header) => {
|
|
78
|
+
if (!isRecord(header) ||
|
|
79
|
+
typeof header.name !== "string" ||
|
|
80
|
+
typeof header.value !== "string") {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return [{ name: header.name, value: header.value }];
|
|
84
|
+
})
|
|
85
|
+
: [];
|
|
86
|
+
return {
|
|
87
|
+
hasBody: String(params.request.method ?? "").toUpperCase() !== "HEAD" &&
|
|
88
|
+
responseCode >= 200 &&
|
|
89
|
+
(responseCode < 300 || responseCode >= 400) &&
|
|
90
|
+
responseCode !== 204 &&
|
|
91
|
+
responseCode !== 205 &&
|
|
92
|
+
responseCode !== 304,
|
|
93
|
+
requestId: params.requestId,
|
|
94
|
+
responseCode,
|
|
95
|
+
responseHeaders,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function toCdpDocumentFulfillParams(response, body, contentSecurityPolicy) {
|
|
99
|
+
const responseBody = typeof body.body === "string" ? body.body : "";
|
|
100
|
+
return {
|
|
101
|
+
body: body.base64Encoded ? responseBody : Buffer.from(responseBody).toString("base64"),
|
|
102
|
+
requestId: response.requestId,
|
|
103
|
+
responseCode: response.responseCode,
|
|
104
|
+
responseHeaders: [
|
|
105
|
+
...response.responseHeaders,
|
|
106
|
+
{ name: "Content-Security-Policy", value: contentSecurityPolicy },
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function resourcePolicyFetchPatterns(policy) {
|
|
111
|
+
return [
|
|
112
|
+
{ requestStage: "Request", urlPattern: "*" },
|
|
113
|
+
...(policy.documentContentSecurityPolicy
|
|
114
|
+
? [
|
|
115
|
+
{
|
|
116
|
+
requestStage: "Response",
|
|
117
|
+
resourceType: "Document",
|
|
118
|
+
urlPattern: "*",
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
: []),
|
|
122
|
+
];
|
|
123
|
+
}
|
|
62
124
|
function toCdpResourceHeaders(value) {
|
|
63
125
|
if (!isRecord(value)) {
|
|
64
126
|
return {};
|
|
@@ -104,6 +166,128 @@ async function fulfillResourceRoute(route, decision) {
|
|
|
104
166
|
status: decision.status ?? 200,
|
|
105
167
|
});
|
|
106
168
|
}
|
|
169
|
+
async function decideResourceRequest(policy, request) {
|
|
170
|
+
for (const resourceRoute of policy.routes) {
|
|
171
|
+
if (matchesResourceRoute(resourceRoute.match, request)) {
|
|
172
|
+
return await resourceRoute.handle(request);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { action: "block" };
|
|
176
|
+
}
|
|
177
|
+
async function abortResourceRoute(route) {
|
|
178
|
+
await route.abort("blockedbyclient");
|
|
179
|
+
}
|
|
180
|
+
async function handleCdpResourceRequest(session, policy, allowedMethods, params) {
|
|
181
|
+
const paused = toCdpResourceRequest(params);
|
|
182
|
+
const requestId = paused?.requestId ?? getCdpPausedRequestId(params);
|
|
183
|
+
if (!requestId)
|
|
184
|
+
return;
|
|
185
|
+
try {
|
|
186
|
+
if (!paused || !allowedMethods.has(paused.request.method)) {
|
|
187
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const decision = await decideResourceRequest(policy, paused.request);
|
|
191
|
+
switch (decision.action) {
|
|
192
|
+
case "continue":
|
|
193
|
+
await session.send("Fetch.continueRequest", { requestId });
|
|
194
|
+
return;
|
|
195
|
+
case "fulfill":
|
|
196
|
+
await session.send("Fetch.fulfillRequest", toCdpFulfillParams(requestId, decision));
|
|
197
|
+
return;
|
|
198
|
+
case "block":
|
|
199
|
+
await session.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
await session
|
|
205
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
206
|
+
.catch(() => undefined);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function handleCdpDocumentResponse(session, contentSecurityPolicy, params) {
|
|
210
|
+
const requestId = getCdpPausedRequestId(params);
|
|
211
|
+
if (!requestId)
|
|
212
|
+
return;
|
|
213
|
+
try {
|
|
214
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
215
|
+
if (!response)
|
|
216
|
+
throw new Error("CDP document response metadata is unavailable");
|
|
217
|
+
if (!response.hasBody) {
|
|
218
|
+
await session.send("Fetch.continueResponse", {
|
|
219
|
+
requestId,
|
|
220
|
+
responseCode: response.responseCode,
|
|
221
|
+
responseHeaders: [...response.responseHeaders],
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const body = await session.send("Fetch.getResponseBody", {
|
|
226
|
+
requestId: response.requestId,
|
|
227
|
+
});
|
|
228
|
+
await session.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, contentSecurityPolicy));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
await session
|
|
232
|
+
.send("Fetch.failRequest", { errorReason: "BlockedByClient", requestId })
|
|
233
|
+
.catch(() => undefined);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function getCdpAuthRequiredRequestId(params) {
|
|
237
|
+
if (!isRecord(params) || typeof params.requestId !== "string") {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return params.requestId;
|
|
241
|
+
}
|
|
242
|
+
function isCdpProxyAuthChallenge(params) {
|
|
243
|
+
return (isRecord(params) &&
|
|
244
|
+
isRecord(params.authChallenge) &&
|
|
245
|
+
params.authChallenge.source === "Proxy");
|
|
246
|
+
}
|
|
247
|
+
async function handleCdpAuthRequired(session, proxy, authAttempts, params) {
|
|
248
|
+
const requestId = getCdpAuthRequiredRequestId(params);
|
|
249
|
+
if (!requestId)
|
|
250
|
+
return;
|
|
251
|
+
// Chromium can emit another challenge after rejected credentials. Cancel a
|
|
252
|
+
// retry so incorrect credentials fail promptly instead of looping forever.
|
|
253
|
+
const response = isCdpProxyAuthChallenge(params) && !authAttempts.has(requestId)
|
|
254
|
+
? (() => {
|
|
255
|
+
authAttempts.add(requestId);
|
|
256
|
+
return {
|
|
257
|
+
authChallengeResponse: {
|
|
258
|
+
password: proxy.password,
|
|
259
|
+
response: "ProvideCredentials",
|
|
260
|
+
username: proxy.username,
|
|
261
|
+
},
|
|
262
|
+
requestId,
|
|
263
|
+
};
|
|
264
|
+
})()
|
|
265
|
+
: {
|
|
266
|
+
authChallengeResponse: { response: "CancelAuth" },
|
|
267
|
+
requestId,
|
|
268
|
+
};
|
|
269
|
+
await session.send("Fetch.continueWithAuth", response).catch(() => undefined);
|
|
270
|
+
}
|
|
271
|
+
function createResourcePolicyHandler(policy, allowedMethods) {
|
|
272
|
+
return async (route) => {
|
|
273
|
+
const request = await toResourceRequest(route.request());
|
|
274
|
+
if (!request || !allowedMethods.has(request.method)) {
|
|
275
|
+
await abortResourceRoute(route);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const decision = await decideResourceRequest(policy, request);
|
|
279
|
+
switch (decision.action) {
|
|
280
|
+
case "continue":
|
|
281
|
+
await route.continue();
|
|
282
|
+
return;
|
|
283
|
+
case "fulfill":
|
|
284
|
+
await fulfillResourceRoute(route, decision);
|
|
285
|
+
return;
|
|
286
|
+
case "block":
|
|
287
|
+
await abortResourceRoute(route);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
107
291
|
function getDefaultCdpPoolUrl(env = process.env) {
|
|
108
292
|
return env.APIFUSE__CDP_POOL__URL;
|
|
109
293
|
}
|
|
@@ -139,7 +323,7 @@ function formatExpression(fn) {
|
|
|
139
323
|
}
|
|
140
324
|
return `(${fn.toString()})()`;
|
|
141
325
|
}
|
|
142
|
-
function toLaunchOptions(options) {
|
|
326
|
+
function toLaunchOptions(options, proxy) {
|
|
143
327
|
return {
|
|
144
328
|
// `extraArgs` is optional, but playwright-extra's stealth evasions mutate
|
|
145
329
|
// `options.args` unguarded (navigator.webdriver does
|
|
@@ -150,7 +334,49 @@ function toLaunchOptions(options) {
|
|
|
150
334
|
args: options.extraArgs ?? [],
|
|
151
335
|
executablePath: options.executablePath,
|
|
152
336
|
headless: options.headless ?? true,
|
|
153
|
-
proxy
|
|
337
|
+
proxy,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function hasProxyCredentials(proxy) {
|
|
341
|
+
return proxy?.username !== undefined && proxy.password !== undefined;
|
|
342
|
+
}
|
|
343
|
+
function toPlaywrightProxy(proxy) {
|
|
344
|
+
if (!proxy)
|
|
345
|
+
return undefined;
|
|
346
|
+
const schemeEnd = proxy.indexOf("://");
|
|
347
|
+
const authorityStart = schemeEnd >= 0 ? schemeEnd + 3 : 0;
|
|
348
|
+
const remainder = proxy.slice(authorityStart);
|
|
349
|
+
const authorityEnd = remainder.search(/[/?#]/);
|
|
350
|
+
const authority = remainder.slice(0, authorityEnd >= 0 ? authorityEnd : undefined);
|
|
351
|
+
if (!authority.includes("@")) {
|
|
352
|
+
return { server: proxy };
|
|
353
|
+
}
|
|
354
|
+
let parsed;
|
|
355
|
+
try {
|
|
356
|
+
parsed = new URL(proxy);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
throw new ProviderError("Browser proxy URL is invalid", {
|
|
360
|
+
code: "BROWSER_PROXY_INVALID",
|
|
361
|
+
fix: "Use a valid proxy URL.",
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
let username;
|
|
365
|
+
let password;
|
|
366
|
+
try {
|
|
367
|
+
username = decodeURIComponent(parsed.username);
|
|
368
|
+
password = decodeURIComponent(parsed.password);
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
throw new ProviderError("Browser proxy credentials use invalid percent-encoding", {
|
|
372
|
+
code: "BROWSER_PROXY_INVALID",
|
|
373
|
+
fix: "Percent-encode the proxy username and password as URL userinfo.",
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
server: `${parsed.protocol}//${parsed.host}`,
|
|
378
|
+
username,
|
|
379
|
+
password,
|
|
154
380
|
};
|
|
155
381
|
}
|
|
156
382
|
class PlaywrightBrowserLocator {
|
|
@@ -315,10 +541,12 @@ async function loadSeleniumBase() {
|
|
|
315
541
|
}
|
|
316
542
|
class PlaywrightBrowserPage {
|
|
317
543
|
page;
|
|
544
|
+
proxy;
|
|
318
545
|
id = "main";
|
|
319
546
|
pageId;
|
|
320
|
-
constructor(page) {
|
|
547
|
+
constructor(page, proxy = undefined) {
|
|
321
548
|
this.page = page;
|
|
549
|
+
this.proxy = proxy;
|
|
322
550
|
}
|
|
323
551
|
async goto(url) {
|
|
324
552
|
await this.page.goto(url);
|
|
@@ -368,40 +596,102 @@ class PlaywrightBrowserPage {
|
|
|
368
596
|
}
|
|
369
597
|
async withResourcePolicy(policy, run) {
|
|
370
598
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
599
|
+
const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
|
|
600
|
+
const context = this.page.context();
|
|
371
601
|
const handler = async (route) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
602
|
+
try {
|
|
603
|
+
// Context routing sees a popup's first request before the late `page`
|
|
604
|
+
// event. Fail every secondary page closed so no redirect can escape
|
|
605
|
+
// the per-page CDP interception while that session is being attached.
|
|
606
|
+
if (route.request().frame().page() !== this.page) {
|
|
607
|
+
await abortResourceRoute(route);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
await abortResourceRoute(route);
|
|
375
613
|
return;
|
|
376
614
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
615
|
+
await policyHandler(route);
|
|
616
|
+
};
|
|
617
|
+
const sessions = new Set();
|
|
618
|
+
const pendingAttachments = new Set();
|
|
619
|
+
const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
|
|
620
|
+
let active = true;
|
|
621
|
+
const attachPage = async (page) => {
|
|
622
|
+
const session = await context.newCDPSession(page);
|
|
623
|
+
if (!active) {
|
|
624
|
+
await session.detach();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const onRequestPaused = (params) => {
|
|
628
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
629
|
+
void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
|
|
630
|
+
return;
|
|
380
631
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
632
|
+
void handleCdpResourceRequest(session, policy, allowedMethods, params);
|
|
633
|
+
};
|
|
634
|
+
const authAttempts = new Set();
|
|
635
|
+
const onAuthRequired = (params) => {
|
|
636
|
+
if (!proxy)
|
|
637
|
+
return;
|
|
638
|
+
void handleCdpAuthRequired(session, proxy, authAttempts, params);
|
|
639
|
+
};
|
|
640
|
+
session.on("Fetch.requestPaused", onRequestPaused);
|
|
641
|
+
if (proxy) {
|
|
642
|
+
session.on("Fetch.authRequired", onAuthRequired);
|
|
643
|
+
}
|
|
644
|
+
try {
|
|
645
|
+
await session.send("Fetch.enable", {
|
|
646
|
+
...(proxy ? { handleAuthRequests: true } : {}),
|
|
647
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
648
|
+
});
|
|
649
|
+
sessions.add(session);
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
session.off("Fetch.requestPaused", onRequestPaused);
|
|
653
|
+
if (proxy) {
|
|
654
|
+
session.off("Fetch.authRequired", onAuthRequired);
|
|
389
655
|
}
|
|
656
|
+
await session.detach().catch(() => undefined);
|
|
657
|
+
throw error;
|
|
390
658
|
}
|
|
391
|
-
await route.abort("blockedbyclient");
|
|
392
659
|
};
|
|
393
|
-
|
|
660
|
+
const onPage = (page) => {
|
|
661
|
+
const attachment = attachPage(page)
|
|
662
|
+
.catch(async () => {
|
|
663
|
+
await page.close().catch(() => undefined);
|
|
664
|
+
})
|
|
665
|
+
.finally(() => pendingAttachments.delete(attachment));
|
|
666
|
+
pendingAttachments.add(attachment);
|
|
667
|
+
};
|
|
668
|
+
await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
394
669
|
try {
|
|
395
|
-
|
|
670
|
+
context.on("page", onPage);
|
|
671
|
+
try {
|
|
672
|
+
await attachPage(this.page);
|
|
673
|
+
return await run();
|
|
674
|
+
}
|
|
675
|
+
finally {
|
|
676
|
+
context.off("page", onPage);
|
|
677
|
+
}
|
|
396
678
|
}
|
|
397
679
|
finally {
|
|
398
|
-
|
|
680
|
+
active = false;
|
|
681
|
+
await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
682
|
+
await Promise.allSettled(pendingAttachments);
|
|
683
|
+
await Promise.all([...sessions].map(async (session) => {
|
|
684
|
+
await session.send("Fetch.disable").catch(() => undefined);
|
|
685
|
+
await session.detach().catch(() => undefined);
|
|
686
|
+
}));
|
|
399
687
|
}
|
|
400
688
|
}
|
|
401
689
|
}
|
|
402
690
|
class PlaywrightBrowserClient {
|
|
403
691
|
options;
|
|
404
692
|
browser = null;
|
|
693
|
+
parsedProxy;
|
|
694
|
+
proxyParsed = false;
|
|
405
695
|
engine = "playwright-stealth";
|
|
406
696
|
constructor(options = {}) {
|
|
407
697
|
this.options = options;
|
|
@@ -411,13 +701,17 @@ class PlaywrightBrowserClient {
|
|
|
411
701
|
return this.browser;
|
|
412
702
|
}
|
|
413
703
|
const chromium = await loadChromiumLauncher(this.options);
|
|
414
|
-
|
|
704
|
+
if (!this.proxyParsed) {
|
|
705
|
+
this.parsedProxy = toPlaywrightProxy(this.options.proxy);
|
|
706
|
+
this.proxyParsed = true;
|
|
707
|
+
}
|
|
708
|
+
this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
|
|
415
709
|
return this.browser;
|
|
416
710
|
}
|
|
417
711
|
async newPage() {
|
|
418
712
|
const browser = await this.ensureBrowser();
|
|
419
713
|
const page = await browser.newPage();
|
|
420
|
-
return new PlaywrightBrowserPage(page);
|
|
714
|
+
return new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
421
715
|
}
|
|
422
716
|
async rawPage() {
|
|
423
717
|
throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
|
|
@@ -427,9 +721,9 @@ class PlaywrightBrowserClient {
|
|
|
427
721
|
}
|
|
428
722
|
async withIsolatedContext(handler) {
|
|
429
723
|
const browser = await this.ensureBrowser();
|
|
430
|
-
const context = await browser.newContext();
|
|
724
|
+
const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
|
|
431
725
|
const page = await context.newPage();
|
|
432
|
-
const browserPage = new PlaywrightBrowserPage(page);
|
|
726
|
+
const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
433
727
|
try {
|
|
434
728
|
return await handler(browserPage);
|
|
435
729
|
}
|
|
@@ -966,7 +1260,7 @@ class CdpPoolBrowserPage {
|
|
|
966
1260
|
const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
|
|
967
1261
|
try {
|
|
968
1262
|
await this.pageClient.send("Fetch.enable", {
|
|
969
|
-
patterns:
|
|
1263
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
970
1264
|
});
|
|
971
1265
|
}
|
|
972
1266
|
catch (error) {
|
|
@@ -991,6 +1285,24 @@ class CdpPoolBrowserPage {
|
|
|
991
1285
|
return;
|
|
992
1286
|
}
|
|
993
1287
|
try {
|
|
1288
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
1289
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
1290
|
+
if (!response)
|
|
1291
|
+
throw new Error("CDP document response metadata is unavailable");
|
|
1292
|
+
if (!response.hasBody) {
|
|
1293
|
+
await this.pageClient.send("Fetch.continueResponse", {
|
|
1294
|
+
requestId,
|
|
1295
|
+
responseCode: response.responseCode,
|
|
1296
|
+
responseHeaders: [...response.responseHeaders],
|
|
1297
|
+
});
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const body = await this.pageClient.send("Fetch.getResponseBody", {
|
|
1301
|
+
requestId: response.requestId,
|
|
1302
|
+
});
|
|
1303
|
+
await this.pageClient.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy));
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
994
1306
|
const parsed = toCdpResourceRequest(params);
|
|
995
1307
|
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
996
1308
|
await this.failCdpResourceRequest(requestId);
|
|
@@ -1002,6 +1314,11 @@ class CdpPoolBrowserPage {
|
|
|
1002
1314
|
}
|
|
1003
1315
|
const decision = await resourceRoute.handle(parsed.request);
|
|
1004
1316
|
switch (decision.action) {
|
|
1317
|
+
case "continue":
|
|
1318
|
+
await this.pageClient.send("Fetch.continueRequest", {
|
|
1319
|
+
requestId: parsed.requestId,
|
|
1320
|
+
});
|
|
1321
|
+
return;
|
|
1005
1322
|
case "fulfill":
|
|
1006
1323
|
await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
|
|
1007
1324
|
return;
|
package/dist/runtime/choice.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { CredentialContext, EnvContext, ProviderChoiceConsumeMode, ProviderChoiceContext, ProviderRequestContext, ProviderRuntimeState } from "../types.js";
|
|
2
2
|
export declare const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV = "APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
|
|
3
|
-
export declare const PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV = "APIFUSE__PROVIDER_RUNTIME__CHOICE_WORD_ISSUANCE";
|
|
4
3
|
export type ProviderChoiceTelemetryEvent = {
|
|
5
4
|
readonly providerId: string;
|
|
6
5
|
readonly purpose: string;
|