@apifuse/provider-sdk 2.2.0-beta.27 → 2.2.0-beta.29

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-dev.ts +34 -5
  3. package/bin/apifuse-pack-smoke.ts +1 -1
  4. package/bin/apifuse-pack-types.ts +26 -2
  5. package/bin/apifuse-perf.ts +2 -4
  6. package/bin/apifuse-record.ts +39 -6
  7. package/dist/auth-turn/index.d.ts +1 -1
  8. package/dist/auth-turn/index.js +1 -1
  9. package/dist/auth.d.ts +14 -0
  10. package/dist/auth.js +38 -0
  11. package/dist/ceremonies/index.js +52 -11
  12. package/dist/config/loader.d.ts +3 -1
  13. package/dist/config/loader.js +4 -2
  14. package/dist/index.d.ts +8 -7
  15. package/dist/index.js +5 -7
  16. package/dist/provider.d.ts +2 -1
  17. package/dist/provider.js +1 -1
  18. package/dist/runtime/auth-flow.js +1 -1
  19. package/dist/runtime/browser.js +45 -2
  20. package/dist/runtime/http.d.ts +1 -0
  21. package/dist/runtime/http.js +135 -12
  22. package/dist/runtime/instrumentation.js +1 -1
  23. package/dist/runtime/native-network-errors.d.ts +33 -0
  24. package/dist/runtime/native-network-errors.js +69 -0
  25. package/dist/runtime/native-network.d.ts +2 -33
  26. package/dist/runtime/native-network.js +2 -68
  27. package/dist/runtime/proxy-telemetry.js +3 -0
  28. package/dist/runtime/redis.d.ts +1 -1
  29. package/dist/runtime/redis.js +4 -2
  30. package/dist/runtime/resolver-config.d.ts +6 -0
  31. package/dist/runtime/resolver-config.js +6 -0
  32. package/dist/runtime/resolver-public.d.ts +1 -0
  33. package/dist/runtime/resolver-public.js +1 -0
  34. package/dist/runtime/resolver-shared.d.ts +3 -0
  35. package/dist/runtime/resolver-shared.js +12 -0
  36. package/dist/runtime/resolver-vendors/browser.js +14 -4
  37. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
  38. package/dist/runtime/resolver-vendors/twocaptcha.js +157 -53
  39. package/dist/runtime/resolver-vendors/types.d.ts +2 -2
  40. package/dist/runtime/resolver-vendors/types.js +3 -1
  41. package/dist/runtime/resolver.d.ts +15 -10
  42. package/dist/runtime/resolver.js +92 -23
  43. package/dist/runtime/state.js +5 -115
  44. package/dist/runtime/stealth-cookies.d.ts +20 -0
  45. package/dist/runtime/stealth-cookies.js +111 -0
  46. package/dist/runtime/stealth.d.ts +1 -0
  47. package/dist/runtime/stealth.js +8 -131
  48. package/dist/serve.d.ts +1 -1
  49. package/dist/serve.js +1 -1
  50. package/dist/server/index.d.ts +1 -1
  51. package/dist/server/index.js +1 -1
  52. package/dist/server/self-test.d.ts +13 -0
  53. package/dist/server/self-test.js +124 -46
  54. package/dist/server/serve-implementation.d.ts +199 -0
  55. package/dist/server/serve-implementation.js +2072 -0
  56. package/dist/server/serve.d.ts +1 -187
  57. package/dist/server/serve.js +1 -1827
  58. package/dist/stateful/errors.d.ts +5 -0
  59. package/dist/stateful/errors.js +10 -0
  60. package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
  61. package/dist/stateful/stateful-provider-session-routing.js +2 -10
  62. package/dist/stream.js +7 -1
  63. package/dist/testing/index.d.ts +1 -0
  64. package/dist/testing/index.js +1 -0
  65. package/package.json +27 -2
  66. package/src/auth-turn/index.ts +1 -1
  67. package/src/auth.ts +78 -0
  68. package/src/ceremonies/index.ts +68 -18
  69. package/src/config/loader.ts +8 -2
  70. package/src/index.ts +18 -24
  71. package/src/provider.ts +12 -14
  72. package/src/runtime/auth-flow.ts +1 -1
  73. package/src/runtime/browser.ts +50 -2
  74. package/src/runtime/http.ts +155 -11
  75. package/src/runtime/instrumentation.ts +1 -1
  76. package/src/runtime/native-network-errors.ts +99 -0
  77. package/src/runtime/native-network.ts +16 -97
  78. package/src/runtime/proxy-telemetry.ts +5 -0
  79. package/src/runtime/redis.ts +7 -2
  80. package/src/runtime/resolver-config.ts +6 -0
  81. package/src/runtime/resolver-public.ts +18 -0
  82. package/src/runtime/resolver-shared.ts +17 -0
  83. package/src/runtime/resolver-vendors/browser.ts +14 -4
  84. package/src/runtime/resolver-vendors/twocaptcha.ts +190 -56
  85. package/src/runtime/resolver-vendors/types.ts +8 -2
  86. package/src/runtime/resolver.ts +140 -28
  87. package/src/runtime/state.ts +5 -144
  88. package/src/runtime/stealth-cookies.ts +132 -0
  89. package/src/runtime/stealth.ts +15 -158
  90. package/src/serve.ts +6 -1
  91. package/src/server/index.ts +1 -0
  92. package/src/server/self-test.ts +184 -59
  93. package/src/server/serve-implementation.ts +3042 -0
  94. package/src/server/serve.ts +1 -2661
  95. package/src/stateful/errors.ts +12 -0
  96. package/src/stateful/stateful-provider-session-routing.ts +2 -11
  97. package/src/stream.ts +8 -1
  98. package/src/testing/index.ts +1 -0
