@apifuse/provider-sdk 2.2.0-beta.24 → 2.2.0-beta.26

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 (66) hide show
  1. package/AUTHORING.md +7 -6
  2. package/CHANGELOG.md +9 -1
  3. package/README.md +3 -3
  4. package/bin/apifuse-check.ts +62 -3
  5. package/bin/apifuse-pack-check.ts +8 -2
  6. package/bin/apifuse-pack-smoke.ts +43 -2
  7. package/bin/apifuse-pack-types.ts +58 -0
  8. package/bin/apifuse-submit-check.ts +15 -2
  9. package/dist/auth.js +29 -0
  10. package/dist/cli/templates/provider/README.md.tpl +4 -4
  11. package/dist/contract-serialization.d.ts +20 -1
  12. package/dist/contract-serialization.js +583 -8
  13. package/dist/contract.d.ts +2 -0
  14. package/dist/contract.js +9 -5
  15. package/dist/declaration-validation.d.ts +23 -0
  16. package/dist/declaration-validation.js +159 -0
  17. package/dist/define.d.ts +1 -1
  18. package/dist/define.js +13 -2
  19. package/dist/index.d.ts +4 -3
  20. package/dist/index.js +3 -3
  21. package/dist/lint.js +85 -3
  22. package/dist/provider.d.ts +1 -1
  23. package/dist/provider.js +1 -1
  24. package/dist/runtime/cache.d.ts +1 -0
  25. package/dist/runtime/cache.js +169 -15
  26. package/dist/runtime/resolver-vendors/bindings.d.ts +42 -2
  27. package/dist/runtime/resolver-vendors/bindings.js +31 -6
  28. package/dist/runtime/resolver-vendors/browser.d.ts +3 -7
  29. package/dist/runtime/resolver-vendors/browser.js +7 -22
  30. package/dist/runtime/resolver-vendors/hosts.d.ts +2 -0
  31. package/dist/runtime/resolver-vendors/hosts.js +33 -0
  32. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +23 -0
  33. package/dist/runtime/resolver-vendors/twocaptcha.js +264 -0
  34. package/dist/runtime/resolver-vendors/types.d.ts +44 -3
  35. package/dist/runtime/resolver-vendors/types.js +10 -0
  36. package/dist/runtime/resolver.d.ts +17 -2
  37. package/dist/runtime/resolver.js +237 -15
  38. package/dist/runtime/stealth.d.ts +26 -4
  39. package/dist/runtime/stealth.js +224 -114
  40. package/dist/schema.d.ts +63 -0
  41. package/dist/schema.js +808 -8
  42. package/dist/server/serve.js +8 -0
  43. package/dist/stealth/profiles.js +16 -7
  44. package/dist/types.d.ts +37 -4
  45. package/package.json +2 -2
  46. package/src/auth.ts +40 -0
  47. package/src/cli/templates/provider/README.md.tpl +4 -4
  48. package/src/contract-serialization.ts +857 -8
  49. package/src/contract.ts +16 -5
  50. package/src/declaration-validation.ts +202 -0
  51. package/src/define.ts +23 -2
  52. package/src/index.ts +13 -0
  53. package/src/lint.ts +98 -3
  54. package/src/provider.ts +10 -0
  55. package/src/runtime/cache.ts +189 -14
  56. package/src/runtime/resolver-vendors/bindings.ts +40 -15
  57. package/src/runtime/resolver-vendors/browser.ts +9 -31
  58. package/src/runtime/resolver-vendors/hosts.ts +38 -0
  59. package/src/runtime/resolver-vendors/twocaptcha.ts +366 -0
  60. package/src/runtime/resolver-vendors/types.ts +54 -0
  61. package/src/runtime/resolver.ts +304 -24
  62. package/src/runtime/stealth.ts +317 -136
  63. package/src/schema.ts +1060 -9
  64. package/src/server/serve.ts +8 -0
  65. package/src/stealth/profiles.ts +17 -7
  66. package/src/types.ts +39 -6
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { Hono } from "hono";
4
4
  import { z } from "zod";
5
5
  import { AuthAbortError, createAuthFlowHelpers } from "../auth.js";
6
+ import { validateFailClosedDeclaration } from "../declaration-validation.js";
6
7
  import { SDK_OWNED_PROVIDER_ERROR_CODES, SDK_RUNTIME_OWNED_ERROR_CODES, SDK_STATUS_MAPPED_PROVIDER_ERROR_CODES, } from "../error-resolution.js";
