@apifuse/provider-sdk 2.2.0-beta.32 → 2.2.0-beta.35
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 +14 -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/auth-flow.d.ts +3 -1
- package/dist/runtime/auth-flow.js +1 -0
- package/dist/runtime/browser.d.ts +1 -0
- package/dist/runtime/browser.js +350 -27
- package/dist/runtime/choice.d.ts +0 -1
- package/dist/runtime/choice.js +10 -126
- 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 +57 -18
- package/dist/runtime/resolver-vendors/types.d.ts +5 -2
- package/dist/runtime/resolver-vendors/types.js +16 -4
- package/dist/runtime/resolver.d.ts +1 -1
- package/dist/runtime/resolver.js +24 -5
- package/dist/server/serve-implementation.d.ts +2 -1
- package/dist/server/serve-implementation.js +27 -17
- package/dist/testing/run.js +1 -0
- package/dist/types.d.ts +25 -3
- package/package.json +3 -2
- package/src/error-resolution.ts +0 -1
- package/src/index.ts +0 -1
- package/src/provider.ts +0 -1
- package/src/runtime/auth-flow.ts +4 -0
- package/src/runtime/browser.ts +438 -31
- package/src/runtime/choice.ts +10 -151
- package/src/runtime/resolver-vendors/browser.ts +83 -16
- package/src/runtime/resolver-vendors/capsolver.ts +170 -33
- package/src/runtime/resolver-vendors/twocaptcha.ts +54 -15
- package/src/runtime/resolver-vendors/types.ts +22 -4
- package/src/runtime/resolver.ts +31 -7
- package/src/server/serve-implementation.ts +74 -17
- package/src/testing/run.ts +1 -0
- package/src/types.ts +26 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @apifuse/provider-sdk Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0-beta.35
|
|
4
|
+
|
|
5
|
+
- Release candidate for main commit 027fa0087b883a6281bc6d8bdaaf6d0be382000f.
|
|
6
|
+
|
|
7
|
+
## 2.2.0-beta.34
|
|
8
|
+
|
|
9
|
+
- Release candidate for main commit 383a97e7d0ce995d098d5f95c9ffdcbeed13de31.
|
|
10
|
+
|
|
11
|
+
## 2.2.0-beta.33
|
|
12
|
+
|
|
13
|
+
- Release candidate for main commit e7d854ec3e859702b22e2b37eaa2ae6bd4545c80.
|
|
14
|
+
|
|
3
15
|
## 2.2.0-beta.32
|
|
4
16
|
|
|
5
17
|
- Release candidate for main commit 82f1c0c1de916a40c8c74b1d6470a493b134c0ed.
|
|
@@ -46,6 +58,8 @@
|
|
|
46
58
|
|
|
47
59
|
## Unreleased
|
|
48
60
|
|
|
61
|
+
- Server-stored provider choices now issue word-format tokens unconditionally. The runtime issuance setting and legacy issuance path were removed.
|
|
62
|
+
- **Breaking:** Legacy encrypted server-handle choice tokens are no longer parsed. They are rejected with the uniform choice-not-found error; server-stored choices are now word-only. Encrypted inline choice tokens remain supported.
|
|
49
63
|
- 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
64
|
|
|
51
65
|
## 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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ContextScratchpad, EnvContext, FlowContext, HttpClient, OcrContext, StealthClient, SttContext } from "../types.js";
|
|
1
|
+
import type { ContextScratchpad, EnvContext, FlowContext, HttpClient, OcrContext, ProviderRuntimeState, StealthClient, SttContext } from "../types.js";
|
|
2
2
|
export declare function createScratchpad(allowedKeys: string[], initial?: Record<string, unknown>): ContextScratchpad;
|
|
3
3
|
export declare function createFlowContext(options: {
|
|
4
4
|
flowId?: string;
|
|
@@ -9,6 +9,8 @@ export declare function createFlowContext(options: {
|
|
|
9
9
|
providerId: string;
|
|
10
10
|
connectionId?: string;
|
|
11
11
|
externalRef?: string;
|
|
12
|
+
/** Host-agnostic: callers pass an already-scoped runtime state, which this helper forwards verbatim. */
|
|
13
|
+
state?: ProviderRuntimeState;
|
|
12
14
|
allowedKeys: string[];
|
|
13
15
|
initialContext?: Record<string, unknown>;
|
|
14
16
|
ocr?: OcrContext;
|
|
@@ -40,6 +40,7 @@ export function createFlowContext(options) {
|
|
|
40
40
|
tenantId: options.tenantId,
|
|
41
41
|
providerId: options.providerId,
|
|
42
42
|
http: options.http,
|
|
43
|
+
state: options.state,
|
|
43
44
|
stealth: options.stealth,
|
|
44
45
|
env: options.env,
|
|
45
46
|
context: createScratchpad(options.allowedKeys, options.initialContext),
|
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);
|
|
@@ -329,6 +557,9 @@ class PlaywrightBrowserPage {
|
|
|
329
557
|
}
|
|
330
558
|
return await this.page.evaluate(fn);
|
|
331
559
|
}
|
|
560
|
+
async userAgent() {
|
|
561
|
+
return await this.evaluate("navigator.userAgent");
|
|
562
|
+
}
|
|
332
563
|
async waitForSelector(selector, options) {
|
|
333
564
|
await this.page.waitForSelector(selector, options);
|
|
334
565
|
}
|
|
@@ -368,40 +599,102 @@ class PlaywrightBrowserPage {
|
|
|
368
599
|
}
|
|
369
600
|
async withResourcePolicy(policy, run) {
|
|
370
601
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
602
|
+
const policyHandler = createResourcePolicyHandler(policy, allowedMethods);
|
|
603
|
+
const context = this.page.context();
|
|
371
604
|
const handler = async (route) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
605
|
+
try {
|
|
606
|
+
// Context routing sees a popup's first request before the late `page`
|
|
607
|
+
// event. Fail every secondary page closed so no redirect can escape
|
|
608
|
+
// the per-page CDP interception while that session is being attached.
|
|
609
|
+
if (route.request().frame().page() !== this.page) {
|
|
610
|
+
await abortResourceRoute(route);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
await abortResourceRoute(route);
|
|
375
616
|
return;
|
|
376
617
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
618
|
+
await policyHandler(route);
|
|
619
|
+
};
|
|
620
|
+
const sessions = new Set();
|
|
621
|
+
const pendingAttachments = new Set();
|
|
622
|
+
const proxy = hasProxyCredentials(this.proxy) ? this.proxy : undefined;
|
|
623
|
+
let active = true;
|
|
624
|
+
const attachPage = async (page) => {
|
|
625
|
+
const session = await context.newCDPSession(page);
|
|
626
|
+
if (!active) {
|
|
627
|
+
await session.detach();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
const onRequestPaused = (params) => {
|
|
631
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
632
|
+
void handleCdpDocumentResponse(session, policy.documentContentSecurityPolicy, params);
|
|
633
|
+
return;
|
|
380
634
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
635
|
+
void handleCdpResourceRequest(session, policy, allowedMethods, params);
|
|
636
|
+
};
|
|
637
|
+
const authAttempts = new Set();
|
|
638
|
+
const onAuthRequired = (params) => {
|
|
639
|
+
if (!proxy)
|
|
640
|
+
return;
|
|
641
|
+
void handleCdpAuthRequired(session, proxy, authAttempts, params);
|
|
642
|
+
};
|
|
643
|
+
session.on("Fetch.requestPaused", onRequestPaused);
|
|
644
|
+
if (proxy) {
|
|
645
|
+
session.on("Fetch.authRequired", onAuthRequired);
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
await session.send("Fetch.enable", {
|
|
649
|
+
...(proxy ? { handleAuthRequests: true } : {}),
|
|
650
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
651
|
+
});
|
|
652
|
+
sessions.add(session);
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
session.off("Fetch.requestPaused", onRequestPaused);
|
|
656
|
+
if (proxy) {
|
|
657
|
+
session.off("Fetch.authRequired", onAuthRequired);
|
|
389
658
|
}
|
|
659
|
+
await session.detach().catch(() => undefined);
|
|
660
|
+
throw error;
|
|
390
661
|
}
|
|
391
|
-
await route.abort("blockedbyclient");
|
|
392
662
|
};
|
|
393
|
-
|
|
663
|
+
const onPage = (page) => {
|
|
664
|
+
const attachment = attachPage(page)
|
|
665
|
+
.catch(async () => {
|
|
666
|
+
await page.close().catch(() => undefined);
|
|
667
|
+
})
|
|
668
|
+
.finally(() => pendingAttachments.delete(attachment));
|
|
669
|
+
pendingAttachments.add(attachment);
|
|
670
|
+
};
|
|
671
|
+
await context.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
394
672
|
try {
|
|
395
|
-
|
|
673
|
+
context.on("page", onPage);
|
|
674
|
+
try {
|
|
675
|
+
await attachPage(this.page);
|
|
676
|
+
return await run();
|
|
677
|
+
}
|
|
678
|
+
finally {
|
|
679
|
+
context.off("page", onPage);
|
|
680
|
+
}
|
|
396
681
|
}
|
|
397
682
|
finally {
|
|
398
|
-
|
|
683
|
+
active = false;
|
|
684
|
+
await context.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
|
|
685
|
+
await Promise.allSettled(pendingAttachments);
|
|
686
|
+
await Promise.all([...sessions].map(async (session) => {
|
|
687
|
+
await session.send("Fetch.disable").catch(() => undefined);
|
|
688
|
+
await session.detach().catch(() => undefined);
|
|
689
|
+
}));
|
|
399
690
|
}
|
|
400
691
|
}
|
|
401
692
|
}
|
|
402
693
|
class PlaywrightBrowserClient {
|
|
403
694
|
options;
|
|
404
695
|
browser = null;
|
|
696
|
+
parsedProxy;
|
|
697
|
+
proxyParsed = false;
|
|
405
698
|
engine = "playwright-stealth";
|
|
406
699
|
constructor(options = {}) {
|
|
407
700
|
this.options = options;
|
|
@@ -411,13 +704,17 @@ class PlaywrightBrowserClient {
|
|
|
411
704
|
return this.browser;
|
|
412
705
|
}
|
|
413
706
|
const chromium = await loadChromiumLauncher(this.options);
|
|
414
|
-
|
|
707
|
+
if (!this.proxyParsed) {
|
|
708
|
+
this.parsedProxy = toPlaywrightProxy(this.options.proxy);
|
|
709
|
+
this.proxyParsed = true;
|
|
710
|
+
}
|
|
711
|
+
this.browser = await chromium.launch(toLaunchOptions(this.options, this.parsedProxy));
|
|
415
712
|
return this.browser;
|
|
416
713
|
}
|
|
417
714
|
async newPage() {
|
|
418
715
|
const browser = await this.ensureBrowser();
|
|
419
716
|
const page = await browser.newPage();
|
|
420
|
-
return new PlaywrightBrowserPage(page);
|
|
717
|
+
return new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
421
718
|
}
|
|
422
719
|
async rawPage() {
|
|
423
720
|
throw new ProviderError("ctx.browser.rawPage() requires a CDP pool", {
|
|
@@ -427,9 +724,9 @@ class PlaywrightBrowserClient {
|
|
|
427
724
|
}
|
|
428
725
|
async withIsolatedContext(handler) {
|
|
429
726
|
const browser = await this.ensureBrowser();
|
|
430
|
-
const context = await browser.newContext();
|
|
727
|
+
const context = await browser.newContext({ serviceWorkers: this.options.serviceWorkers });
|
|
431
728
|
const page = await context.newPage();
|
|
432
|
-
const browserPage = new PlaywrightBrowserPage(page);
|
|
729
|
+
const browserPage = new PlaywrightBrowserPage(page, this.parsedProxy);
|
|
433
730
|
try {
|
|
434
731
|
return await handler(browserPage);
|
|
435
732
|
}
|
|
@@ -823,6 +1120,9 @@ class CdpPoolBrowserPage {
|
|
|
823
1120
|
await this.initialize();
|
|
824
1121
|
return await this.evaluateWithContext(fn);
|
|
825
1122
|
}
|
|
1123
|
+
async userAgent() {
|
|
1124
|
+
return await this.evaluate("navigator.userAgent");
|
|
1125
|
+
}
|
|
826
1126
|
async evaluateInFrame(frameId, fn) {
|
|
827
1127
|
await this.initialize();
|
|
828
1128
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
@@ -966,7 +1266,7 @@ class CdpPoolBrowserPage {
|
|
|
966
1266
|
const unsubscribe = this.pageClient.on("Fetch.requestPaused", handlePausedRequest);
|
|
967
1267
|
try {
|
|
968
1268
|
await this.pageClient.send("Fetch.enable", {
|
|
969
|
-
patterns:
|
|
1269
|
+
patterns: resourcePolicyFetchPatterns(policy),
|
|
970
1270
|
});
|
|
971
1271
|
}
|
|
972
1272
|
catch (error) {
|
|
@@ -991,6 +1291,24 @@ class CdpPoolBrowserPage {
|
|
|
991
1291
|
return;
|
|
992
1292
|
}
|
|
993
1293
|
try {
|
|
1294
|
+
if (isCdpResponseStage(params) && policy.documentContentSecurityPolicy) {
|
|
1295
|
+
const response = toCdpPausedDocumentResponse(params);
|
|
1296
|
+
if (!response)
|
|
1297
|
+
throw new Error("CDP document response metadata is unavailable");
|
|
1298
|
+
if (!response.hasBody) {
|
|
1299
|
+
await this.pageClient.send("Fetch.continueResponse", {
|
|
1300
|
+
requestId,
|
|
1301
|
+
responseCode: response.responseCode,
|
|
1302
|
+
responseHeaders: [...response.responseHeaders],
|
|
1303
|
+
});
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
const body = await this.pageClient.send("Fetch.getResponseBody", {
|
|
1307
|
+
requestId: response.requestId,
|
|
1308
|
+
});
|
|
1309
|
+
await this.pageClient.send("Fetch.fulfillRequest", toCdpDocumentFulfillParams(response, body, policy.documentContentSecurityPolicy));
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
994
1312
|
const parsed = toCdpResourceRequest(params);
|
|
995
1313
|
if (!parsed || !allowedMethods.has(parsed.request.method)) {
|
|
996
1314
|
await this.failCdpResourceRequest(requestId);
|
|
@@ -1002,6 +1320,11 @@ class CdpPoolBrowserPage {
|
|
|
1002
1320
|
}
|
|
1003
1321
|
const decision = await resourceRoute.handle(parsed.request);
|
|
1004
1322
|
switch (decision.action) {
|
|
1323
|
+
case "continue":
|
|
1324
|
+
await this.pageClient.send("Fetch.continueRequest", {
|
|
1325
|
+
requestId: parsed.requestId,
|
|
1326
|
+
});
|
|
1327
|
+
return;
|
|
1005
1328
|
case "fulfill":
|
|
1006
1329
|
await this.pageClient.send("Fetch.fulfillRequest", toCdpFulfillParams(parsed.requestId, decision));
|
|
1007
1330
|
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;
|