@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 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
@@ -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, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "./runtime/choice.js";
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, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "./runtime/choice.js";
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";
@@ -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, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "./runtime/choice.js";
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, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "./runtime/choice.js";
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";
@@ -5,6 +5,7 @@ export type BrowserClientOptions = BrowserOptions & {
5
5
  cdpUrl?: string;
6
6
  executablePath?: string;
7
7
  extraArgs?: string[];
8
+ serviceWorkers?: "allow" | "block";
8
9
  };
9
10
  export declare class BrowserClient implements BrowserClientContract {
10
11
  private readonly client;
@@ -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: options.proxy ? { server: options.proxy } : undefined,
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
- const request = await toResourceRequest(route.request());
373
- if (!request || !allowedMethods.has(request.method)) {
374
- await route.abort("blockedbyclient");
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
- for (const resourceRoute of policy.routes) {
378
- if (!matchesResourceRoute(resourceRoute.match, request)) {
379
- continue;
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
- const decision = await resourceRoute.handle(request);
382
- switch (decision.action) {
383
- case "fulfill":
384
- await fulfillResourceRoute(route, decision);
385
- return;
386
- case "block":
387
- await route.abort("blockedbyclient");
388
- return;
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
- await this.page.route(RESOURCE_POLICY_ROUTE_PATTERN, handler);
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
- return await run();
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
- await this.page.unroute(RESOURCE_POLICY_ROUTE_PATTERN, handler);
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
- this.browser = await chromium.launch(toLaunchOptions(this.options));
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: [{ requestStage: "Request", urlPattern: "*" }],
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;
@@ -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;