@apifuse/provider-sdk 2.2.0-beta.30 → 2.2.0-beta.32

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,13 @@
1
1
  # @apifuse/provider-sdk Changelog
2
2
 
3
+ ## 2.2.0-beta.32
4
+
5
+ - Release candidate for main commit 82f1c0c1de916a40c8c74b1d6470a493b134c0ed.
6
+
7
+ ## 2.2.0-beta.31
8
+
9
+ - Release candidate for main commit 94006be0852918867d254dc23d0fa8420a07be9c.
10
+
3
11
  ## 2.2.0-beta.30
4
12
 
5
13
  - Release candidate for main commit ccf3d56bfcbe9683c6e7ad45dde8e6703467f9e7.
@@ -18,6 +18,7 @@ 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",
21
22
  "CHOICE_STATE_PAYLOAD_TOO_LARGE",
22
23
  "CHOICE_STATE_UNAVAILABLE",
23
24
  "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, } from "./runtime/choice.js";
20
+ export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_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, } from "./runtime/choice.js";
18
+ export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_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, } from "./runtime/choice.js";
8
+ export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_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, } from "./runtime/choice.js";
7
+ export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV, } from "./runtime/choice.js";
8
8
  export { APIFUSE_DESCRIPTION_KEY_META_KEY, APIFUSE_REDACTION_MARKER, APIFUSE_SENSITIVE_KIND_META_KEY, APIFUSE_SENSITIVE_META_KEY, collectSensitivePaths, describeKey, field, fields, isSensitiveSchema, redactPayload, sensitive, z, } from "./schema.js";
9
9
  export { NativeEgressGrantExpiredError, NativeEgressNotDeclaredError, NativeIdleTimeoutError, NativeNetworkError, NativeProxyExpiredError, } from "./runtime/native-network-errors.js";
10
10
  export { HttpRetryAfterPolicy, HttpRetryDelayStrategy, HttpRetryJitter, HttpRetryPreset, HttpRetryUnsafeMethodPolicy, } from "./types.js";