@@ -1,4 +1,9 @@
1
1
  export type StatefulControlPlaneOperation = "resolve" | "acquire" | "renew" | "release";
2
+ export declare class StatefulRoutingDeadlineError extends Error {
3
+ readonly requestId: string;
4
+ readonly deadlineAt: string;
5
+ constructor(requestId: string, deadlineAt: string);
6
+ }
2
7
  export declare class StatefulControlPlaneError extends Error {
3
8
  readonly code: string;
4
9
  readonly operation: StatefulControlPlaneOperation;
@@ -1,3 +1,13 @@
1
+ export class StatefulRoutingDeadlineError extends Error {
2
+ requestId;
3
+ deadlineAt;
4
+ constructor(requestId, deadlineAt) {
5
+ super(`Stateful operation request ${requestId} deadline has expired.`);
6
+ this.name = "StatefulRoutingDeadlineError";
7
+ this.requestId = requestId;
8
+ this.deadlineAt = deadlineAt;
9
+ }
10
+ }
1
11
  export class StatefulControlPlaneError extends Error {
2
12
  code;
3
13
  operation;
@@ -40,11 +40,7 @@ export interface StatefulSessionRouterOptions {
40
40
  readonly clock?: () => Date;
41
41
  readonly metricEmitter?: StatefulProviderMetricEmitter;
42
42
  }
43
- export declare class StatefulRoutingDeadlineError extends Error {
44
- readonly requestId: string;
45
- readonly deadlineAt: string;
46
- constructor(requestId: string, deadlineAt: string);
47
- }
43
+ export { StatefulRoutingDeadlineError } from "./errors.js";
48
44
  export declare class StatefulRoutingOwnershipError extends Error {
49
45
  readonly requestId: string;
50
46
  readonly sessionKey: SessionKey;
@@ -1,15 +1,7 @@
1
+ import { StatefulRoutingDeadlineError } from "./errors.js";
1
2
  import { NOOP_STATEFUL_PROVIDER_METRIC_EMITTER, } from "./stateful-provider-observability.js";
2
3
  export { forwardingContextFromStatefulRuntimeContext, isStatefulOperationRuntimeContext, providerContextFromStatefulRuntimeContext, statefulForwardingContextFromProviderRequest, withStatefulLocalProviderContext, } from "./stateful-provider-runtime-context.js";
3
- export class StatefulRoutingDeadlineError extends Error {
4
- requestId;
5
- deadlineAt;
6
- constructor(requestId, deadlineAt) {
7
- super(`Stateful operation request ${requestId} deadline has expired.`);
8
- this.name = "StatefulRoutingDeadlineError";
9
- this.requestId = requestId;
10
- this.deadlineAt = deadlineAt;
11
- }
12
- }
4
+ export { StatefulRoutingDeadlineError } from "./errors.js";
13
5
  export class StatefulRoutingOwnershipError extends Error {
14
6
  requestId;
15
7
  sessionKey;
package/dist/stream.js CHANGED
@@ -41,16 +41,22 @@ function sseFieldValue(value, field) {
41
41
  }
42
42
  export async function* readableBytes(body) {
43
43
  const reader = body.getReader();
44
+ let completed = false;
44
45
  try {
45
46
  for (;;) {
46
47
  const { value, done } = await reader.read();
47
- if (done)
48
+ if (done) {
49
+ completed = true;
48
50
  return;
51
+ }
49
52
  if (value)
50
53
  yield value;
51
54
  }
52
55
  }
53
56
  finally {
57
+ if (!completed) {
58
+ await reader.cancel().catch(() => undefined);
59
+ }
54
60
  reader.releaseLock();
55
61
  }
56
62
  }
@@ -1,2 +1,3 @@
1
1
  export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
2
+ export { resetProviderCacheForTests } from "../runtime/cache.js";
2
3
  export { runStandardTests, type StandardTestsManifest, type StandardTestsOptions, type StandardTestsResult, type StandardTestsUpstreamCall, type StandardTestsUpstreamResponse, type StandardTestsUpstreamStub, } from "./run.js";
@@ -1,2 +1,3 @@
1
1
  export { describeTransform, snapshotTransform, toMatchShape } from "./helpers.js";
2
+ export { resetProviderCacheForTests } from "../runtime/cache.js";
2
3
  export { runStandardTests, } from "./run.js";
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.0-beta.27",
2
+ "version": "2.2.0-beta.29",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
@@ -61,6 +61,31 @@
61
61
  "import": "./dist/server/index.js",
62
62
  "default": "./dist/server/index.js"
63
63
  },
64
+ "./runtime/browser": {
65
+ "types": "./dist/runtime/browser.d.ts",
66
+ "import": "./dist/runtime/browser.js",
67
+ "default": "./dist/runtime/browser.js"
68
+ },
69
+ "./runtime/native-network": {
70
+ "types": "./dist/runtime/native-network.d.ts",
71
+ "import": "./dist/runtime/native-network.js",
72
+ "default": "./dist/runtime/native-network.js"
73
+ },
74
+ "./runtime/prevalidate": {
75
+ "types": "./dist/runtime/prevalidate.d.ts",
76
+ "import": "./dist/runtime/prevalidate.js",
77
+ "default": "./dist/runtime/prevalidate.js"
78
+ },
79
+ "./runtime/resolver": {
80
+ "types": "./dist/runtime/resolver-public.d.ts",
81
+ "import": "./dist/runtime/resolver-public.js",
82
+ "default": "./dist/runtime/resolver-public.js"
83
+ },
84
+ "./runtime/stealth": {
85
+ "types": "./dist/runtime/stealth.d.ts",
86
+ "import": "./dist/runtime/stealth.js",
87
+ "default": "./dist/runtime/stealth.js"
88
+ },
64
89
  "./stateful": {
65
90
  "types": "./dist/stateful/index.d.ts",
66
91
  "import": "./dist/stateful/index.js",
@@ -97,6 +122,7 @@
97
122
  "@biomejs/biome": "^2.5.0",
98
123
  "@types/bun": "latest",
99
124
  "@types/node": "^25.9.3",
125
+ "ajv": "^8.17",
100
126
  "typescript": "6.0.3"
101
127
  },
102
128
  "dependencies": {
@@ -104,7 +130,6 @@
104
130
  "@rgrove/parse-xml": "4.2.2",
105
131
  "@types/ms": "^2.1.0",
106
132
  "acorn": "^8.17.0",
107
- "ajv": "^8.17",
108
133
  "hono": "^4.12.25",
109
134
  "ioredis": "^5.11.1",
110
135
  "ms": "^2.1.3",
@@ -24,7 +24,7 @@ export const AUTH_TURN_SCHEMA_ARTIFACT_PATH = "dist/auth-turn/auth-turn.v1.schem
24
24
  *
25
25
  * This is the exact codification of the runtime validation the SDK applies to
26
26
  * ceremony outputs (see `validateCeremonyOutput` in `src/ceremonies`), which
27
- * compiles this same document. `kind` is an OPEN string on the wire: the known
27
+ * evaluates this same document. `kind` is an OPEN string on the wire: the known
28
28
  * kinds in {@link TURN_KINDS} are tooling metadata, never a wire constraint.
29
29
  *
30
30
  * The committed artifact at `src/auth-turn/auth-turn.v1.schema.json` (shipped
package/src/auth.ts CHANGED
@@ -296,6 +296,25 @@ export interface DefineCredentialsAuthOptions<
296
296
  ):
297
297
  | CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>
298
298
  | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
299
+ /**
300
+ * Optional re-mint of an expired session from the stored credential, wired
301
+ * to `auth.flow.refresh`.
302
+ *
303
+ * Credential-auth upstreams routinely invalidate a session well before the
304
+ * `expiresAt` the provider advertised, which leaves every operation failing
305
+ * with a reauth error until a human repeats the whole interactive login.
306
+ * Implement this to re-establish the session from what is already stored on
307
+ * the connection; the result is resolved exactly like `login`, so it may
308
+ * also raise a challenge when the upstream demands one. Omit it when the
309
+ * upstream has no non-interactive path and re-authentication genuinely
310
+ * requires the user.
311
+ */
312
+ refresh?(
313
+ ctx: FlowContext,
314
+ input: Partial<CredentialsAuthInput<TFields>>,
315
+ ):
316
+ | CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>
317
+ | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
299
318
  }
300
319
 
301
320
  export interface DefinedCredentialsAuth {
@@ -376,6 +395,27 @@ function normalizeInput<TFields extends CredentialsAuthFields>(
376
395
  return result as CredentialsAuthInput<TFields>;
377
396
  }
378
397
 
398
+ /**
399
+ * Refresh variant of {@link normalizeInput}. `login` coerces every declared
400
+ * field to a string because the interactive turn has already enforced that they
401
+ * are present; refresh runs with no user present, so an absent field is omitted
402
+ * rather than turned into an empty string. That keeps "the user did not supply
403
+ * this" distinguishable from "the user supplied an empty value".
404
+ */
405
+ function normalizePartialInput<TFields extends CredentialsAuthFields>(
406
+ fields: TFields,
407
+ input: Record<string, unknown> | undefined,
408
+ ): Partial<CredentialsAuthInput<TFields>> {
409
+ const result: Record<string, string> = {};
410
+ for (const name of Object.keys(fields)) {
411
+ const value = input?.[name];
412
+ if (typeof value === "string") {
413
+ result[name] = value;
414
+ }
415
+ }
416
+ return result as Partial<CredentialsAuthInput<TFields>>;
417
+ }
418
+
379
419
  function assertCredentialKeys<TCredentialKeys extends readonly string[]>(
380
420
  credentialKeys: TCredentialKeys,
381
421
  credential: Record<string, unknown>,
@@ -716,6 +756,44 @@ export function defineCredentialsAuth<
716
756
  completeTurnId,
717
757
  );
718
758
  },
759
+ // Only advertise refresh when the provider implements it: the
760
+ // protocol treats the hook's presence as "this connection can be
761
+ // re-established without the user", and exposing a stub that
762
+ // cannot actually re-mint would turn a recoverable expiry into a
763
+ // silent failure.
764
+ ...(options.refresh
765
+ ? {
766
+ refresh: async (ctx: FlowContext, rawInput?: Record<string, unknown>) => {
767
+ // A pending challenge belongs to the interactive flow that
768
+ // raised it; finish it there rather than restarting.
769
+ const pending = getPendingChallenge(ctx);
770
+ if (pending) {
771
+ return await continuePendingChallenge(
772
+ ctx,
773
+ options.credentialKeys,
774
+ challenges,
775
+ pending,
776
+ rawInput,
777
+ completeTurnId,
778
+ );
779
+ }
780
+
781
+ // Refresh runs without user input, so fields are optional
782
+ // here — unlike `continue`, missing ones are not a retry.
783
+ const result = await options.refresh!(
784
+ ctx,
785
+ normalizePartialInput(options.fields, rawInput),
786
+ );
787
+ return await resolveAuthResult(
788
+ ctx,
789
+ options.credentialKeys,
790
+ challenges,
791
+ result,
792
+ completeTurnId,
793
+ );
794
+ },
795
+ }
796
+ : {}),
719
797
  },
720
798
  },
721
799
  credential: {
@@ -1,7 +1,5 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
 
3
- import Ajv2020 from "ajv/dist/2020.js";
4
-
5
3
  import { AUTH_TURN_SCHEMA, type KnownAuthTurnKind } from "../auth-turn/index.js";
6
4
  import {
7
5
  FlowExpiredError,
@@ -16,13 +14,14 @@ type TurnKind = KnownAuthTurnKind;
16
14
  type CeremonyHandler = AuthFlowInputHandler;
17
15
 
18
16
  type JsonObject = Record<string, unknown>;
19
-
20
- const ajv = new Ajv2020({ allErrors: true, strict: true, strictSchema: true });
21
-
22
- // Runtime ceremony-output validation derives from the exported versioned
23
- // contract: it compiles the exact AUTH_TURN_SCHEMA document shipped at
24
- // dist/auth-turn/auth-turn.v1.schema.json, so the two cannot drift.
25
- const validateAuthTurn = ajv.compile(AUTH_TURN_SCHEMA);
17
+ type JsonSchemaNode = {
18
+ readonly additionalProperties?: boolean;
19
+ readonly minimum?: number;
20
+ readonly minLength?: number;
21
+ readonly properties?: Readonly<Record<string, JsonSchemaNode>>;
22
+ readonly required?: readonly string[];
23
+ readonly type?: "number" | "object" | "string";
24
+ };
26
25
 
27
26
  const OAUTH2_STATE_KEY = "__oauth2_state";
28
27
  const OAUTH2_PKCE_VERIFIER_KEY = "__oauth2_pkce_verifier";
@@ -45,6 +44,56 @@ function isRecord(value: unknown): value is JsonObject {
45
44
  return !!value && typeof value === "object" && !Array.isArray(value);
46
45
  }
47
46
 
47
+ function validateSchemaNode(
48
+ value: unknown,
49
+ schema: JsonSchemaNode,
50
+ path: string,
51
+ errors: string[],
52
+ ): void {
53
+ if (schema.type === "object") {
54
+ if (!isRecord(value)) {
55
+ errors.push(`${path} must be object`);
56
+ return;
57
+ }
58
+ const properties = schema.properties ?? {};
59
+ for (const required of schema.required ?? []) {
60
+ if (!Object.hasOwn(value, required) || value[required] === undefined) {
61
+ errors.push(`${path} must have required property '${required}'`);
62
+ }
63
+ }
64
+ if (schema.additionalProperties === false) {
65
+ for (const key of Object.keys(value)) {
66
+ if (!Object.hasOwn(properties, key)) {
67
+ errors.push(`${path} must NOT have additional property '${key}'`);
68
+ }
69
+ }
70
+ }
71
+ for (const [key, childSchema] of Object.entries(properties)) {
72
+ if (Object.hasOwn(value, key) && value[key] !== undefined) {
73
+ validateSchemaNode(value[key], childSchema, `${path}/${key}`, errors);
74
+ }
75
+ }
76
+ return;
77
+ }
78
+
79
+ if (schema.type === "string") {
80
+ if (typeof value !== "string") {
81
+ errors.push(`${path} must be string`);
82
+ } else if (schema.minLength !== undefined && value.length < schema.minLength) {
83
+ errors.push(`${path} must NOT have fewer than ${schema.minLength} characters`);
84
+ }
85
+ return;
86
+ }
87
+
88
+ if (schema.type === "number") {
89
+ if (typeof value !== "number" || !Number.isFinite(value)) {
90
+ errors.push(`${path} must be number`);
91
+ } else if (schema.minimum !== undefined && value < schema.minimum) {
92
+ errors.push(`${path} must be >= ${schema.minimum}`);
93
+ }
94
+ }
95
+ }
96
+
48
97
  function ensureRecord(value: unknown): JsonObject {
49
98
  return isRecord(value) ? value : {};
50
99
  }
@@ -162,14 +211,17 @@ function withDeclaredFormFieldOrder(expectedInput: JsonObject): JsonObject {
162
211
  }
163
212
 
164
213
  export function validateCeremonyOutput(turn: unknown): AuthTurn {
165
- if (!validateAuthTurn(turn)) {
166
- const detail = validateAuthTurn.errors
167
- ?.map((error) => `${error.instancePath || "$"} ${error.message ?? "invalid"}`)
168
- .join("; ");
169
- throw new TurnValidationError(detail || "Invalid AuthTurn output");
214
+ // Evaluate the exact exported versioned schema without eagerly initializing
215
+ // a general-purpose JSON Schema compiler in every provider process. Contract
216
+ // parity tests compare this focused evaluator with AJV over all fixtures and
217
+ // edge probes, so the runtime and shipped document remain locked together.
218
+ const errors: string[] = [];
219
+ validateSchemaNode(turn, AUTH_TURN_SCHEMA, "", errors);
220
+ if (errors.length > 0) {
221
+ throw new TurnValidationError(errors.join("; "));
170
222
  }
171
223
 
172
- return turn;
224
+ return turn as AuthTurn;
173
225
  }
174
226
 
175
227
  export function createOAuth2Ceremony(options: {
@@ -285,9 +337,7 @@ export function createOAuth2ProxiedStart(
285
337
  }
286
338
  const clientId = getRequiredEnv(ctx, options.clientIdEnvKey);
287
339
  if (!ctx.flowId) {
288
- throw new ValidationError(
289
- "OAuth2 proxied start requires the gateway flow id.",
290
- );
340
+ throw new ValidationError("OAuth2 proxied start requires the gateway flow id.");
291
341
  }
292
342
 
293
343
  const authorizePath = new URL(options.authorizeUrl).pathname;
@@ -1,8 +1,9 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { existsSync } from "node:fs";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
 
5
- import { Redis } from "ioredis";
6
+ import type { Redis } from "ioredis";
6
7
 
7
8
  import type { ProviderProxyPolicy, ProviderProxyProvider, TraceConfig } from "../types.js";
8
9
  import {
@@ -102,8 +103,11 @@ export type SmartproxyAllocatorBodyClass =
102
103
  | "text_without_proxies"
103
104
  | "usable_proxy_endpoints";
104
105
 
106
+ export type ProxyUserAgentSource = "declared" | "defaulted";
107
+
105
108
  export type ProxyResolutionTelemetryEvent = {
106
109
  provider: ProxyVendorName;
110
+ userAgentSource?: ProxyUserAgentSource;
107
111
  protocol?: ProxyProtocol;
108
112
  cacheStatus: ProxyCacheStatus;
109
113
  cacheHit: boolean;
@@ -217,6 +221,7 @@ type ProxyRedisClient = Pick<
217
221
  >;
218
222
 
219
223
  const proxyCache = new Map<string, CachedProxyPool>();
224
+ const require = createRequire(import.meta.url);
220
225
  const proxyInflight = new Map<string, Promise<SmartproxyAllocationResult>>();
221
226
  const invalidatedProxyKeys = new Map<string, number>();
222
227
  const redisClients = new Map<string, ProxyRedisClient>();
@@ -276,7 +281,8 @@ function getProxyRedis(): ProxyRedisClient | undefined {
276
281
  const existing = redisClients.get(redisUrl);
277
282
  if (existing) return existing;
278
283
 
279
- const redis = new Redis(redisUrl, {
284
+ const { Redis: RedisClient } = require("ioredis") as typeof import("ioredis");
285
+ const redis = new RedisClient(redisUrl, {
280
286
  connectTimeout: REDIS_TIMEOUT_MS,
281
287
  enableOfflineQueue: false,
282
288
  lazyConnect: true,
package/src/index.ts CHANGED
@@ -50,13 +50,11 @@ export * from "./recipes/gov-api.js";
50
50
  export * from "./recipes/rest-api.js";
51
51
  export { createFlowContext, createScratchpad } from "./runtime/auth-flow.js";
52
52
  export type { BrowserClientOptions } from "./runtime/browser.js";
53
- export { BrowserClient, createBrowserClient } from "./runtime/browser.js";
54
53
  export {
55
54
  APIFUSE__CACHE__KEY_PEPPER_ENV,
56
55
  createBypassProviderCache,
57
56
  createProviderCache,
58
57
  type ProviderCacheOptions,
59
- resetProviderCacheForTests,
60
58
  } from "./runtime/cache.js";
61
59
  export {
62
60
  type CreateProviderChoiceContextOptions,
@@ -72,25 +70,23 @@ export { createEnvContext } from "./runtime/env.js";
72
70
  export { executeOperation } from "./runtime/executor.js";
73
71
  export { createHttpClient } from "./runtime/http.js";
74
72
  export {
75
- createNativeNetworkClient,
76
- createEnvVendorCredentialResolver,
77
- deriveNativeCredentialAffinityKey,
78
73
  NativeEgressGrantExpiredError,
79
74
  NativeEgressNotDeclaredError,
80
75
  NativeIdleTimeoutError,
81
76
  NativeNetworkError,
82
77
  NativeProxyExpiredError,
83
- resolveNativeGatewayProxy,
84
- type NativeGatewayProxy,
85
- type NativeGatewayProxyResolutionInput,
86
- type NativeGatewayProxySkipReason,
87
- type NativeGatewayProxySynthesizer,
88
- type NativeGatewayProxySynthesisResult,
89
- type NativeGatewayProxySynthesisInput,
90
- type NativeNetworkClientOptions,
91
- type NativeNetworkErrorCode,
92
- type VendorCredentialLookup,
93
- type VendorCredentialResolver,
78
+ } from "./runtime/native-network-errors.js";
79
+ export type {
80
+ NativeGatewayProxy,
81
+ NativeGatewayProxyResolutionInput,
82
+ NativeGatewayProxySkipReason,
83
+ NativeGatewayProxySynthesizer,
84
+ NativeGatewayProxySynthesisResult,
85
+ NativeGatewayProxySynthesisInput,
86
+ NativeNetworkClientOptions,
87
+ NativeNetworkErrorCode,
88
+ VendorCredentialLookup,
89
+ VendorCredentialResolver,
94
90
  } from "./runtime/native-network.js";
95
91
  export type { Insight, InsightSeverity } from "./runtime/insights.js";
96
92
  export { generateInsights } from "./runtime/insights.js";
@@ -99,7 +95,7 @@ export {
99
95
  type InstrumentedProviderContext,
100
96
  wrapWithInstrumentation,
101
97
  } from "./runtime/instrumentation.js";
102
- export { type PrevalidateResult, prevalidate } from "./runtime/prevalidate.js";
98
+ export type { PrevalidateResult } from "./runtime/prevalidate.js";
103
99
  export { getProviderBaseUrl } from "./runtime/provider.js";
104
100
  export {
105
101
  APIFUSE__CDP_POOL__URL,
@@ -107,12 +103,10 @@ export {
107
103
  APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
108
104
  APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
109
105
  APIFUSE__RESOLVER__TIMEOUT_MS,
110
- createResolverClientFromEnv,
111
- createUnsupportedResolverClient,
112
106
  DEFAULT_RESOLVER_TIMEOUT_MS,
113
- invalidateResolverSolution,
114
- type ResolverRuntimeOptions,
115
- } from "./runtime/resolver.js";
107
+ } from "./runtime/resolver-config.js";
108
+ export { createUnsupportedResolverClient } from "./runtime/resolver-shared.js";
109
+ export type { ResolverRuntimeOptions } from "./runtime/resolver.js";
116
110
  export type { ResolverVendorTransport } from "./runtime/resolver-vendors/types.js";
117
111
  export {
118
112
  assertRequiredSecretsPresent,
@@ -123,7 +117,6 @@ export {
123
117
  createUnsupportedProviderRuntimeState,
124
118
  UnsupportedProviderStateError,
125
119
  } from "./runtime/state.js";
126
- export { createStealthClient } from "./runtime/stealth.js";
127
120
  export {
128
121
  APIFUSE__OCR__API_KEY_ENV,
129
122
  APIFUSE__OCR__BACKEND_ENV,
@@ -174,10 +167,11 @@ export {
174
167
  } from "./schema.js";
175
168
  export {
176
169
  createServerApp,
170
+ createServerAppAsync,
177
171
  ERROR_OBSERVABILITY_HEADER,
178
172
  type ServeOptions,
179
173
  serve,
180
- } from "./server/index.js";
174
+ } from "./server/serve.js";
181
175
  export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
182
176
  export * from "./stream.js";
183
177
  export type {
package/src/provider.ts CHANGED
@@ -167,25 +167,23 @@ export type {
167
167
  StateWriteOptions,
168
168
  } from "./types.js";
169
169
  export {
170
- createNativeNetworkClient,
171
- createEnvVendorCredentialResolver,
172
- deriveNativeCredentialAffinityKey,
173
170
  NativeEgressGrantExpiredError,
174
171
  NativeEgressNotDeclaredError,
175
172
  NativeIdleTimeoutError,
176
173
  NativeNetworkError,
177
174
  NativeProxyExpiredError,
178
- resolveNativeGatewayProxy,
179
- type NativeGatewayProxy,
180
- type NativeGatewayProxyResolutionInput,
181
- type NativeGatewayProxySkipReason,
182
- type NativeGatewayProxySynthesizer,
183
- type NativeGatewayProxySynthesisResult,
184
- type NativeGatewayProxySynthesisInput,
185
- type NativeNetworkClientOptions,
186
- type NativeNetworkErrorCode,
187
- type VendorCredentialLookup,
188
- type VendorCredentialResolver,
175
+ } from "./runtime/native-network-errors.js";
176
+ export type {
177
+ NativeGatewayProxy,
178
+ NativeGatewayProxyResolutionInput,
179
+ NativeGatewayProxySkipReason,
180
+ NativeGatewayProxySynthesizer,
181
+ NativeGatewayProxySynthesisResult,
182
+ NativeGatewayProxySynthesisInput,
183
+ NativeNetworkClientOptions,
184
+ NativeNetworkErrorCode,
185
+ VendorCredentialLookup,
186
+ VendorCredentialResolver,
189
187
  } from "./runtime/native-network.js";
190
188
  export {
191
189
  HttpRetryAfterPolicy,
@@ -10,7 +10,7 @@ import type {
10
10
  SttContext,
11
11
  } from "../types.js";
12
12
  import { createUnsupportedOcrClient } from "./ocr.js";
13
- import { createUnsupportedResolverClient } from "./resolver.js";
13
+ import { createUnsupportedResolverClient } from "./resolver-shared.js";
14
14
  import { createUnsupportedSttClient } from "./stt.js";
15
15
 
16
16
  function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
@@ -290,7 +290,13 @@ function formatExpression<T>(fn: string | (() => T)): string {
290
290
 
291
291
  function toLaunchOptions(options: BrowserClientOptions): LaunchOptions {
292
292
  return {
293
- args: options.extraArgs,
293
+ // `extraArgs` is optional, but playwright-extra's stealth evasions mutate
294
+ // `options.args` unguarded (navigator.webdriver does
295
+ // `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
296
+ // therefore crashes every stealth launch that omits extraArgs with
297
+ // "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
298
+ // Always hand the launcher a concrete array.
299
+ args: options.extraArgs ?? [],
294
300
  executablePath: options.executablePath,
295
301
  headless: options.headless ?? true,
296
302
  proxy: options.proxy ? { server: options.proxy } : undefined,
@@ -975,6 +981,20 @@ function getCdpExecutionContext(params: unknown): {
975
981
  };
976
982
  }
977
983
 
984
+ function getCdpDestroyedExecutionContextId(params: unknown): number | undefined {
985
+ if (!isRecord(params)) {
986
+ return undefined;
987
+ }
988
+
989
+ return typeof params.executionContextId === "number"
990
+ ? params.executionContextId
991
+ : undefined;
992
+ }
993
+
994
+ function isMissingExecutionContextError(error: unknown): boolean {
995
+ return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
996
+ }
997
+
978
998
  class CdpBrowserLocator implements BrowserLocator {
979
999
  constructor(
980
1000
  private readonly frame: {
@@ -1120,7 +1140,20 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1120
1140
  async evaluateInFrame<T>(frameId: string, fn: string | (() => T)): Promise<T> {
1121
1141
  await this.initialize();
1122
1142
  const contextId = await this.getFrameExecutionContextId(frameId);
1123
- return await this.evaluateWithContext<T>(fn, contextId);
1143
+ try {
1144
+ return await this.evaluateWithContext<T>(fn, contextId);
1145
+ } catch (error) {
1146
+ if (!isMissingExecutionContextError(error)) {
1147
+ throw error;
1148
+ }
1149
+
1150
+ if (this.frameExecutionContexts.get(frameId) === contextId) {
1151
+ this.frameExecutionContexts.delete(frameId);
1152
+ }
1153
+
1154
+ const refreshedContextId = await this.getFrameExecutionContextId(frameId);
1155
+ return await this.evaluateWithContext<T>(fn, refreshedContextId);
1156
+ }
1124
1157
  }
1125
1158
 
1126
1159
  async waitForSelectorInFrame(
@@ -1384,6 +1417,21 @@ class CdpPoolBrowserPage implements BrowserPageContract {
1384
1417
  this.frameExecutionContexts.set(context.frameId, context.id);
1385
1418
  }
1386
1419
  });
1420
+ this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
1421
+ const destroyedContextId = getCdpDestroyedExecutionContextId(params);
1422
+ if (destroyedContextId === undefined) {
1423
+ return;
1424
+ }
1425
+
1426
+ for (const [frameId, contextId] of this.frameExecutionContexts) {
1427
+ if (contextId === destroyedContextId) {
1428
+ this.frameExecutionContexts.delete(frameId);
1429
+ }
1430
+ }
1431
+ });
1432
+ this.pageClient.on("Runtime.executionContextsCleared", () => {
1433
+ this.frameExecutionContexts.clear();
1434
+ });
1387
1435
  await this.pageClient.send("Page.enable");
1388
1436
  await this.pageClient.send("Runtime.enable");
1389
1437
  this.initialized = true;