7
8
  import { AuthError, isProviderError, isSessionExpiredError, isTransportError, isValidationError, ProviderError, } from "../errors.js";
8
9
  import { loadProviderLocaleCatalogs, localizeAuthTurn, } from "../i18n/catalog.js";
@@ -1298,6 +1299,11 @@ function parseStatefulForwardingEnvelope(rawBody) {
1298
1299
  });
1299
1300
  }
1300
1301
  export function createServerApp(provider, options = {}) {
1302
+ // Fail-closed validation runs here rather than only in serve(): createServerApp
1303
+ // is a public export, so a cast-bypassed declaration would otherwise reach the
1304
+ // request path unvalidated. serve() calls into this function, so validating
1305
+ // here covers both entry points exactly once per app construction.
1306
+ validateFailClosedDeclaration(provider);
1301
1307
  validateStatefulServerConfig(options);
1302
1308
  const app = new Hono();
1303
1309
  const logger = options.logger ?? defaultProviderServerLogger;
@@ -1626,6 +1632,8 @@ const DEFAULT_SHUTDOWN_TIMEOUT_MS = 30_000;
1626
1632
  const DEFAULT_SHUTDOWN_SIGNALS = ["SIGTERM", "SIGINT"];
1627
1633
  const processSignalCoordinators = new Map();
1628
1634
  export async function serve(provider, options = {}) {
1635
+ // Declaration validation happens inside createServerApp (the shared app
1636
+ // construction path), so serve() does not duplicate the call here.
1629
1637
  const bunRuntime = getBunServeRuntime();
1630
1638
  if (bunRuntime === undefined) {
1631
1639
  throw new ProviderError("Bun runtime is required to start the provider server", {
@@ -57,7 +57,7 @@ const FIREFOX_H2_SETTINGS = {
57
57
  MAX_HEADER_LIST_SIZE: 65536,
58
58
  };
59
59
  const SAFARI_H2_SETTINGS = {
60
- HEADER_TABLE_SIZE: 4096,
60
+ // Safari 17 also sends a connection-level WINDOW_UPDATE increment of 10485760.
61
61
  ENABLE_PUSH: 0,
62
62
  INITIAL_WINDOW_SIZE: 4194304,
63
63
  MAX_CONCURRENT_STREAMS: 100,
@@ -162,8 +162,8 @@ const STEALTH_PROFILES = {
162
162
  }),
163
163
  "firefox-132": createProfile("firefox-132", {
164
164
  platform: "macos",
165
- version: "132.0",
166
- userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:132.0) Gecko/20100101 Firefox/132.0",
165
+ version: "133.0",
166
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
167
167
  tlsClientIdentifier: "firefox_132",
168
168
  ja3: FIREFOX_JA3,
169
169
  h2Settings: FIREFOX_H2_SETTINGS,
@@ -178,6 +178,15 @@ const STEALTH_PROFILES = {
178
178
  h2Settings: SAFARI_H2_SETTINGS,
179
179
  headerOrder: SAFARI_HEADER_ORDER,
180
180
  }),
181
+ "safari-17": createProfile("safari-17", {
182
+ platform: "macos",
183
+ version: "17.0",
184
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
185
+ tlsClientIdentifier: "safari_17_0",
186
+ ja3: SAFARI_JA3,
187
+ h2Settings: SAFARI_H2_SETTINGS,
188
+ headerOrder: SAFARI_HEADER_ORDER,
189
+ }),
181
190
  "safari-15": createProfile("safari-15", {
182
191
  platform: "macos",
183
192
  version: "15.6.1",
@@ -198,8 +207,8 @@ const STEALTH_PROFILES = {
198
207
  }),
199
208
  "ios-safari-18": createProfile("ios-safari-18", {
200
209
  platform: "ios",
201
- version: "18.0",
202
- userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1",
210
+ version: "18.1.1",
211
+ userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 18_1_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.1.1 Mobile/15E148 Safari/604.1",
203
212
  tlsClientIdentifier: "safari_ios_18_0",
204
213
  ja3: SAFARI_JA3,
205
214
  h2Settings: SAFARI_H2_SETTINGS,
@@ -207,8 +216,8 @@ const STEALTH_PROFILES = {
207
216
  }),
208
217
  "ios-safari-17": createProfile("ios-safari-17", {
209
218
  platform: "ios",
210
- version: "17.0",
211
- userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1",
219
+ version: "17.2",
220
+ userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1",
212
221
  tlsClientIdentifier: "safari_ios_17_0",
213
222
  ja3: SAFARI_JA3,
214
223
  h2Settings: SAFARI_H2_SETTINGS,
package/dist/types.d.ts CHANGED
@@ -298,9 +298,27 @@ export type ProviderChallenge = {
298
298
  } | {
299
299
  readonly kind: "aws_waf";
300
300
  readonly pageUrl: string;
301
+ /** `window.gokuProps.key`; solver vendors require it, while `"browser"` does not. */
302
+ readonly siteKey?: string;
301
303
  readonly captchaScript?: string;
302
304
  readonly context?: string;
303
305
  readonly iv?: string;
306
+ } | {
307
+ readonly kind: "akamai_sec_cpt";
308
+ readonly pageUrl: string;
309
+ /** The admitted challenge document, needed for tile/context extraction. */
310
+ readonly challengeHtml?: string;
311
+ } | {
312
+ readonly kind: "akamai_sensor";
313
+ readonly pageUrl: string;
314
+ /** Upstream sensor script URL the payload must be POSTed to. */
315
+ readonly scriptUrl: string;
316
+ /** Current `_abck` cookie value, rotates each round. */
317
+ readonly abck?: string;
318
+ /** Current `bm_sz` / `ak_bmsc` value when the upstream set one. */
319
+ readonly bmsz?: string;
320
+ /** Bot Manager major version when known ("3" measured on zozo.jp). */
321
+ readonly version?: string;
304
322
  };
305
323
  export type ProviderChallengeKind = ProviderChallenge["kind"];
306
324
  /**
@@ -317,12 +335,21 @@ export type ChallengeSolution = {
317
335
  readonly form: "cookies";
318
336
  readonly cookies: Readonly<Record<string, string>>;
319
337
  readonly userAgent: string;
338
+ /** Epoch seconds copied from the upstream cookie's own expiry attribute; never a constant. */
339
+ readonly expires?: number;
320
340
  };
321
341
  export interface ProviderResolverConfig {
322
342
  /** Ordered vendor fallback chain, tried first to last. */
323
343
  readonly vendors: readonly ProviderResolverVendor[];
324
344
  /** Challenge kinds this provider is permitted to request. */
325
345
  readonly kinds: readonly ProviderChallengeKind[];
346
+ /**
347
+ * Client fingerprint profile the SDK must use when reaching this upstream.
348
+ * Measured on zozo.jp: Chrome/Firefox profiles are refused 403 before any
349
+ * challenge is served, while a Safari profile is admitted. Provider-declared
350
+ * because only the provider knows its upstream's admission rule.
351
+ */
352
+ readonly clientProfile?: string;
326
353
  }
327
354
  export type SttAudioInput = {
328
355
  kind: "base64";
@@ -534,7 +561,13 @@ export interface HealthJourneyDefinition {
534
561
  requiredSecrets?: readonly string[];
535
562
  manualTrigger?: HealthJourneyManualTriggerPolicy;
536
563
  steps: readonly [HealthJourneyStep, ...HealthJourneyStep[]];
537
- run?: (ctx: HealthJourneyRunContext) => Promise<HealthJourneyRunResult | undefined>;
564
+ /**
565
+ * Required: a journey always declares `coversOperations`, and the health
566
+ * monitor reports a run-less journey as `journey_run_missing`. Declaration
567
+ * validation rejects a missing `run` (`health-journey-executable`), so this
568
+ * is typed required to fail at compile time rather than at boot.
569
+ */
570
+ run: (ctx: HealthJourneyRunContext) => Promise<HealthJourneyRunResult | undefined>;
538
571
  }
539
572
  /**
540
573
  * Health-check authoring surface owned by `@apifuse/provider-sdk`.
@@ -1332,9 +1365,9 @@ export interface NativeProviderConfig {
1332
1365
  }
1333
1366
  export interface ProviderCacheKeyOptions {
1334
1367
  /**
1335
- * Additional field names to omit from stable key material. The SDK always
1336
- * omits known secret-bearing names such as serviceKey, authorization,
1337
- * cookie, token, password, and secret.
1368
+ * Additional field names whose values are hashed in stable key material. The
1369
+ * SDK always hashes values under known secret-bearing names such as serviceKey,
1370
+ * authorization, cookie, token, password, and secret.
1338
1371
  */
1339
1372
  redactFields?: string[];
1340
1373
  }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.2.0-beta.24",
2
+ "version": "2.2.0-beta.26",
3
3
  "name": "@apifuse/provider-sdk",
4
4
  "private": false,
5
5
  "type": "module",
@@ -106,7 +106,6 @@
106
106
  "acorn": "^8.17.0",
107
107
  "ajv": "^8.17",
108
108
  "hono": "^4.12.25",
109
- "impit": "0.14.1",
110
109
  "ioredis": "^5.11.1",
111
110
  "ms": "^2.1.3",
112
111
  "playwright": "^1.55.1",
@@ -116,6 +115,7 @@
116
115
  "safe-regex": "^2.1",
117
116
  "socks": "^2.8.9",
118
117
  "tough-cookie": "^6.0.2",
118
+ "wreq-js": "3.0.0",
119
119
  "zod": "^4.4.3"
120
120
  },
121
121
  "repository": {
package/src/auth.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  import { AuthError, ProviderError } from "./errors.js";
2
+ import {
3
+ declarationInvalidError,
4
+ DECLARATION_RULE_IDS,
5
+ type DeclarationViolation,
6
+ } from "./declaration-validation.js";
2
7
  import type {
3
8
  AuthAbortData,
4
9
  AuthConfig,
@@ -656,6 +661,7 @@ export function defineCredentialsAuth<
656
661
  string,
657
662
  CredentialsAuthChallengeDefinition<CredentialsAuthFields, TCredentialKeys, string>
658
663
  >;
664
+ validateCredentialsAuthChallenges(challenges);
659
665
 
660
666
  return {
661
667
  auth: {
@@ -726,3 +732,37 @@ export function defineCredentialsAuth<
726
732
  },
727
733
  };
728
734
  }
735
+
736
+ function validateCredentialsAuthChallenges(
737
+ challenges: Record<
738
+ string,
739
+ CredentialsAuthChallengeDefinition<CredentialsAuthFields, readonly string[], string>
740
+ >,
741
+ ): void {
742
+ const violations: DeclarationViolation[] = [];
743
+ for (const [challengeId, challenge] of Object.entries(challenges)) {
744
+ const fieldCount =
745
+ challenge.fields && typeof challenge.fields === "object"
746
+ ? Object.keys(challenge.fields).length
747
+ : 0;
748
+ const fieldsDeclared =
749
+ challenge.fields !== undefined && challenge.fields !== null;
750
+ const hasFields = fieldCount > 0;
751
+ const hasVerify = typeof challenge.verify === "function";
752
+ const hasPoll = typeof challenge.poll === "function";
753
+ const isInteractive = hasFields && hasVerify && !hasPoll;
754
+ const isPolling = !fieldsDeclared && !hasVerify && hasPoll;
755
+ const isHybrid = hasFields && hasVerify && hasPoll;
756
+ const emptyFieldsDeclared = fieldsDeclared && !hasFields;
757
+ if (!emptyFieldsDeclared && (isInteractive || isPolling || isHybrid)) continue;
758
+
759
+ const path = `challenges.${challengeId}`;
760
+ violations.push({
761
+ ruleId: DECLARATION_RULE_IDS.challengeShape,
762
+ path,
763
+ message: "challenge must be interactive, polling, or an explicit hybrid.",
764
+ fix: `Give ${path} non-empty fields plus verify, poll alone, or all three for a hybrid.`,
765
+ });
766
+ }
767
+ if (violations.length > 0) throw declarationInvalidError(violations);
768
+ }
@@ -114,10 +114,10 @@ Structured errors return an `error` object with `code`, `message`,
114
114
  - Auth flow: call `/auth/start`, then `/auth/continue` with the same `flowId`;
115
115
  carry returned `contextPatch` values into the next request's `context`.
116
116
  - Stealth/browser runtime: keep access-sensitive operations on `ctx.stealth.fetch()` with an
117
- SDK stealth `profile`; the TypeScript stealth runtime uses `impit` internally.
118
- `ctx.stealth` supports Chrome/Firefox-style profiles. For TypeScript browser
119
- Providers or Safari-specific behavior use `browser.engine: "playwright-stealth"`
120
- (`nodriver` is Python-runtime only), then install local Chromium with
117
+ SDK stealth `profile`; the TypeScript stealth runtime uses `wreq-js` internally
118
+ and supports Chrome, Firefox, and Safari profiles. Use `ctx.browser` only when
119
+ the provider needs browser execution; TypeScript browser Providers use
120
+ `browser.engine: "playwright-stealth"` (`nodriver` is Python-runtime only). Install local Chromium with
121
121
  `bunx playwright install chromium` or set `APIFUSE__CDP_POOL__URL`.
122
122
 
123
123
  ## Next steps