@@ -1,5 +1,6 @@
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";
3
4
  export type ProviderChoiceTelemetryEvent = {
4
5
  readonly providerId: string;
5
6
  readonly purpose: string;
@@ -3,6 +3,7 @@ import { assertFreshProviderChoiceIssuedAt, ProviderChoiceTokenError, } from "..
3
3
  import { isProviderError, ProviderError } from "../errors.js";
4
4
  import { CHOICE_WORDLIST_SIZE, choiceWordAt, HIGH_CHOICE_WORD_COUNT, isChoiceWord, STANDARD_CHOICE_WORD_COUNT, } from "./choice-wordlist.js";
5
5
  export const PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV = "APIFUSE__PROVIDER_RUNTIME__CHOICE_TOKEN_MASTER_SECRET";
6
+ export const PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV = "APIFUSE__PROVIDER_RUNTIME__CHOICE_WORD_ISSUANCE";
6
7
  const PRIMARY_CHOICE_TOKEN_KID = "v1";
7
8
  const MANAGED_CHOICE_TOKEN_VERSION = 1;
8
9
  const SERVER_STORED_CHOICE_RECORD_VERSION = 1;
@@ -15,28 +16,53 @@ export function createProviderChoiceContext(options) {
15
16
  const issuedAtMs = issueOptions.nowMs ?? Date.now();
16
17
  const resolvedStorage = resolveIssueStorage(issueOptions.storage, issueOptions.payload);
17
18
  if (resolvedStorage.mode === "server") {
19
+ const issuance = resolveChoiceWordIssuance(options.env);
20
+ const keys = hasRequestedChoiceBinding(issueOptions.bind)
21
+ ? deriveManagedChoiceKeys({
22
+ masterSecret: resolveMasterSecret(),
23
+ providerId: options.providerId,
24
+ purpose: issueOptions.purpose,
25
+ kid,
26
+ })
27
+ : undefined;
18
28
  const binding = hasRequestedChoiceBinding(issueOptions.bind)
19
29
  ? createChoiceBinding({
20
- keys: deriveManagedChoiceKeys({
21
- masterSecret: resolveMasterSecret(),
22
- providerId: options.providerId,
23
- purpose: issueOptions.purpose,
24
- kid,
25
- }),
30
+ keys: keys,
26
31
  options: issueOptions.bind,
27
32
  request: options.request,
28
33
  credential: options.credential,
29
34
  required: true,
30
35
  })
31
36
  : undefined;
32
- return issueServerStoredChoice({
37
+ const baseEnvelope = {
38
+ v: MANAGED_CHOICE_TOKEN_VERSION,
39
+ provider_id: options.providerId,
40
+ purpose: issueOptions.purpose,
41
+ issued_at_ms: issuedAtMs,
42
+ ttl_ms: issueOptions.ttlMs,
43
+ binding,
44
+ };
45
+ if (issuance === "legacy") {
46
+ const legacyKeys = keys ??
47
+ deriveManagedChoiceKeys({
48
+ masterSecret: resolveMasterSecret(),
49
+ providerId: options.providerId,
50
+ purpose: issueOptions.purpose,
51
+ kid,
52
+ });
53
+ return issueLegacyServerStoredChoice({
54
+ baseEnvelope,
55
+ issueOptions,
56
+ storage: resolvedStorage.storage,
57
+ contextState: options.state,
58
+ kid,
59
+ keys: legacyKeys,
60
+ issuedAtMs,
61
+ });
62
+ }
63
+ return issueWordServerStoredChoice({
33
64
  baseEnvelope: {
34
- v: MANAGED_CHOICE_TOKEN_VERSION,
35
- provider_id: options.providerId,
36
- purpose: issueOptions.purpose,
37
- issued_at_ms: issuedAtMs,
38
- ttl_ms: issueOptions.ttlMs,
39
- binding,
65
+ ...baseEnvelope,
40
66
  },
41
67
  issueOptions,
42
68
  storage: resolvedStorage.storage,
@@ -133,13 +159,7 @@ export function createProviderChoiceContext(options) {
133
159
  purpose: parseOptions.purpose,
134
160
  kid: tokenKid,
135
161
  });
136
- const signedBody = [
137
- parseOptions.prefix,
138
- tokenKid,
139
- encodedIv,
140
- encryptedPayload,
141
- authTag,
142
- ].join(".");
162
+ const signedBody = [parseOptions.prefix, tokenKid, encodedIv, encryptedPayload, authTag].join(".");
143
163
  assertManagedChoiceSignature({
144
164
  signedBody,
145
165
  signature,
@@ -287,6 +307,20 @@ function createLegacyExplicitParseResult(options) {
287
307
  },
288
308
  };
289
309
  }
310
+ function resolveChoiceWordIssuance(env) {
311
+ const configured = env?.get(PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV);
312
+ const value = configured?.trim() ?? "";
313
+ if (value === "" || value === "legacy")
314
+ return "legacy";
315
+ if (value === "word")
316
+ return "word";
317
+ throw new ProviderError(`Unsupported provider choice word issuance mode "${value}". Expected "legacy" or "word".`, {
318
+ code: "CHOICE_WORD_ISSUANCE_INVALID",
319
+ category: "input_validation",
320
+ retryable: false,
321
+ details: { env: PROVIDER_RUNTIME_CHOICE_WORD_ISSUANCE_ENV },
322
+ });
323
+ }
290
324
  function resolveChoiceMasterSecret(options) {
291
325
  const configured = options.masterSecret ?? options.env?.get(PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV);
292
326
  const trimmed = configured?.trim();
@@ -336,7 +370,7 @@ function encryptManagedChoiceToken(options) {
336
370
  .digest("base64url");
337
371
  return `${signedBody}.${signature}`;
338
372
  }
339
- async function issueServerStoredChoice(options) {
373
+ async function issueWordServerStoredChoice(options) {
340
374
  const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
341
375
  const payloadDigest = digestChoicePayload(serializedPayload);
342
376
  const namespace = resolveChoiceStateNamespace({
@@ -386,6 +420,47 @@ async function issueServerStoredChoice(options) {
386
420
  retryable: false,
387
421
  });
388
422
  }
423
+ /**
424
+ * Beta.28-compatible server handle issuance. The state stores the payload and
425
+ * the client receives the existing six-part encrypted envelope whose payload
426
+ * is a server-state handle.
427
+ */
428
+ async function issueLegacyServerStoredChoice(options) {
429
+ const serializedPayload = serializeChoicePayload(options.issueOptions.payload);
430
+ const payloadBytes = Buffer.byteLength(serializedPayload, "utf8");
431
+ if (payloadBytes > options.storage.maxValueBytes) {
432
+ throw new ProviderError("Provider choice payload exceeds state storage policy.", {
433
+ code: "CHOICE_STATE_PAYLOAD_TOO_LARGE",
434
+ category: "input_validation",
435
+ retryable: false,
436
+ details: { maxValueBytes: options.storage.maxValueBytes, payloadBytes },
437
+ });
438
+ }
439
+ const stateId = `choice_${randomBytes(16).toString("base64url")}`;
440
+ const namespace = resolveChoiceStateNamespace({
441
+ storage: options.storage,
442
+ contextState: options.contextState,
443
+ ttlMs: options.issueOptions.ttlMs,
444
+ });
445
+ await namespace.set(optionsStateKey(stateId), options.issueOptions.payload, {
446
+ ttl: stateTtl(options.storage, options.issueOptions.ttlMs),
447
+ });
448
+ const envelope = {
449
+ ...options.baseEnvelope,
450
+ payload: {
451
+ storage: "server",
452
+ state_id: stateId,
453
+ payload_digest: digestChoicePayload(serializedPayload),
454
+ created_at_ms: options.issuedAtMs,
455
+ },
456
+ };
457
+ return encryptManagedChoiceToken({
458
+ prefix: options.issueOptions.prefix,
459
+ kid: options.kid,
460
+ envelope,
461
+ keys: options.keys,
462
+ });
463
+ }
389
464
  async function parseWordServerStoredChoice(options) {
390
465
  const storage = resolveParseStorage(options.parseOptions.storage);
391
466
  const namespace = resolveChoiceStateNamespace({
@@ -0,0 +1,24 @@
1
+ import type { ChallengeSolution, ProviderChallenge } from "../../types.js";
2
+ import type { TraceRecorder } from "../trace.js";
3
+ import { type ResolverIdentity, type ResolverVendorAdapter } from "./types.js";
4
+ type Delay = (ms: number, signal: AbortSignal) => Promise<void>;
5
+ export interface CapsolverResolverVendorOptions {
6
+ readonly apiKey?: string;
7
+ readonly timeoutMs?: number;
8
+ readonly pollIntervalMs?: number;
9
+ readonly allowedHosts: readonly string[];
10
+ readonly fetchImpl?: typeof fetch;
11
+ readonly baseUrl?: string;
12
+ /** Test-only clock override; supplying it disables the real-time deadline timer. */
13
+ readonly now?: () => number;
14
+ /** Test-only delay override used with `now` to exercise polling without sleeping. */
15
+ readonly delay?: Delay;
16
+ }
17
+ export interface CapsolverResolverVendorAdapter extends ResolverVendorAdapter {
18
+ readonly id: "capsolver";
19
+ solve(challenge: ProviderChallenge, identity: ResolverIdentity | undefined, signal: AbortSignal, traceRecorder?: TraceRecorder): Promise<Extract<ChallengeSolution, {
20
+ readonly form: "token";
21
+ }>>;
22
+ }
23
+ export declare function createCapsolverResolverVendorAdapter(options: CapsolverResolverVendorOptions): CapsolverResolverVendorAdapter;
24
+ export {};
@@ -0,0 +1,402 @@
1
+ import { redactSensitiveText } from "../request-options.js";
2
+ import { assertResolverHostAllowed } from "./hosts.js";
3
+ import { ResolverChallengeVerdictError, ResolverVendorUnavailableError, resolverVendorSupports, } from "./types.js";
4
+ const CAPSOLVER_VENDOR_ID = "capsolver";
5
+ const DEFAULT_CAPSOLVER_BASE_URL = "https://api.capsolver.com";
6
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
7
+ const DEFAULT_TIMEOUT_MS = 120_000;
8
+ class CapsolverSolveTimeoutError extends Error {
9
+ constructor() {
10
+ super("Capsolver resolver solve budget elapsed");
11
+ this.name = "CapsolverSolveTimeoutError";
12
+ }
13
+ }
14
+ class CapsolverUnavailableError extends ResolverVendorUnavailableError {
15
+ errorCode;
16
+ errorDescription;
17
+ responseStatus;
18
+ responseContentType;
19
+ responseLength;
20
+ constructor(reason, options) {
21
+ super(CAPSOLVER_VENDOR_ID, reason, options);
22
+ this.errorCode = options.errorCode;
23
+ this.errorDescription = options.errorDescription;
24
+ this.responseStatus = options.responseStatus;
25
+ this.responseContentType = options.responseContentType;
26
+ this.responseLength = options.responseLength;
27
+ }
28
+ }
29
+ class CapsolverVerdictError extends ResolverChallengeVerdictError {
30
+ phase;
31
+ errorCode;
32
+ errorDescription;
33
+ constructor(options) {
34
+ super(CAPSOLVER_VENDOR_ID, "solve_failed", options);
35
+ this.phase = options.phase;
36
+ this.errorCode = options.errorCode;
37
+ this.errorDescription = options.errorDescription;
38
+ }
39
+ }
40
+ function isJsonRecord(value) {
41
+ return value !== null && typeof value === "object" && !Array.isArray(value);
42
+ }
43
+ function abortReason(signal) {
44
+ return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
45
+ }
46
+ function containsSensitiveValue(value, sensitiveValues) {
47
+ const secrets = sensitiveValues.filter((secret) => secret.length > 0);
48
+ if (secrets.length === 0)
49
+ return false;
50
+ const seen = new Set();
51
+ const inspect = (candidate) => {
52
+ if (typeof candidate === "string") {
53
+ return secrets.some((secret) => candidate.includes(secret));
54
+ }
55
+ if (candidate === null || (typeof candidate !== "object" && typeof candidate !== "function")) {
56
+ return false;
57
+ }
58
+ if (seen.has(candidate))
59
+ return false;
60
+ seen.add(candidate);
61
+ try {
62
+ for (const property of Reflect.ownKeys(candidate)) {
63
+ if (typeof property === "string" && inspect(property))
64
+ return true;
65
+ const descriptor = Object.getOwnPropertyDescriptor(candidate, property);
66
+ if (!descriptor)
67
+ return true;
68
+ if ("value" in descriptor && inspect(descriptor.value))
69
+ return true;
70
+ if (descriptor.get !== undefined || descriptor.set !== undefined)
71
+ return true;
72
+ }
73
+ }
74
+ catch {
75
+ return true;
76
+ }
77
+ return false;
78
+ };
79
+ return inspect(value);
80
+ }
81
+ function safeCauseOptions(error, sensitiveValues) {
82
+ return containsSensitiveValue(error, sensitiveValues) ? {} : { cause: error };
83
+ }
84
+ function sanitizedJsonParseCause() {
85
+ return new SyntaxError("Upstream response failed");
86
+ }
87
+ function raceWithAbort(operation, signal, phase, sensitiveValues = []) {
88
+ if (signal.aborted)
89
+ return Promise.reject(abortReason(signal));
90
+ return new Promise((resolve, reject) => {
91
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
92
+ const onAbort = () => {
93
+ cleanup();
94
+ reject(abortReason(signal));
95
+ };
96
+ signal.addEventListener("abort", onAbort, { once: true });
97
+ operation().then((value) => {
98
+ cleanup();
99
+ resolve(value);
100
+ }, (error) => {
101
+ cleanup();
102
+ if (phase === undefined) {
103
+ reject(error);
104
+ return;
105
+ }
106
+ reject(new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", {
107
+ ...safeCauseOptions(error, sensitiveValues),
108
+ phase,
109
+ }));
110
+ });
111
+ });
112
+ }
113
+ async function abortableDelay(ms, signal) {
114
+ let timer;
115
+ try {
116
+ await raceWithAbort(() => new Promise((resolve) => {
117
+ timer = setTimeout(resolve, ms);
118
+ }), signal);
119
+ }
120
+ finally {
121
+ if (timer !== undefined)
122
+ clearTimeout(timer);
123
+ }
124
+ }
125
+ function responseErrorFields(payload) {
126
+ return {
127
+ errorId: typeof payload.errorId === "number" ? payload.errorId : undefined,
128
+ errorCode: typeof payload.errorCode === "string" ? payload.errorCode : undefined,
129
+ errorDescription: typeof payload.errorDescription === "string" ? payload.errorDescription : undefined,
130
+ };
131
+ }
132
+ function parseCreateTaskResponse(payload) {
133
+ const taskId = payload.taskId;
134
+ return {
135
+ ...responseErrorFields(payload),
136
+ taskId: typeof taskId === "string" || typeof taskId === "number" ? taskId : undefined,
137
+ };
138
+ }
139
+ function parsePollResultResponse(payload) {
140
+ const solution = isJsonRecord(payload.solution) ? payload.solution : undefined;
141
+ return {
142
+ ...responseErrorFields(payload),
143
+ status: typeof payload.status === "string" ? payload.status : undefined,
144
+ solution: solution
145
+ ? { token: typeof solution.token === "string" ? solution.token : undefined }
146
+ : undefined,
147
+ };
148
+ }
149
+ function isAllocationExhausted(payload) {
150
+ const code = payload.errorCode?.toLowerCase() ?? "";
151
+ const description = payload.errorDescription?.toLowerCase() ?? "";
152
+ return (code === "error_zero_balance" ||
153
+ /(?:insufficient|zero|no|not enough)\s+(?:balance|funds|credit)/u.test(`${code} ${description}`));
154
+ }
155
+ function isNegativeVerdict(payload) {
156
+ return payload.errorCode?.toLowerCase() === "error_captcha_unsolvable";
157
+ }
158
+ function safeVendorDetail(value, sensitiveValues) {
159
+ const redacted = redactSensitiveText(value, sensitiveValues);
160
+ return containsSensitiveValue(redacted, sensitiveValues) ? "[REDACTED]" : redacted;
161
+ }
162
+ function vendorErrorDetails(payload, sensitiveValues) {
163
+ const errorCode = payload.errorCode?.trim();
164
+ const errorDescription = payload.errorDescription?.trim();
165
+ return {
166
+ ...(errorCode ? { errorCode: safeVendorDetail(errorCode, sensitiveValues) } : {}),
167
+ ...(errorDescription
168
+ ? { errorDescription: safeVendorDetail(payload.errorDescription ?? "", sensitiveValues) }
169
+ : {}),
170
+ };
171
+ }
172
+ function unavailableForPayload(payload, phase, sensitiveValues) {
173
+ const details = vendorErrorDetails(payload, sensitiveValues);
174
+ if (isNegativeVerdict(payload)) {
175
+ return new CapsolverVerdictError({ phase, ...details });
176
+ }
177
+ return new CapsolverUnavailableError(isAllocationExhausted(payload) ? "allocation_exhausted" : "transport_failure", { phase, ...details });
178
+ }
179
+ async function postJson(fetchImpl, url, body, signal, phase, sensitiveValues, parseResponse) {
180
+ const response = await raceWithAbort(() => fetchImpl(url, {
181
+ method: "POST",
182
+ headers: { "content-type": "application/json" },
183
+ body: JSON.stringify(body),
184
+ signal,
185
+ redirect: "error",
186
+ }), signal, phase, sensitiveValues);
187
+ let responseText;
188
+ try {
189
+ responseText = await raceWithAbort(() => response.text(), signal, phase, sensitiveValues);
190
+ }
191
+ catch (error) {
192
+ if (error instanceof ResolverVendorUnavailableError)
193
+ throw error;
194
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", {
195
+ cause: error,
196
+ phase,
197
+ });
198
+ }
199
+ let payload;
200
+ try {
201
+ payload = JSON.parse(responseText);
202
+ }
203
+ catch {
204
+ const contentType = response.headers.get("content-type");
205
+ throw new CapsolverUnavailableError("transport_failure", {
206
+ cause: sanitizedJsonParseCause(),
207
+ phase,
208
+ responseStatus: response.status,
209
+ responseContentType: contentType
210
+ ? safeVendorDetail(contentType.slice(0, 128), sensitiveValues)
211
+ : undefined,
212
+ responseLength: responseText.length,
213
+ });
214
+ }
215
+ if (!isJsonRecord(payload)) {
216
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", {
217
+ phase,
218
+ });
219
+ }
220
+ return { ok: response.ok, payload: parseResponse(payload) };
221
+ }
222
+ function endpoint(baseUrl, path) {
223
+ return `${baseUrl.replace(/\/+$/u, "")}/${path}`;
224
+ }
225
+ function spanErrorAttributes(error, phase) {
226
+ if (error instanceof ResolverVendorUnavailableError) {
227
+ return {
228
+ unavailability_reason: error.reason,
229
+ transport_phase: error.phase,
230
+ ...(error instanceof CapsolverUnavailableError
231
+ ? {
232
+ vendor_error_code: error.errorCode,
233
+ vendor_error_description: error.errorDescription,
234
+ response_status: error.responseStatus,
235
+ response_content_type: error.responseContentType,
236
+ response_length: error.responseLength,
237
+ }
238
+ : {}),
239
+ };
240
+ }
241
+ if (error instanceof CapsolverVerdictError) {
242
+ return {
243
+ verdict_reason: error.reason,
244
+ transport_phase: error.phase,
245
+ vendor_error_code: error.errorCode,
246
+ vendor_error_description: error.errorDescription,
247
+ };
248
+ }
249
+ if (error instanceof CapsolverSolveTimeoutError) {
250
+ return {
251
+ unavailability_reason: "timeout",
252
+ transport_phase: phase,
253
+ };
254
+ }
255
+ return undefined;
256
+ }
257
+ export function createCapsolverResolverVendorAdapter(options) {
258
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
259
+ const baseUrl = options.baseUrl ?? DEFAULT_CAPSOLVER_BASE_URL;
260
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
261
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
262
+ const now = options.now ?? Date.now;
263
+ const delay = options.delay ?? abortableDelay;
264
+ return {
265
+ id: CAPSOLVER_VENDOR_ID,
266
+ supports(kind) {
267
+ return resolverVendorSupports(CAPSOLVER_VENDOR_ID, kind);
268
+ },
269
+ async solve(challenge, _identity, callerSignal, traceRecorder) {
270
+ const apiKey = options.apiKey?.trim();
271
+ if (!apiKey) {
272
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "missing_credentials", {
273
+ phase: "create_task",
274
+ });
275
+ }
276
+ if (!resolverVendorSupports(CAPSOLVER_VENDOR_ID, challenge.kind)) {
277
+ throw new TypeError(`Capsolver resolver does not support ${challenge.kind}`);
278
+ }
279
+ if (challenge.kind !== "turnstile") {
280
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "not_implemented", {
281
+ phase: "create_task",
282
+ });
283
+ }
284
+ const sensitiveValues = [
285
+ apiKey,
286
+ challenge.pageUrl,
287
+ challenge.siteKey,
288
+ ...(challenge.action !== undefined ? [challenge.action] : []),
289
+ ...(challenge.cdata !== undefined ? [challenge.cdata] : []),
290
+ ];
291
+ assertResolverHostAllowed(challenge.pageUrl, options.allowedHosts);
292
+ callerSignal.throwIfAborted();
293
+ const solveController = new AbortController();
294
+ const onCallerAbort = () => solveController.abort(abortReason(callerSignal));
295
+ callerSignal.addEventListener("abort", onCallerAbort, { once: true });
296
+ const timeout = options.now
297
+ ? undefined
298
+ : setTimeout(() => solveController.abort(new CapsolverSolveTimeoutError()), timeoutMs);
299
+ const startedAt = now();
300
+ let phase = "create_task";
301
+ try {
302
+ const createTask = async () => {
303
+ const metadata = {
304
+ ...(challenge.action !== undefined ? { action: challenge.action } : {}),
305
+ ...(challenge.cdata !== undefined ? { cdata: challenge.cdata } : {}),
306
+ };
307
+ const createResult = await postJson(fetchImpl, endpoint(baseUrl, "createTask"), {
308
+ clientKey: apiKey,
309
+ task: {
310
+ type: "AntiTurnstileTaskProxyLess",
311
+ websiteURL: challenge.pageUrl,
312
+ websiteKey: challenge.siteKey,
313
+ ...(challenge.action !== undefined || challenge.cdata !== undefined
314
+ ? { metadata }
315
+ : {}),
316
+ },
317
+ }, solveController.signal, phase, sensitiveValues, parseCreateTaskResponse);
318
+ const taskId = createResult.payload.taskId;
319
+ if (!createResult.ok || createResult.payload.errorId !== 0 || taskId === undefined) {
320
+ throw unavailableForPayload(createResult.payload, phase, sensitiveValues);
321
+ }
322
+ return taskId;
323
+ };
324
+ const taskId = traceRecorder
325
+ ? await traceRecorder.runSpan("resolver.vendor.create_task", createTask, {
326
+ attributes: {
327
+ vendor: CAPSOLVER_VENDOR_ID,
328
+ challenge_kind: challenge.kind,
329
+ },
330
+ onError: (error) => spanErrorAttributes(error, "create_task"),
331
+ })
332
+ : await createTask();
333
+ phase = "poll_result";
334
+ const pollResult = async () => {
335
+ while (true) {
336
+ callerSignal.throwIfAborted();
337
+ const remainingMs = timeoutMs - (now() - startedAt);
338
+ if (remainingMs <= 0)
339
+ throw new CapsolverSolveTimeoutError();
340
+ await delay(Math.min(pollIntervalMs, remainingMs), solveController.signal);
341
+ callerSignal.throwIfAborted();
342
+ if (now() - startedAt >= timeoutMs)
343
+ throw new CapsolverSolveTimeoutError();
344
+ const result = await postJson(fetchImpl, endpoint(baseUrl, "getTaskResult"), { clientKey: apiKey, taskId }, solveController.signal, phase, sensitiveValues, parsePollResultResponse);
345
+ if (!result.ok || result.payload.errorId !== 0) {
346
+ throw unavailableForPayload(result.payload, phase, sensitiveValues);
347
+ }
348
+ switch (result.payload.status) {
349
+ case "idle":
350
+ case "processing":
351
+ continue;
352
+ case "ready":
353
+ break;
354
+ default:
355
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", { phase });
356
+ }
357
+ const token = result.payload.solution?.token;
358
+ if (!token?.trim()) {
359
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", {
360
+ phase,
361
+ });
362
+ }
363
+ return { form: "token", token };
364
+ }
365
+ };
366
+ return traceRecorder
367
+ ? await traceRecorder.runSpan("resolver.vendor.poll_result", pollResult, {
368
+ attributes: {
369
+ vendor: CAPSOLVER_VENDOR_ID,
370
+ challenge_kind: challenge.kind,
371
+ },
372
+ onError: (error) => spanErrorAttributes(error, "poll_result"),
373
+ })
374
+ : await pollResult();
375
+ }
376
+ catch (error) {
377
+ if (callerSignal.aborted)
378
+ throw abortReason(callerSignal);
379
+ if (error instanceof CapsolverSolveTimeoutError ||
380
+ solveController.signal.reason instanceof CapsolverSolveTimeoutError) {
381
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "timeout", {
382
+ cause: error,
383
+ phase,
384
+ });
385
+ }
386
+ if (error instanceof ResolverVendorUnavailableError ||
387
+ error instanceof ResolverChallengeVerdictError) {
388
+ throw error;
389
+ }
390
+ throw new ResolverVendorUnavailableError(CAPSOLVER_VENDOR_ID, "transport_failure", {
391
+ ...safeCauseOptions(error, sensitiveValues),
392
+ phase,
393
+ });
394
+ }
395
+ finally {
396
+ if (timeout !== undefined)
397
+ clearTimeout(timeout);
398
+ callerSignal.removeEventListener("abort", onCallerAbort);
399
+ }
400
+ },
401
+ };
402
+ }