@apifuse/provider-sdk 2.2.0-beta.10 → 2.2.0-beta.12

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 (83) hide show
  1. package/AUTHORING.md +37 -0
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +18 -0
  4. package/bin/apifuse-pack-smoke.ts +14 -0
  5. package/bin/apifuse-pack-types.ts +11 -1
  6. package/dist/config/loader.d.ts +9 -1
  7. package/dist/config/loader.js +9 -0
  8. package/dist/errors.d.ts +5 -0
  9. package/dist/errors.js +15 -0
  10. package/dist/index.d.ts +3 -3
  11. package/dist/index.js +1 -1
  12. package/dist/runtime/stealth.js +113 -47
  13. package/dist/server/index.d.ts +1 -1
  14. package/dist/server/index.js +1 -1
  15. package/dist/server/serve.d.ts +98 -2
  16. package/dist/server/serve.js +485 -23
  17. package/dist/stateful/errors.d.ts +14 -0
  18. package/dist/stateful/errors.js +14 -0
  19. package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
  20. package/dist/stateful/http-provider-event-emitter.js +237 -0
  21. package/dist/stateful/http-session-owner-registry.d.ts +44 -0
  22. package/dist/stateful/http-session-owner-registry.js +210 -0
  23. package/dist/stateful/index.d.ts +18 -0
  24. package/dist/stateful/index.js +18 -0
  25. package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
  26. package/dist/stateful/provider-event-delivery-failures.js +43 -0
  27. package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
  28. package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
  29. package/dist/stateful/provider-event-pipeline.d.ts +50 -0
  30. package/dist/stateful/provider-event-pipeline.js +1 -0
  31. package/dist/stateful/provider-events.d.ts +101 -0
  32. package/dist/stateful/provider-events.js +289 -0
  33. package/dist/stateful/session-key.d.ts +15 -0
  34. package/dist/stateful/session-key.js +86 -0
  35. package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
  36. package/dist/stateful/stateful-provider-adapter-context.js +42 -0
  37. package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
  38. package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
  39. package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
  40. package/dist/stateful/stateful-provider-adapter.js +287 -0
  41. package/dist/stateful/stateful-provider-observability.d.ts +62 -0
  42. package/dist/stateful/stateful-provider-observability.js +161 -0
  43. package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
  44. package/dist/stateful/stateful-provider-owner-forwarder.js +207 -0
  45. package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
  46. package/dist/stateful/stateful-provider-runtime-context.js +60 -0
  47. package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
  48. package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
  49. package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
  50. package/dist/stateful/stateful-provider-session-routing.js +353 -0
  51. package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
  52. package/dist/stateful/stateful-provider-session-runtime.js +245 -0
  53. package/dist/stateful-signing.d.ts +18 -0
  54. package/dist/stateful-signing.js +27 -0
  55. package/dist/types.d.ts +39 -7
  56. package/package.json +7 -1
  57. package/src/config/loader.ts +22 -1
  58. package/src/errors.ts +15 -0
  59. package/src/index.ts +8 -1
  60. package/src/runtime/stealth.ts +127 -48
  61. package/src/server/index.ts +13 -1
  62. package/src/server/serve.ts +691 -25
  63. package/src/stateful/README.md +146 -0
  64. package/src/stateful/errors.ts +23 -0
  65. package/src/stateful/http-provider-event-emitter.ts +314 -0
  66. package/src/stateful/http-session-owner-registry.ts +306 -0
  67. package/src/stateful/index.ts +18 -0
  68. package/src/stateful/provider-event-delivery-failures.ts +80 -0
  69. package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
  70. package/src/stateful/provider-event-pipeline.ts +61 -0
  71. package/src/stateful/provider-events.ts +462 -0
  72. package/src/stateful/session-key.ts +111 -0
  73. package/src/stateful/stateful-provider-adapter-context.ts +59 -0
  74. package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
  75. package/src/stateful/stateful-provider-adapter.ts +562 -0
  76. package/src/stateful/stateful-provider-observability.ts +261 -0
  77. package/src/stateful/stateful-provider-owner-forwarder.ts +279 -0
  78. package/src/stateful/stateful-provider-runtime-context.ts +92 -0
  79. package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
  80. package/src/stateful/stateful-provider-session-routing.ts +555 -0
  81. package/src/stateful/stateful-provider-session-runtime.ts +403 -0
  82. package/src/stateful-signing.ts +46 -0
  83. package/src/types.ts +41 -7
package/AUTHORING.md CHANGED
@@ -517,6 +517,43 @@ const credentialsAuth = defineCredentialsAuth({
517
517
  `bunx playwright install chromium`, or set
518
518
  `APIFUSE__CDP_POOL__URL` for remote browser debugging.
519
519
 
520
+ ### Persisting stealth session cookies
521
+
522
+ Persist `session.cookies.serialize()` as JSON when an authenticated session must
523
+ survive a restart or move to another replica. The returned
524
+ `StealthCookieStoreV1` has an explicit version and retains every cookie together
525
+ with its Domain, Path, Secure, expiry, host-only, and other cookie attributes.
526
+ Restore it with `session.cookies.deserialize()`. Unsupported future versions
527
+ fail explicitly instead of being accepted as a partial cookie jar.
528
+
529
+ Credential values are strings, so stringify the store at the credential
530
+ boundary and parse it when rebuilding the session:
531
+
532
+ ```ts
533
+ // After login (including any redirects across sibling hosts):
534
+ const result = await session.redirects.run({ url: loginUrl });
535
+ return {
536
+ credential: {
537
+ cookieStore: JSON.stringify(result.cookieStore),
538
+ },
539
+ };
540
+
541
+ // In a later operation or replica:
542
+ const persisted = ctx.credential.get("cookieStore");
543
+ if (persisted) {
544
+ session.cookies.deserialize(JSON.parse(persisted));
545
+ }
546
+ ```
547
+
548
+ `snapshot()` and `restore()` remain only for backward compatibility with flat
549
+ `Record<string, string>` credentials. `snapshot()` enumerates cookies across all
550
+ hosts and paths, but the flat shape is inherently lossy: duplicate names
551
+ collapse and Domain, Path, Secure, expiry, and host-only attributes cannot be
552
+ represented. `restore()` therefore recreates host-only `Path=/` cookies on the
553
+ session base origin. Do not use the flat form for new persistence code. Cookie
554
+ headers remain origin-filtered: use `toHeader(url)` for a particular request and
555
+ never build a request header from serialized or snapshotted persistence data.
556
+
520
557
  ### Running the pre-submission report
521
558
 
522
559
  ```bash
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.12
4
+
5
+ - Release candidate for main commit c66789c4745c72fc94ad3c10b3e0d7e5ed83fd25.
6
+
7
+ ## 2.2.0-beta.11
8
+
9
+ - Release candidate for main commit f6f739bd5265afe714bbace9900edc2695fcf826.
10
+
3
11
  ## 2.2.0-beta.10
4
12
 
5
13
  - Release candidate for main commit c41bd919739e0293ae8fa4d72a8a32f034cef4b8.
package/README.md CHANGED
@@ -146,6 +146,24 @@ the bad request path; provider/runtime failures include `code`, `message`, and
146
146
  `profile` such as `chrome-146`; do not tune JA3, HTTP/2 SETTINGS, or
147
147
  pseudo-header order in provider code. Chrome/Firefox-style profiles are
148
148
  supported; use `ctx.browser` when Safari-specific behavior is required.
149
+ - **Proxy URLs for non-stealth consumers**: use `resolveProxy()` when a
150
+ provider-owned client outside `ctx.stealth` needs the provider's proxy, such
151
+ as a CAPTCHA solver that must use matching egress. Pass the provider proxy
152
+ policy used by the provider and consume the returned `url`; the SDK owns
153
+ vendor selection, allocation, failover, and URL formats. Never call proxy
154
+ allocator APIs or hardcode proxy vendor hostnames in provider code.
155
+
156
+ ```ts
157
+ import { resolveProxy } from "@apifuse/provider-sdk"
158
+
159
+ const resolvedProxy = await resolveProxy({
160
+ proxyPolicy,
161
+ affinityKey: connectionId,
162
+ })
163
+
164
+ if (!resolvedProxy.url) throw new Error("This login requires proxy egress")
165
+ const captchaTask = { proxy: resolvedProxy.url }
166
+ ```
149
167
  - **Browser providers**: for TypeScript Providers use `runtime: "browser"` plus
150
168
  `browser.engine: "playwright-stealth"`; `nodriver` is a Python-runtime path.
151
169
  Install local browser assets with `bunx playwright install chromium` when
@@ -59,6 +59,20 @@ try {
59
59
  );
60
60
 
61
61
  run("bun", ["install"], consumerDir);
62
+ run(
63
+ "bun",
64
+ [
65
+ "--eval",
66
+ [
67
+ 'import { resolveProxy } from "@apifuse/provider-sdk";',
68
+ 'if (typeof resolveProxy !== "function") throw new Error("resolveProxy is not exported");',
69
+ 'const resolved = await resolveProxy({ proxy: "http://127.0.0.1:8080" });',
70
+ 'if (resolved.url !== "http://127.0.0.1:8080") throw new Error("resolveProxy returned the wrong URL");',
71
+ 'console.log("packed root resolveProxy export OK");',
72
+ ].join("\n"),
73
+ ],
74
+ consumerDir,
75
+ );
62
76
 
63
77
  const cliBin = join(consumerDir, "node_modules", ".bin", "apifuse");
64
78
  if (!existsSync(cliBin)) {
@@ -123,7 +123,8 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
123
123
  writeFileSync(
124
124
  join(consumerDir, "consumer.ts"),
125
125
  [
126
- 'import { ProviderError, SessionExpiredError, z } from "@apifuse/provider-sdk";',
126
+ 'import { ProviderError, resolveProxy, SessionExpiredError, z } from "@apifuse/provider-sdk";',
127
+ 'import type { ProxyProtocol, ProxyResolutionOptions, ProxyResolutionSource, ProxyVendorName, ResolvedProxyConfig } from "@apifuse/provider-sdk";',
127
128
  'import { defineCredentialsAuth } from "@apifuse/provider-sdk/provider";',
128
129
  'import { extractProviderContract } from "@apifuse/provider-sdk/contract";',
129
130
  'import { AUTH_TURN_SCHEMA } from "@apifuse/provider-sdk/auth-turn";',
@@ -143,6 +144,11 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
143
144
  "// Re-exported zod must keep .refine() callback parameter inference.",
144
145
  "const refined = z.object({ shopId: z.string() }).refine((value) => value.shopId.length > 0);",
145
146
  'const refinedString = z.string().refine((value) => value.startsWith("tabelog:"));',
147
+ 'const proxyOptions: ProxyResolutionOptions = { proxyPolicy: { mode: "disabled" } };',
148
+ 'const proxyProtocol: ProxyProtocol = "http";',
149
+ "const proxyResult: Promise<ResolvedProxyConfig> = resolveProxy(proxyOptions);",
150
+ 'const proxySource: ProxyResolutionSource = "smartproxy-allocator";',
151
+ 'const proxyVendor: ProxyVendorName = "smartproxy";',
146
152
  "",
147
153
  "export const witnesses = {",
148
154
  " inheritedName,",
@@ -152,6 +158,10 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
152
158
  " sessionExpired,",
153
159
  " refined,",
154
160
  " refinedString,",
161
+ " proxyResult,",
162
+ " proxyProtocol,",
163
+ " proxySource,",
164
+ " proxyVendor,",
155
165
  " defineCredentialsAuth,",
156
166
  " extractProviderContract,",
157
167
  " AUTH_TURN_SCHEMA,",
@@ -112,10 +112,13 @@ export type ProxyTelemetrySink = {
112
112
  recordProxyAttempt?(event: ProxyAttemptTelemetryEvent): void;
113
113
  recordProxyVendorFailover?(event: ProxyVendorFailoverTelemetryEvent): void;
114
114
  };
115
+ export type ProxyResolutionSource = "explicit" | "env" | "config" | "smartproxy-allocator" | "nodemaven-gateway";
115
116
  export type ResolvedProxyConfig = {
116
117
  shouldWarn: boolean;
117
118
  url?: string;
118
- source?: "explicit" | "env" | "config" | "smartproxy-allocator" | "nodemaven-gateway";
119
+ /** SDK-native vendor that supplied the URL, when applicable. */
120
+ vendor?: ProxyVendorName;
121
+ source?: ProxyResolutionSource;
119
122
  protocol?: ProxyProtocol;
120
123
  diagnostics?: Record<string, string | number | boolean>;
121
124
  };
@@ -142,6 +145,11 @@ export declare function __setProxyRedisForTests(redis: ProxyRedisClient | undefi
142
145
  export declare function __setSmartproxyAllocatorDeadlineMsForTests(deadlineMs: number | undefined): void;
143
146
  export declare function resolveProxyConfig(options?: ProxyResolutionOptions): ResolvedProxyConfig;
144
147
  export declare function resolveProxyConfigAsync(options?: ProxyResolutionOptions): Promise<ResolvedProxyConfig>;
148
+ /**
149
+ * Resolve the proxy URL for a provider-owned consumer such as a CAPTCHA solver.
150
+ * Vendor allocation and failover remain owned by the SDK.
151
+ */
152
+ export declare function resolveProxy(options?: ProxyResolutionOptions): Promise<ResolvedProxyConfig>;
145
153
  /**
146
154
  * Guard the No-MITM invariant: a resolved proxy URL must use a tunnelling scheme
147
155
  * (http CONNECT or socks5) so the client TLS handshake reaches the origin
@@ -400,6 +400,15 @@ export async function resolveProxyConfigAsync(options = {}) {
400
400
  }
401
401
  return { shouldWarn: true };
402
402
  }
403
+ /**
404
+ * Resolve the proxy URL for a provider-owned consumer such as a CAPTCHA solver.
405
+ * Vendor allocation and failover remain owned by the SDK.
406
+ */
407
+ export async function resolveProxy(options = {}) {
408
+ const resolved = await resolveProxyConfigAsync(options);
409
+ const vendor = vendorFromResolvedSource(resolved.source);
410
+ return vendor ? { ...resolved, vendor } : resolved;
411
+ }
403
412
  /**
404
413
  * Each vendor's default egress protocol, chosen from live KR benchmarks. HTTP
405
414
  * CONNECT wins for nodemaven (socks5 adds ~500ms through the gateway) and ties
package/dist/errors.d.ts CHANGED
@@ -17,6 +17,11 @@ export declare class ProviderError extends Error {
17
17
  export declare class SDKError extends ProviderError {
18
18
  constructor(message: string, options?: ProviderErrorOptions);
19
19
  }
20
+ /** Raised when persisted stealth cookies use a store version this SDK cannot read. */
21
+ export declare class StealthCookieStoreVersionError extends SDKError {
22
+ readonly version: unknown;
23
+ constructor(version: unknown);
24
+ }
20
25
  export declare class AuthError extends ProviderError {
21
26
  constructor(message: string, options?: ProviderErrorOptions);
22
27
  }
package/dist/errors.js CHANGED
@@ -57,6 +57,21 @@ export class SDKError extends ProviderError {
57
57
  this.name = "SDKError";
58
58
  }
59
59
  }
60
+ /** Raised when persisted stealth cookies use a store version this SDK cannot read. */
61
+ export class StealthCookieStoreVersionError extends SDKError {
62
+ version;
63
+ constructor(version) {
64
+ const displayedVersion = typeof version === "string" || typeof version === "number"
65
+ ? String(version)
66
+ : "missing or invalid";
67
+ super(`Unsupported stealth cookie store version: ${displayedVersion}`, {
68
+ code: "unsupported_stealth_cookie_store_version",
69
+ details: { receivedVersion: version, supportedVersions: [1] },
70
+ });
71
+ this.version = version;
72
+ this.name = "StealthCookieStoreVersionError";
73
+ }
74
+ }
60
75
  export class AuthError extends ProviderError {
61
76
  constructor(message, options) {
62
77
  super(message, options);
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export * from "./auth.js";
2
2
  export * from "./ceremonies/index.js";
3
3
  export * from "./choice-token.js";
4
- export type { ApiFuseConfig, BrowserConfig, ProxyConfig, SessionConfig, } from "./config/loader.js";
5
- export { defineConfig, loadApiFuseConfig } from "./config/loader.js";
4
+ export type { ApiFuseConfig, BrowserConfig, ProxyConfig, ProxyProtocol, ProxyResolutionOptions, ProxyResolutionSource, ProxyVendorName, ResolvedProxyConfig, SessionConfig, } from "./config/loader.js";
5
+ export { defineConfig, loadApiFuseConfig, resolveProxy } from "./config/loader.js";
6
6
  export { canonicalJson, digestProviderContract, extractProviderContract, type JsonPrimitive, type JsonValue, PROVIDER_CONTRACT_SCHEMA_VERSION, type ProviderContractOperation, type ProviderContractSnapshot, } from "./contract.js";
7
7
  export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, type ProviderConfig, } from "./define.js";
8
8
  export type { DevServerOptions } from "./dev.js";
@@ -36,7 +36,7 @@ export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SEN
36
36
  export { createServerApp, type ServeOptions, serve } from "./server/index.js";
37
37
  export { getStealthProfile, listStealthProfiles } from "./stealth/profiles.js";
38
38
  export * from "./stream.js";
39
- export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
39
+ export type { ApiFuseResponse, AuthConfig, AuthContext, AuthFlowDefinition, AuthFlowInputHandler, AuthFlowStartHandler, AuthMode, AuthTurn, Bcp47Locale, BrowserEngine, BrowserOptions, BrowserResourceBody, BrowserResourceDecision, BrowserResourceMethod, BrowserResourcePolicy, BrowserResourceRequest, BrowserResourceRoute, ConnectionMode, ContextDeclaration, CookieJar, ContextScratchpad, CredentialContext, CredentialDeclaration, E164PhoneNumber, EnvContext, FlowContext, FlowContextStore, HealthCheckAssertionContext, HealthCheckCase, HealthCheckCaseResult, HealthCheckSuite, HealthCheckUnsupported, HealthJourneyDefinition, HealthJourneyEventContext, HealthJourneyGatewayContext, HealthJourneyJournalContext, HealthJourneyManualTriggerPolicy, HealthJourneyRunContext, HealthJourneyRunResult, HealthJourneySchedule, HealthScheduleRandomization, HealthJourneySmsContext, HealthJourneyStep, HttpClient, HttpMethod, HttpResponse, HttpRetryOptions, HttpRetrySummary, HttpStreamResponse, IanaTimeZone, InferSchemaOutput, Iso3166Alpha2CountryCode, Iso4217CurrencyCode, Iso8601Duration, OperationAnnotations, OperationApprovalPolicy, OperationContractMetadata, OperationDefinition, OperationDeprecationMetadata, OperationDocMeta, OperationErrorCode, OperationHandlerResult, OperationInputExample, OperationLifecycle, OperationObservabilityConfig, OperationObservabilitySensitiveConfig, OperationRelationships, OperationRiskClass, OperationSensitivePath, OperationToolRouterMetadata, OperationTransport, OperationTransportKind, ProbeInterval, ProviderAccessConfig, ProviderAccessVisibility, ProviderCache, ProviderCacheGetOrSetOptions, ProviderCacheKeyOptions, ProviderCacheLookupMeta, ProviderCacheResponseMeta, ProviderCacheResult, ProviderChoiceBindingOptions, ProviderChoiceContext, ProviderChoiceIssueOptions, ProviderChoiceParseOptions, ProviderContext, ProviderDefinition, ProviderDeploymentOverrides, ProviderHealthMonitorConfig, ProviderHealthProbeConfig, ProviderLocale, ProviderLocaleKey, ProviderLocaleKeyInput, ProviderLogoProfile, ProviderLogoSource, ProviderMeta, ProviderProxyConfig, ProviderProxyMode, ProviderProxyPolicy, ProviderProxyProvider, ProviderProxySessionAffinity, ProviderPublicConnectionMode, ProviderPublicProfile, ProviderReviewed, ProviderRuntimeState, ProviderSecretDeclaration, ProviderStateDurationString, ProviderStateNamespace, ProviderStreamEvent, ProviderSttConfig, ProviderSttMode, ProviderSupportLevel, RequestOptions, Rfc3339Instant, SchemaLike, SmsOrigin, SmsOtpExtractionPattern, SmsOtpMatcherDefinition, SseMessage, StandardSchemaV1, StateCasResult, StateNamespaceOptions, StateValue, StateWriteOptions, StealthClient, StealthCookieStore, StealthCookieStoreV1, StealthFetchOptions, StealthPlatform, StealthProfile, StealthRedirectHop, StealthRedirectRunOptions, StealthRedirectRunResult, StealthResponse, StealthSession, StealthSessionCookies, SttAudioInput, SttContext, SttPromptPolicy, SttSegment, SttTranscribeMode, SttTranscribeRequest, SttTranscript, SttUnsupportedOptionPolicy, SttUsage, SttVerificationCodeOptions, SttWarning, TraceConfig, TraceSpan, VerificationCodeCandidate, VerificationCodeCandidateSource, VerificationCodeExtractionResult, } from "./types.js";
40
40
  export { DEFAULT_OPERATION_TRANSPORT, HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, PROBE_INTERVALS, STREAM_CHUNK_BYTES_MAX, STREAM_CHUNK_BYTES_MIN, STREAM_HEARTBEAT_MS_MAX, STREAM_HEARTBEAT_MS_MIN, STREAM_IDLE_TIMEOUT_MS_MAX, STREAM_IDLE_TIMEOUT_MS_MIN, STREAM_MAX_DURATION_MS_MAX, STREAM_MAX_DURATION_MS_MIN, } from "./types.js";
41
41
  export * from "./utils/date.js";
42
42
  export * from "./utils/parse.js";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  export * from "./auth.js";
3
3
  export * from "./ceremonies/index.js";
4
4
  export * from "./choice-token.js";
5
- export { defineConfig, loadApiFuseConfig } from "./config/loader.js";
5
+ export { defineConfig, loadApiFuseConfig, resolveProxy } from "./config/loader.js";
6
6
  export { canonicalJson, digestProviderContract, extractProviderContract, PROVIDER_CONTRACT_SCHEMA_VERSION, } from "./contract.js";
7
7
  export { centered, delayed, defineHealthJourney, defineOperation, defineProvider, defineSmsOtpMatcher, defineStreamOperation, every, } from "./define.js";
8
8
  export { createDevServer, startDevServer } from "./dev.js";
@@ -1,7 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Impit } from "impit";
3
- import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, policyResolvesRegistryVendorChain, ProxyResolutionError, resolvePolicyProxyPoolSpan, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, vendorFromResolvedSource, } from "../config/loader.js";
4
- import { SDKError, TransportError } from "../errors.js";
3
+ import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
4
+ import { DEFAULT_SMARTPROXY_POOL_SIZE, invalidateProxyResolutionCacheAsync, ProxyResolutionError, policyResolvesRegistryVendorChain, resolvePolicyProxyPoolSpan, resolvePolicyTransportAttemptCap, resolveProxyConfigAsync, vendorFromResolvedSource, } from "../config/loader.js";
5
+ import { SDKError, StealthCookieStoreVersionError, TransportError } from "../errors.js";
5
6
  import { getStealthProfile } from "../stealth/profiles.js";
6
7
  import { createProxyAuthIpDeniedError, createProxyEdgeAuthRejectedError, createProxyEdgeTlsRejectedError, createProxyPoolExhaustedError, createProxyPoolStaleError, isProxyAuthIpDeniedMessage, isProxyEdgeAuthRejectedMessage, isProxyEdgeTlsRejectedResponse, isProxyPoolRefreshableError, isProxyPoolStaleMessage, isProxyPoolStaleStatus, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_STALE_CODE, } from "./proxy-errors.js";
7
8
  import { computeProxyAttemptIndex, computeProxyTransportRetryDelayMs, createDefaultProxyTransportRetryOptions, normalizeProxyTransportRetryOptions, shouldRetryProxyTransportAttempt, validateUnsafeProxyTransportRetryMethods, } from "./proxy-retry-policy.js";
@@ -49,69 +50,128 @@ const FIREFOX_IMPIT_BY_MAJOR = {
49
50
  function isRecord(value) {
50
51
  return typeof value === "object" && value !== null;
51
52
  }
53
+ const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
52
54
  class CookieJarImpl {
53
55
  cookies;
54
- constructor(cookieStrings) {
55
- this.cookies = {};
56
+ defaultUrl;
57
+ constructor(cookieStrings, defaultUrl = LEGACY_COOKIE_ORIGIN) {
58
+ this.cookies = new ToughCookieJar(undefined, {
59
+ allowSecureOnLocal: false,
60
+ rejectPublicSuffixes: true,
61
+ });
62
+ this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
56
63
  this.setFromCookieStrings(cookieStrings);
57
64
  }
58
- setFromCookieStrings(cookieStrings) {
65
+ /**
66
+ * URL-less legacy operations are scoped to this jar's default URL. Session
67
+ * jars use the client's base URL and response jars use the response URL. A
68
+ * flat restore has no attributes to recover, so it creates host-only Path=/
69
+ * cookies for that default URL instead of making them visible to every host.
70
+ */
71
+ setFromCookieStrings(cookieStrings, url = this.defaultUrl) {
72
+ const cookieUrl = this.normalizeUrl(url);
73
+ if (!cookieUrl)
74
+ return;
59
75
  for (const cookieString of cookieStrings) {
60
- const [nameValue] = cookieString.split(";");
61
- if (!nameValue) {
62
- continue;
63
- }
64
- const separatorIndex = nameValue.indexOf("=");
65
- if (separatorIndex === -1) {
66
- continue;
67
- }
68
- const name = nameValue.slice(0, separatorIndex).trim();
69
- const value = nameValue.slice(separatorIndex + 1).trim();
70
- if (name)
71
- this.cookies[name] = value;
76
+ this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
72
77
  }
73
78
  }
74
- get(name) {
75
- return this.cookies[name];
79
+ get(name, url) {
80
+ return this.getAll(url)[name];
76
81
  }
77
- getAll() {
78
- return { ...this.cookies };
82
+ getAll(url) {
83
+ return Object.fromEntries(this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => [cookie.key, cookie.value]));
79
84
  }
80
- has(name) {
81
- return Object.hasOwn(this.cookies, name);
85
+ has(name, url) {
86
+ return Object.hasOwn(this.getAll(url), name);
82
87
  }
83
- toString() {
84
- return Object.entries(this.cookies)
85
- .map(([name, value]) => `${name}=${value}`)
88
+ toString(url) {
89
+ return this.getUniqueCookies(url ?? this.defaultUrl)
90
+ .map((cookie) => cookie.cookieString())
86
91
  .join("; ");
87
92
  }
88
- toHeader() {
89
- return this.toString();
93
+ toHeader(url) {
94
+ return this.toString(url);
90
95
  }
91
96
  snapshot() {
92
- return this.getAll();
97
+ // This compatibility view deliberately enumerates the serialized store,
98
+ // not getAll(defaultUrl): persistence must include sibling hosts and paths.
99
+ // Duplicate names still collapse because a flat map cannot represent them.
100
+ const entries = [];
101
+ for (const cookie of this.serialize().jar.cookies) {
102
+ if (typeof cookie.key === "string" && typeof cookie.value === "string" && cookie.key) {
103
+ entries.push([cookie.key, cookie.value]);
104
+ }
105
+ }
106
+ return Object.fromEntries(entries);
93
107
  }
94
108
  restore(cookies) {
95
109
  this.clear();
96
110
  for (const [name, value] of Object.entries(cookies)) {
97
- if (name)
98
- this.cookies[name] = value;
111
+ if (!name)
112
+ continue;
113
+ this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
114
+ ignoreError: true,
115
+ });
99
116
  }
100
117
  }
101
- clear() {
102
- for (const name of Object.keys(this.cookies)) {
103
- delete this.cookies[name];
118
+ serialize() {
119
+ const jar = this.cookies.serializeSync();
120
+ if (!jar) {
121
+ throw new SDKError("Stealth cookie store could not be serialized", {
122
+ code: "stealth_cookie_store_serialize_failed",
123
+ });
104
124
  }
125
+ return { version: 1, jar };
105
126
  }
106
- find(predicate) {
107
- for (const [name, value] of Object.entries(this.cookies)) {
108
- const cookie = `${name}=${value}`;
109
- if (predicate(cookie)) {
110
- return cookie;
127
+ deserialize(state) {
128
+ const version = isRecord(state) ? state.version : undefined;
129
+ if (version !== 1) {
130
+ throw new StealthCookieStoreVersionError(version);
131
+ }
132
+ // Deserialize into a new jar first so invalid state cannot partially clear
133
+ // or replace a live session. tough-cookie restores the cookie attributes and
134
+ // matching semantics represented in its own serialized format.
135
+ const restored = ToughCookieJar.deserializeSync(state.jar);
136
+ // tough-cookie 6 does not include this option in serializeSync(). Preserve
137
+ // the SDK's stricter setting across restoration.
138
+ Reflect.set(restored, "allowSecureOnLocal", false);
139
+ this.cookies = restored;
140
+ }
141
+ clear() {
142
+ this.cookies.removeAllCookiesSync();
143
+ }
144
+ find(predicate, url) {
145
+ for (const cookie of this.getUniqueCookies(url ?? this.defaultUrl)) {
146
+ const cookieString = cookie.cookieString();
147
+ if (predicate(cookieString)) {
148
+ return cookieString;
111
149
  }
112
150
  }
113
151
  return undefined;
114
152
  }
153
+ normalizeUrl(url) {
154
+ try {
155
+ return new URL(url).toString();
156
+ }
157
+ catch {
158
+ return undefined;
159
+ }
160
+ }
161
+ getUniqueCookies(url) {
162
+ const cookieUrl = this.normalizeUrl(url);
163
+ if (!cookieUrl)
164
+ return [];
165
+ // tough-cookie returns longer (more-specific) paths first. Keeping the
166
+ // first cookie for each name prevents ambiguous duplicate-name headers.
167
+ const names = new Set();
168
+ return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
169
+ if (names.has(cookie.key))
170
+ return false;
171
+ names.add(cookie.key);
172
+ return true;
173
+ });
174
+ }
115
175
  }
116
176
  function closestImpitBrowser(major, candidates) {
117
177
  let closestMajor;
@@ -175,13 +235,13 @@ function hasOwn(object, key) {
175
235
  }
176
236
  function toImpitCookieJar(cookieJar) {
177
237
  return {
178
- setCookie(cookie, _url, cb) {
179
- cookieJar.setFromCookieStrings([cookie]);
238
+ setCookie(cookie, url, cb) {
239
+ cookieJar.setFromCookieStrings([cookie], url);
180
240
  if (typeof cb === "function")
181
241
  cb();
182
242
  },
183
- getCookieString(_url) {
184
- return cookieJar.toString();
243
+ getCookieString(url) {
244
+ return cookieJar.toHeader(url);
185
245
  },
186
246
  };
187
247
  }
@@ -235,7 +295,7 @@ function splitCombinedSetCookieHeader(headerValue) {
235
295
  }
236
296
  export async function normalizeResponse(response, requestUrl) {
237
297
  const headers = Object.fromEntries(response.headers.entries());
238
- const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers));
298
+ const cookies = new CookieJarImpl(setCookieHeadersFromResponse(response.headers), response.url ?? requestUrl);
239
299
  const bodyBytes = await response.arrayBuffer();
240
300
  const body = new TextDecoder().decode(bodyBytes);
241
301
  return {
@@ -441,7 +501,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
441
501
  let closed = false;
442
502
  let hasWarnedMissingProxy = false;
443
503
  const warn = clientOptions.warn ?? console.warn;
444
- const cookieJar = new CookieJarImpl([]);
504
+ const cookieJar = new CookieJarImpl([], baseUrl);
445
505
  const impitCookieJar = toImpitCookieJar(cookieJar);
446
506
  function getClient(profileName, proxyUrl, ignoreTlsErrors) {
447
507
  if (closed) {
@@ -580,7 +640,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
580
640
  const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
581
641
  const headers = { ...(options.headers ?? {}) };
582
642
  if (!hasHeader(headers, "Cookie")) {
583
- const cookieHeader = cookieJar.toString();
643
+ const cookieHeader = cookieJar.toHeader(requestUrl);
584
644
  if (cookieHeader)
585
645
  headers.Cookie = cookieHeader;
586
646
  }
@@ -595,7 +655,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
595
655
  }
596
656
  const response = await getClient(profileName, proxy, ignoreTlsErrors).fetch(requestUrl, requestInit);
597
657
  const normalized = await normalizeResponse(response, requestUrl);
598
- cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers));
658
+ cookieJar.setFromCookieStrings(setCookieHeadersFromResponse(response.headers), response.url ?? requestUrl);
599
659
  if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
600
660
  throw createProxyConnectFailureError(normalized.body);
601
661
  }
@@ -735,6 +795,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
735
795
  hops,
736
796
  reason: "completed",
737
797
  cookies: cookieJar.snapshot(),
798
+ cookieStore: cookieJar.serialize(),
738
799
  };
739
800
  }
740
801
  const location = locationHeader(response.headers);
@@ -755,6 +816,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
755
816
  hops,
756
817
  reason: "stopped",
757
818
  cookies: cookieJar.snapshot(),
819
+ cookieStore: cookieJar.serialize(),
758
820
  };
759
821
  }
760
822
  if (!nextUrl) {
@@ -763,6 +825,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
763
825
  hops,
764
826
  reason: "missing_location",
765
827
  cookies: cookieJar.snapshot(),
828
+ cookieStore: cookieJar.serialize(),
766
829
  };
767
830
  }
768
831
  if (hops.length > maxHops) {
@@ -771,6 +834,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
771
834
  hops,
772
835
  reason: "max_hops",
773
836
  cookies: cookieJar.snapshot(),
837
+ cookieStore: cookieJar.serialize(),
774
838
  };
775
839
  }
776
840
  const nextMethod = nextRedirectMethod(response.status, method);
@@ -783,6 +847,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
783
847
  hops,
784
848
  reason: "loop",
785
849
  cookies: cookieJar.snapshot(),
850
+ cookieStore: cookieJar.serialize(),
786
851
  };
787
852
  }
788
853
  method = nextMethod;
@@ -803,6 +868,7 @@ function createSessionFetcher(baseUrl, defaultProfile, clientOptions) {
803
868
  hops,
804
869
  reason: "max_hops",
805
870
  cookies: cookieJar.snapshot(),
871
+ cookieStore: cookieJar.serialize(),
806
872
  };
807
873
  },
808
874
  },
@@ -1,4 +1,4 @@
1
- export { createServerApp, type ServeOptions, serve } from "./serve.js";
1
+ export { createServerApp, type ProviderServerCloseOptions, type ProviderServerHandle, type ProviderServerLogEvent, type ProviderServerLogger, type ProviderServerOperationExecutor, type ProviderServerOperationExecutorInput, type ProviderServerOptions, type ProviderServerStatefulForwardEnvelope, type ServeOptions, serve, } from "./serve.js";
2
2
  export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, type SelfTestAppOptions, type SelfTestAuthFlowInvoke, type SelfTestAuthFlowRoute, type SelfTestCaseResult, type SelfTestCaseStatus, type SelfTestOperationInvoke, type SelfTestRequest, SelfTestRequestSchema, type SelfTestResponse, } from "./self-test.js";
3
3
  export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens.js";
4
4
  export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction.js";
@@ -1,4 +1,4 @@
1
- export { createServerApp, serve } from "./serve.js";
1
+ export { createServerApp, serve, } from "./serve.js";
2
2
  export { computeSelfTestPlanDigest, createSelfTestApp, createSelfTestAuthFlowInvoke, createSelfTestInvoke, DEFAULT_SELF_TEST_REQUEST_BUDGET_MS, isSelfTestReadOnlyOperation, PROVIDER_RUNTIME_SELF_TEST_REQUEST_BUDGET_MS_ENV, resolveSelfTestPort, SELF_TEST_AUTH_FLOW_MULTI_TURN_SKIP_REASON, SELF_TEST_AUTH_FLOW_REJECTED_SKIP_REASON, SELF_TEST_HEALTHZ_PATH, SELF_TEST_PATH, SELF_TEST_SCHEMA_VERSION, SelfTestRequestSchema, } from "./self-test.js";
3
3
  export { resolveHealthCheckInputDateTokens } from "./self-test-input-tokens.js";
4
4
  export { collectSelfTestSensitiveValues, redactSelfTestText, SELF_TEST_MAX_TEXT_LENGTH, SELF_TEST_REDACTED_PLACEHOLDER, } from "./self-test-redaction.js";