@apifuse/provider-sdk 2.2.0-beta.58 → 2.2.0-beta.60
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/AUTHORING.md +25 -0
- package/CHANGELOG.md +8 -0
- package/README.md +18 -2
- package/bin/apifuse-dev.ts +2 -1
- package/bin/apifuse-pack-types.ts +49 -0
- package/bin/apifuse-perf.ts +41 -4
- package/bin/apifuse-record.ts +9 -2
- package/dist/cli/templates/provider/README.md.tpl +15 -3
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.js +43 -18
- package/dist/declaration-validation.d.ts +2 -1
- package/dist/declaration-validation.js +39 -0
- package/dist/define.d.ts +4 -4
- package/dist/define.js +25 -1
- package/dist/engine.d.ts +16 -0
- package/dist/engine.js +59 -3
- package/dist/error-resolution.js +13 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +4 -2
- package/dist/lint.js +38 -17
- package/dist/provider.d.ts +3 -3
- package/dist/provider.js +2 -2
- package/dist/runtime/bounded-expiring-map.d.ts +21 -0
- package/dist/runtime/bounded-expiring-map.js +64 -0
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.d.ts +2 -0
- package/dist/runtime/cache.js +11 -3
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +11 -0
- package/dist/runtime/diagnostic-env.js +35 -0
- package/dist/runtime/diagnostic-redactor.d.ts +49 -0
- package/dist/runtime/diagnostic-redactor.js +569 -0
- package/dist/runtime/egress-lease.d.ts +65 -0
- package/dist/runtime/egress-lease.js +236 -0
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/http.js +1 -0
- package/dist/runtime/instrumentation.js +3 -0
- package/dist/runtime/ocr.js +2 -1
- package/dist/runtime/otlp.js +5 -4
- package/dist/runtime/proxy-nodemaven.js +3 -2
- package/dist/runtime/proxy-retry-policy.d.ts +4 -1
- package/dist/runtime/proxy-retry-policy.js +11 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/request-telemetry.d.ts +7 -9
- package/dist/runtime/resolver-config.d.ts +7 -0
- package/dist/runtime/resolver-config.js +8 -0
- package/dist/runtime/resolver-public.d.ts +3 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-shared.d.ts +9 -0
- package/dist/runtime/resolver-shared.js +57 -0
- package/dist/runtime/resolver-telemetry.d.ts +134 -0
- package/dist/runtime/resolver-telemetry.js +204 -0
- package/dist/runtime/resolver-usage.d.ts +24 -0
- package/dist/runtime/resolver-usage.js +60 -0
- package/dist/runtime/resolver-vendors/bindings.d.ts +5 -0
- package/dist/runtime/resolver-vendors/bindings.js +5 -0
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver-vendors/capsolver.d.ts +2 -2
- package/dist/runtime/resolver-vendors/capsolver.js +21 -9
- package/dist/runtime/resolver-vendors/hypersolutions.d.ts +27 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +369 -0
- package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -2
- package/dist/runtime/resolver-vendors/twocaptcha.js +26 -10
- package/dist/runtime/resolver-vendors/types.d.ts +35 -5
- package/dist/runtime/resolver-vendors/types.js +20 -10
- package/dist/runtime/resolver.d.ts +5 -1
- package/dist/runtime/resolver.js +400 -111
- package/dist/runtime/stealth-akamai-sbsd.d.ts +54 -0
- package/dist/runtime/stealth-akamai-sbsd.js +142 -0
- package/dist/runtime/stealth.js +547 -53
- package/dist/runtime/stt.js +3 -2
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +115 -20
- package/dist/schema.d.ts +20 -0
- package/dist/schema.js +72 -2
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/self-test.d.ts +4 -0
- package/dist/server/self-test.js +10 -1
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +131 -0
- package/dist/server/serve-implementation.d.ts +7 -1
- package/dist/server/serve-implementation.js +394 -125
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/server/types.d.ts +6 -0
- package/dist/server/types.js +14 -0
- package/dist/stealth/profiles.d.ts +7 -1
- package/dist/stealth/profiles.js +9 -0
- package/dist/testing/run.js +6 -0
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/dist/types.d.ts +106 -10
- package/package.json +2 -1
- package/src/cli/templates/provider/README.md.tpl +15 -3
- package/src/config/loader.ts +47 -18
- package/src/declaration-validation.ts +47 -1
- package/src/define.ts +45 -3
- package/src/engine.ts +78 -3
- package/src/error-resolution.ts +13 -0
- package/src/index.ts +34 -1
- package/src/lint.ts +44 -18
- package/src/provider.ts +10 -0
- package/src/runtime/bounded-expiring-map.ts +67 -0
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +13 -3
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +55 -0
- package/src/runtime/diagnostic-redactor.ts +662 -0
- package/src/runtime/egress-lease.ts +341 -0
- package/src/runtime/env.ts +2 -1
- package/src/runtime/http.ts +1 -0
- package/src/runtime/instrumentation.ts +3 -0
- package/src/runtime/ocr.ts +2 -1
- package/src/runtime/otlp.ts +5 -4
- package/src/runtime/proxy-nodemaven.ts +3 -2
- package/src/runtime/proxy-retry-policy.ts +12 -1
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/request-telemetry.ts +5 -7
- package/src/runtime/resolver-config.ts +12 -0
- package/src/runtime/resolver-public.ts +21 -0
- package/src/runtime/resolver-shared.ts +75 -0
- package/src/runtime/resolver-telemetry.ts +362 -0
- package/src/runtime/resolver-usage.ts +96 -0
- package/src/runtime/resolver-vendors/bindings.ts +5 -0
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver-vendors/capsolver.ts +22 -3
- package/src/runtime/resolver-vendors/hypersolutions.ts +480 -0
- package/src/runtime/resolver-vendors/twocaptcha.ts +26 -12
- package/src/runtime/resolver-vendors/types.ts +60 -12
- package/src/runtime/resolver.ts +468 -126
- package/src/runtime/stealth-akamai-sbsd.ts +207 -0
- package/src/runtime/stealth.ts +727 -95
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +80 -2
- package/src/server/index.ts +21 -0
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/self-test.ts +13 -0
- package/src/server/sensitive-values.ts +200 -0
- package/src/server/serve-implementation.ts +561 -136
- package/src/server/trace-output.ts +77 -17
- package/src/server/types.ts +14 -0
- package/src/stealth/profiles.ts +10 -0
- package/src/testing/run.ts +6 -0
- package/src/trace-sanitization.ts +70 -23
- package/src/types.ts +124 -19
package/src/runtime/resolver.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readDiagnosticEnv } from "./diagnostic-env.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -17,7 +18,9 @@ import type {
|
|
|
17
18
|
ProviderResolverVendor,
|
|
18
19
|
ResolverContext,
|
|
19
20
|
} from "../types.js";
|
|
21
|
+
import { isProviderCacheBypassed } from "./cache.js";
|
|
20
22
|
import {
|
|
23
|
+
RESOLVER_CHALLENGE_BINDINGS,
|
|
21
24
|
resolverChallengeAllowsDirectCache,
|
|
22
25
|
resolverChallengeIsCacheable,
|
|
23
26
|
resolverChallengeIsIdentityScoped,
|
|
@@ -25,21 +28,26 @@ import {
|
|
|
25
28
|
} from "./resolver-vendors/bindings.js";
|
|
26
29
|
import { createBrowserResolverVendorAdapter } from "./resolver-vendors/browser.js";
|
|
27
30
|
import { createCapsolverResolverVendorAdapter } from "./resolver-vendors/capsolver.js";
|
|
31
|
+
import { createHypersolutionsResolverVendorAdapter } from "./resolver-vendors/hypersolutions.js";
|
|
28
32
|
import { assertResolverHostAllowed } from "./resolver-vendors/hosts.js";
|
|
29
33
|
import { createTwoCaptchaResolverVendorAdapter } from "./resolver-vendors/twocaptcha.js";
|
|
30
34
|
import {
|
|
31
35
|
RESOLVER_VENDOR_CAPABILITIES,
|
|
32
36
|
type ResolverIdentity,
|
|
33
37
|
type ResolverIssuingIdentity,
|
|
38
|
+
type ResolverPaidUsageContext,
|
|
34
39
|
type ResolverVendorAdapter,
|
|
35
40
|
type ResolverVendorTransport,
|
|
36
41
|
ResolverVendorUnavailableError,
|
|
42
|
+
attachResolverVendorPollObserver,
|
|
37
43
|
type ResolverVendorUnavailableReason,
|
|
38
44
|
resolveProviderResolverVendors,
|
|
39
45
|
resolverVendorSupports,
|
|
40
46
|
} from "./resolver-vendors/types.js";
|
|
41
47
|
import {
|
|
42
48
|
createUnsupportedResolverClient,
|
|
49
|
+
inheritResolverTelemetryBinding,
|
|
50
|
+
registerResolverTelemetryBinding,
|
|
43
51
|
RESOLVER_INSTRUMENTATION_METADATA,
|
|
44
52
|
type ResolverSolveWithRecorder,
|
|
45
53
|
} from "./resolver-shared.js";
|
|
@@ -48,11 +56,18 @@ import {
|
|
|
48
56
|
APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
|
|
49
57
|
APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
|
|
50
58
|
APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
|
|
59
|
+
APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY,
|
|
51
60
|
APIFUSE__RESOLVER__TIMEOUT_MS,
|
|
52
61
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
62
|
+
kindRequiresClientProfile,
|
|
53
63
|
} from "./resolver-config.js";
|
|
54
64
|
import { DEFAULT_STEALTH_PROFILE } from "./stealth.js";
|
|
55
65
|
import type { TraceRecorder } from "./trace.js";
|
|
66
|
+
import type {
|
|
67
|
+
ResolverTelemetryErrorClass,
|
|
68
|
+
ResolverTelemetryPhase,
|
|
69
|
+
ResolverTelemetrySink,
|
|
70
|
+
} from "./resolver-telemetry.js";
|
|
56
71
|
|
|
57
72
|
export {
|
|
58
73
|
createUnsupportedResolverClient,
|
|
@@ -63,6 +78,7 @@ export {
|
|
|
63
78
|
APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
|
|
64
79
|
APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
|
|
65
80
|
APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
|
|
81
|
+
APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY,
|
|
66
82
|
APIFUSE__RESOLVER__TIMEOUT_MS,
|
|
67
83
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
68
84
|
} from "./resolver-config.js";
|
|
@@ -109,6 +125,8 @@ type ResolverChainClient = ResolverContext & {
|
|
|
109
125
|
export interface ResolverRuntimeOptions {
|
|
110
126
|
readonly allowedHosts?: readonly string[];
|
|
111
127
|
readonly cache?: ProviderCache;
|
|
128
|
+
/** Optional request-scoped observability sink; never used as resolver identity. */
|
|
129
|
+
readonly telemetry?: ResolverTelemetrySink;
|
|
112
130
|
/** Inputs for SDK-owned lazy proxy resolution. The SDK never accepts a caller-built identity. */
|
|
113
131
|
readonly proxyIntent?: {
|
|
114
132
|
readonly mode: ProviderProxyMode;
|
|
@@ -231,6 +249,13 @@ const resolverAdapterRegistry: Partial<Record<ProviderResolverVendor, ResolverAd
|
|
|
231
249
|
timeoutMs,
|
|
232
250
|
});
|
|
233
251
|
},
|
|
252
|
+
hypersolutions(configuration, timeoutMs, allowedHosts) {
|
|
253
|
+
return createHypersolutionsResolverVendorAdapter({
|
|
254
|
+
allowedHosts,
|
|
255
|
+
apiKey: configuration,
|
|
256
|
+
timeoutMs,
|
|
257
|
+
});
|
|
258
|
+
},
|
|
234
259
|
};
|
|
235
260
|
|
|
236
261
|
export const RESOLVER_ADAPTER_REGISTRY: Partial<
|
|
@@ -285,12 +310,12 @@ type ResolverChainEntry = {
|
|
|
285
310
|
};
|
|
286
311
|
|
|
287
312
|
function normalizedEnvValue(env: EnvLike, key: string): string | undefined {
|
|
288
|
-
const value = env
|
|
313
|
+
const value = readDiagnosticEnv(key, env)?.trim();
|
|
289
314
|
return value ? value : undefined;
|
|
290
315
|
}
|
|
291
316
|
|
|
292
317
|
function readPositiveIntegerEnv(env: EnvLike, name: string): string | undefined {
|
|
293
|
-
const raw = env
|
|
318
|
+
const raw = readDiagnosticEnv(name, env)?.trim();
|
|
294
319
|
if (!raw) return undefined;
|
|
295
320
|
if (!/^[1-9]\d*$/.test(raw)) {
|
|
296
321
|
throw new Error(`${name} must be a positive integer`);
|
|
@@ -302,6 +327,12 @@ function assertDeclaredKind(
|
|
|
302
327
|
requestedKind: ProviderChallengeKind,
|
|
303
328
|
declaredKinds: readonly ProviderChallengeKind[],
|
|
304
329
|
): void {
|
|
330
|
+
if (!Object.hasOwn(RESOLVER_CHALLENGE_BINDINGS, requestedKind)) {
|
|
331
|
+
throw new ProviderError(`Unknown resolver kind "${requestedKind}"`, {
|
|
332
|
+
code: "RESOLVER_KIND_NOT_DECLARED",
|
|
333
|
+
fix: "Use a challenge kind exported by @apifuse/provider-sdk.",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
305
336
|
if (declaredKinds.includes(requestedKind)) return;
|
|
306
337
|
|
|
307
338
|
const declared = declaredKinds.length > 0 ? declaredKinds.join(", ") : "none";
|
|
@@ -365,23 +396,26 @@ function throwUnsupportedKind(kind: ProviderChallengeKind, usingDefaultVendors:
|
|
|
365
396
|
});
|
|
366
397
|
}
|
|
367
398
|
|
|
368
|
-
function throwExhausted(
|
|
399
|
+
function throwExhausted(
|
|
400
|
+
challengeKind: ProviderChallengeKind,
|
|
401
|
+
attempts: readonly ResolverChainAttempt[],
|
|
402
|
+
executedAttemptCount = attempts.length,
|
|
403
|
+
): never {
|
|
369
404
|
const hasMissingChallengeInput = attempts.some(
|
|
370
405
|
(attempt) => attempt.reason === "missing_challenge_input",
|
|
371
406
|
);
|
|
372
|
-
|
|
373
|
-
.map(({ vendor, reason, missingFields }) =>
|
|
374
|
-
missingFields === undefined || missingFields.length === 0
|
|
375
|
-
? `${vendor}: ${reason}`
|
|
376
|
-
: `${vendor}: ${reason} (missing fields: ${missingFields.join(", ")})`,
|
|
377
|
-
)
|
|
378
|
-
.join(", ");
|
|
379
|
-
throw new ProviderError(`Resolver vendor chain exhausted: ${summary}`, {
|
|
407
|
+
throw new ProviderError("The challenge could not be resolved.", {
|
|
380
408
|
code: "RESOLVER_CHAIN_EXHAUSTED",
|
|
381
409
|
fix: hasMissingChallengeInput
|
|
382
|
-
? "Capture
|
|
383
|
-
: "
|
|
384
|
-
|
|
410
|
+
? "Capture all required challenge fields and retry the request."
|
|
411
|
+
: "Retry the request or contact support if the challenge continues.",
|
|
412
|
+
retryable: false,
|
|
413
|
+
details: {
|
|
414
|
+
challengeKind,
|
|
415
|
+
attempts: executedAttemptCount,
|
|
416
|
+
outcome: "exhausted",
|
|
417
|
+
retryable: false,
|
|
418
|
+
},
|
|
385
419
|
});
|
|
386
420
|
}
|
|
387
421
|
|
|
@@ -451,6 +485,16 @@ function restrictResolverTransport(
|
|
|
451
485
|
allowedHosts: readonly string[],
|
|
452
486
|
): ResolverVendorTransport {
|
|
453
487
|
return {
|
|
488
|
+
sessionHeaders: transport.sessionHeaders,
|
|
489
|
+
...(transport.getCookie
|
|
490
|
+
? {
|
|
491
|
+
getCookie(name, url) {
|
|
492
|
+
// Jar reads are gated like fetches: an adapter may only read state for declared hosts.
|
|
493
|
+
assertResolverHostAllowed(url, allowedHosts);
|
|
494
|
+
return transport.getCookie?.(name, url);
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
: {}),
|
|
454
498
|
async fetch(url, init) {
|
|
455
499
|
// Empty declarations remain deny-by-default, matching the adapter-factory/browser path.
|
|
456
500
|
assertResolverHostAllowed(url, allowedHosts);
|
|
@@ -494,10 +538,21 @@ function safePhase(phase: string | undefined): string | undefined {
|
|
|
494
538
|
return phase !== undefined && /^[a-z\d_.:-]{1,64}$/i.test(phase) ? phase : undefined;
|
|
495
539
|
}
|
|
496
540
|
|
|
497
|
-
function unavailableAttempt(
|
|
498
|
-
|
|
541
|
+
function unavailableAttempt(
|
|
542
|
+
error: ResolverVendorUnavailableError,
|
|
543
|
+
forTelemetry = false,
|
|
544
|
+
): ResolverChainAttempt {
|
|
545
|
+
// Preserve the full input for the collector's redact-then-bound path. Span
|
|
546
|
+
// attributes retain their existing sanitization and phase validation.
|
|
547
|
+
const cause =
|
|
548
|
+
forTelemetry && error.cause !== undefined
|
|
549
|
+
? {
|
|
550
|
+
name: error.cause instanceof Error ? error.cause.name : "Error",
|
|
551
|
+
message: error.cause instanceof Error ? error.cause.message : String(error.cause),
|
|
552
|
+
}
|
|
553
|
+
: safeCause(error);
|
|
499
554
|
const upstreamHost = safeUpstreamHost(error.upstreamHost);
|
|
500
|
-
const phase = safePhase(error.phase);
|
|
555
|
+
const phase = forTelemetry ? error.phase : safePhase(error.phase);
|
|
501
556
|
const round =
|
|
502
557
|
Number.isSafeInteger(error.round) && (error.round as number) > 0 ? error.round : undefined;
|
|
503
558
|
return {
|
|
@@ -524,6 +579,125 @@ function unavailableSpanAttributes(error: ResolverVendorUnavailableError): Recor
|
|
|
524
579
|
};
|
|
525
580
|
}
|
|
526
581
|
|
|
582
|
+
type ResolverAttemptDiagnostics = {
|
|
583
|
+
phase?: string;
|
|
584
|
+
pollCount?: number;
|
|
585
|
+
vendorErrorCode?: string;
|
|
586
|
+
vendorErrorDescription?: string;
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
function resolverTelemetryPhase(value: unknown): ResolverTelemetryPhase | undefined {
|
|
590
|
+
return value === "create_task" ||
|
|
591
|
+
value === "poll_result" ||
|
|
592
|
+
value === "cleanup" ||
|
|
593
|
+
value === "measure_ip" ||
|
|
594
|
+
value === "fetch_script" ||
|
|
595
|
+
value === "generate_payload" ||
|
|
596
|
+
value === "post_payload"
|
|
597
|
+
? value
|
|
598
|
+
: undefined;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function diagnosticString(value: unknown): string | undefined {
|
|
602
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function diagnosticCount(value: unknown): number | undefined {
|
|
606
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
607
|
+
? Math.max(0, Math.floor(value))
|
|
608
|
+
: undefined;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function createResolverTelemetryTraceRecorder(input: {
|
|
612
|
+
base?: TraceRecorder;
|
|
613
|
+
vendor: ProviderResolverVendor;
|
|
614
|
+
attemptIndex: number;
|
|
615
|
+
telemetry?: ResolverTelemetrySink;
|
|
616
|
+
diagnostics: ResolverAttemptDiagnostics;
|
|
617
|
+
}): TraceRecorder | undefined {
|
|
618
|
+
if (!input.base && !input.telemetry) return undefined;
|
|
619
|
+
const recorder: TraceRecorder = {
|
|
620
|
+
async runSpan<T>(
|
|
621
|
+
name: string,
|
|
622
|
+
fn: () => Promise<T> | T,
|
|
623
|
+
spanOptions: {
|
|
624
|
+
attributes?: Record<string, unknown>;
|
|
625
|
+
onSuccess?: (value: T) => Record<string, unknown> | undefined;
|
|
626
|
+
onError?: (error: unknown) => Record<string, unknown> | undefined;
|
|
627
|
+
} = {},
|
|
628
|
+
) {
|
|
629
|
+
const adapterPhase = name.startsWith("resolver.vendor.")
|
|
630
|
+
? diagnosticString(name.replace("resolver.vendor.", ""))
|
|
631
|
+
: undefined;
|
|
632
|
+
const phase = resolverTelemetryPhase(adapterPhase);
|
|
633
|
+
const startedAt = Date.now();
|
|
634
|
+
let attributes: Record<string, unknown> | undefined;
|
|
635
|
+
let spanOutcome: "ok" | "error" = "ok";
|
|
636
|
+
const wrappedOptions = {
|
|
637
|
+
...spanOptions,
|
|
638
|
+
onSuccess(value: T) {
|
|
639
|
+
attributes = spanOptions.onSuccess?.(value);
|
|
640
|
+
return attributes;
|
|
641
|
+
},
|
|
642
|
+
onError(error: unknown) {
|
|
643
|
+
attributes = spanOptions.onError?.(error);
|
|
644
|
+
return attributes;
|
|
645
|
+
},
|
|
646
|
+
};
|
|
647
|
+
try {
|
|
648
|
+
if (input.base) return await input.base.runSpan(name, fn, wrappedOptions);
|
|
649
|
+
const value = await fn();
|
|
650
|
+
attributes = spanOptions.onSuccess?.(value);
|
|
651
|
+
return value;
|
|
652
|
+
} catch (error) {
|
|
653
|
+
spanOutcome = "error";
|
|
654
|
+
if (!input.base) attributes = spanOptions.onError?.(error);
|
|
655
|
+
throw error;
|
|
656
|
+
} finally {
|
|
657
|
+
if (adapterPhase && adapterPhase !== "attempt") {
|
|
658
|
+
const reportedPhase = diagnosticString(attributes?.transport_phase) ?? adapterPhase;
|
|
659
|
+
const pollCount = diagnosticCount(attributes?.poll_count);
|
|
660
|
+
const vendorErrorCode = diagnosticString(attributes?.vendor_error_code);
|
|
661
|
+
const vendorErrorDescription = diagnosticString(
|
|
662
|
+
attributes?.vendor_error_description ?? attributes?.error_message,
|
|
663
|
+
);
|
|
664
|
+
if (phase === "cleanup") {
|
|
665
|
+
input.telemetry?.recordVendorAttempt({
|
|
666
|
+
vendor: input.vendor,
|
|
667
|
+
phase: "cleanup",
|
|
668
|
+
diagnostics: { phase: reportedPhase, attemptIndex: input.attemptIndex },
|
|
669
|
+
outcome: spanOutcome,
|
|
670
|
+
ms: Date.now() - startedAt,
|
|
671
|
+
...(pollCount === undefined ? {} : { pollCount }),
|
|
672
|
+
...(vendorErrorCode ? { vendorErrorCode } : {}),
|
|
673
|
+
...(vendorErrorDescription ? { vendorErrorDescription } : {}),
|
|
674
|
+
...(spanOutcome === "error" ? { errorClass: "unexpected" as const } : {}),
|
|
675
|
+
});
|
|
676
|
+
} else {
|
|
677
|
+
input.diagnostics.phase = reportedPhase;
|
|
678
|
+
if (pollCount !== undefined) input.diagnostics.pollCount = pollCount;
|
|
679
|
+
if (vendorErrorCode) input.diagnostics.vendorErrorCode = vendorErrorCode;
|
|
680
|
+
if (vendorErrorDescription) {
|
|
681
|
+
input.diagnostics.vendorErrorDescription = vendorErrorDescription;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
};
|
|
688
|
+
return attachResolverVendorPollObserver(recorder, () => {
|
|
689
|
+
input.diagnostics.pollCount = (input.diagnostics.pollCount ?? 0) + 1;
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function resolverAttemptErrorClass(
|
|
694
|
+
error: unknown,
|
|
695
|
+
signal: AbortSignal,
|
|
696
|
+
): ResolverTelemetryErrorClass {
|
|
697
|
+
if (signal.aborted) return "aborted";
|
|
698
|
+
return error instanceof ResolverVendorUnavailableError ? error.reason : "unexpected";
|
|
699
|
+
}
|
|
700
|
+
|
|
527
701
|
function challengeOrigin(challenge: ProviderChallenge): string {
|
|
528
702
|
return new URL(challenge.pageUrl).origin;
|
|
529
703
|
}
|
|
@@ -710,13 +884,13 @@ async function cacheResolverSolution(
|
|
|
710
884
|
solution: ChallengeSolution,
|
|
711
885
|
identity: ResolverIssuingIdentity,
|
|
712
886
|
identityScope: string | undefined,
|
|
713
|
-
): Promise<
|
|
714
|
-
if (!resolverChallengeIsCacheable(challenge)) return;
|
|
887
|
+
): Promise<{ written: boolean; reason?: "no_expires" | "not_cacheable" }> {
|
|
888
|
+
if (!resolverChallengeIsCacheable(challenge)) return { written: false, reason: "not_cacheable" };
|
|
715
889
|
const expiresAtMs = solutionExpiryMs(solution);
|
|
716
890
|
const now = Date.now();
|
|
717
|
-
if (expiresAtMs === undefined) return;
|
|
891
|
+
if (expiresAtMs === undefined) return { written: false, reason: "no_expires" };
|
|
718
892
|
const ttlMs = Math.floor(expiresAtMs - now);
|
|
719
|
-
if (ttlMs <= MIN_RESOLVER_CACHE_TTL_MS) return;
|
|
893
|
+
if (ttlMs <= MIN_RESOLVER_CACHE_TTL_MS) return { written: false, reason: "no_expires" };
|
|
720
894
|
|
|
721
895
|
const scopedDigest =
|
|
722
896
|
identityScope !== undefined && resolverChallengeIsIdentityScoped(challenge)
|
|
@@ -727,7 +901,7 @@ async function cacheResolverSolution(
|
|
|
727
901
|
scopedDigest === undefined &&
|
|
728
902
|
identity.proxyUrl === undefined
|
|
729
903
|
) {
|
|
730
|
-
return;
|
|
904
|
+
return { written: false, reason: "not_cacheable" };
|
|
731
905
|
}
|
|
732
906
|
const issuerDigest = scopedDigest ?? resolverIdentityDigest(identity);
|
|
733
907
|
await cache.set(
|
|
@@ -736,7 +910,7 @@ async function cacheResolverSolution(
|
|
|
736
910
|
{ ttlMs },
|
|
737
911
|
);
|
|
738
912
|
rememberSolutionIssuer(solution, issuerDigest);
|
|
739
|
-
if (scopedDigest !== undefined || identity.proxyUrl !== undefined) return;
|
|
913
|
+
if (scopedDigest !== undefined || identity.proxyUrl !== undefined) return { written: true };
|
|
740
914
|
|
|
741
915
|
const indexKey = resolverSolutionIndexCacheKey(cache, challenge);
|
|
742
916
|
const current = await cache.get(indexKey);
|
|
@@ -750,6 +924,7 @@ async function cacheResolverSolution(
|
|
|
750
924
|
],
|
|
751
925
|
now,
|
|
752
926
|
);
|
|
927
|
+
return { written: true };
|
|
753
928
|
}
|
|
754
929
|
|
|
755
930
|
async function invalidateResolverSolutionWithOutcome(
|
|
@@ -866,6 +1041,10 @@ async function resolveResolverIdentity(
|
|
|
866
1041
|
}
|
|
867
1042
|
}
|
|
868
1043
|
|
|
1044
|
+
function createChainTelemetryBinding(options: Parameters<typeof createResolverChainClient>[0]) {
|
|
1045
|
+
return (telemetry: ResolverTelemetrySink) => createResolverChainClient({ ...options, telemetry });
|
|
1046
|
+
}
|
|
1047
|
+
|
|
869
1048
|
function createResolverChainClient(options: {
|
|
870
1049
|
readonly kinds: readonly ProviderChallengeKind[];
|
|
871
1050
|
readonly entries: readonly ResolverChainEntry[];
|
|
@@ -879,128 +1058,284 @@ function createResolverChainClient(options: {
|
|
|
879
1058
|
readonly createTransport?: ResolverRuntimeOptions["createTransport"];
|
|
880
1059
|
readonly clientProfile?: string;
|
|
881
1060
|
readonly allowedHosts?: readonly string[];
|
|
1061
|
+
readonly telemetry?: ResolverTelemetrySink;
|
|
882
1062
|
}): ResolverChainClient {
|
|
883
1063
|
assertClientProfileTransportContract(options.clientProfile, options.transport);
|
|
1064
|
+
const cache =
|
|
1065
|
+
options.cache && !isProviderCacheBypassed(options.cache) ? options.cache : undefined;
|
|
884
1066
|
const client: ResolverChainClient = {
|
|
885
1067
|
async solve(
|
|
886
1068
|
challenge: ProviderChallenge,
|
|
887
1069
|
signal: AbortSignal = new AbortController().signal,
|
|
888
1070
|
traceRecorder?: TraceRecorder,
|
|
889
1071
|
) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1072
|
+
const solveStartedAt = Date.now();
|
|
1073
|
+
let terminalOutcome: "solved" | "cached" | "exhausted" | "aborted" | "error" = "error";
|
|
1074
|
+
try {
|
|
1075
|
+
assertDeclaredKind(challenge.kind, options.kinds);
|
|
1076
|
+
if (options.unavailableReason) {
|
|
1077
|
+
throw new ProviderError(options.unavailableReason, {
|
|
1078
|
+
code: "RESOLVER_UNAVAILABLE",
|
|
1079
|
+
fix: "Configure at least one declared resolver vendor or provide a test ResolverContext override.",
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
897
1082
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1083
|
+
const supportingEntries = options.entries.filter((entry) => entry.supports(challenge.kind));
|
|
1084
|
+
if (supportingEntries.length === 0) {
|
|
1085
|
+
throwUnsupportedKind(challenge.kind, options.usingDefaultVendors ?? false);
|
|
1086
|
+
}
|
|
1087
|
+
if (kindRequiresClientProfile(challenge.kind) && !options.clientProfile?.trim()) {
|
|
1088
|
+
options.telemetry?.recordIdentity({ source: "none", failure: "missing_client_profile" });
|
|
1089
|
+
throwExhausted(
|
|
1090
|
+
challenge.kind,
|
|
1091
|
+
supportingEntries.map((entry) => ({
|
|
1092
|
+
vendor: entry.id,
|
|
1093
|
+
reason: "missing_client_profile",
|
|
1094
|
+
})),
|
|
1095
|
+
0,
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
signal.throwIfAborted();
|
|
1099
|
+
const identityResolution = options.proxyIntent
|
|
1100
|
+
? await resolveResolverIdentity(options.proxyIntent)
|
|
1101
|
+
: { identity: options.identity };
|
|
1102
|
+
const identity = identityResolution.identity;
|
|
1103
|
+
options.telemetry?.recordIdentity({
|
|
1104
|
+
// For SBSD, "declared" identifies the configured client profile and its bound
|
|
1105
|
+
// session identity; the engine independently supplies solver credentials.
|
|
1106
|
+
source:
|
|
1107
|
+
kindRequiresClientProfile(challenge.kind) && options.clientProfile
|
|
1108
|
+
? "declared"
|
|
1109
|
+
: options.proxyIntent
|
|
1110
|
+
? (identityResolution.userAgentSource ?? "defaulted")
|
|
1111
|
+
: identity
|
|
1112
|
+
? "declared"
|
|
1113
|
+
: "none",
|
|
1114
|
+
...(identityResolution.unavailableReason
|
|
1115
|
+
? { failure: identityResolution.unavailableReason }
|
|
1116
|
+
: {}),
|
|
1117
|
+
});
|
|
1118
|
+
signal.throwIfAborted();
|
|
1119
|
+
const requiredProxyIdentityMissing =
|
|
1120
|
+
options.proxyIntent?.mode === "required" && identity === undefined;
|
|
1121
|
+
if (requiredProxyIdentityMissing) {
|
|
1122
|
+
const reason = identityResolution.unavailableReason ?? "missing_proxy_identity";
|
|
1123
|
+
for (const [entryIndex, entry] of supportingEntries.entries()) {
|
|
1124
|
+
options.telemetry?.recordVendorAttempt({
|
|
1125
|
+
vendor: entry.id,
|
|
1126
|
+
phase: "create_task",
|
|
1127
|
+
diagnostics: { attemptIndex: entryIndex + 1 },
|
|
1128
|
+
outcome: "error",
|
|
1129
|
+
ms: 0,
|
|
1130
|
+
errorClass: reason,
|
|
1131
|
+
});
|
|
1132
|
+
const next = supportingEntries[entryIndex + 1];
|
|
1133
|
+
if (next) {
|
|
1134
|
+
options.telemetry?.recordFailover({ from: entry.id, to: next.id, reason });
|
|
946
1135
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
options.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1136
|
+
}
|
|
1137
|
+
throwExhausted(
|
|
1138
|
+
challenge.kind,
|
|
1139
|
+
supportingEntries.map((entry) => ({
|
|
1140
|
+
vendor: entry.id,
|
|
1141
|
+
reason,
|
|
1142
|
+
})),
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
if (!cache) {
|
|
1146
|
+
options.telemetry?.recordCacheRead({ status: "disabled", challengeKind: challenge.kind });
|
|
1147
|
+
} else if (!resolverChallengeIsCacheable(challenge)) {
|
|
1148
|
+
options.telemetry?.recordCacheRead({
|
|
1149
|
+
status: "not_cacheable",
|
|
1150
|
+
challengeKind: challenge.kind,
|
|
1151
|
+
});
|
|
1152
|
+
} else {
|
|
1153
|
+
const cached = await findCachedSolution(
|
|
1154
|
+
cache,
|
|
1155
|
+
challenge,
|
|
1156
|
+
identity,
|
|
1157
|
+
options.identityScope,
|
|
1158
|
+
);
|
|
1159
|
+
options.telemetry?.recordCacheRead({
|
|
1160
|
+
status: cached ? "hit" : "miss",
|
|
1161
|
+
challengeKind: challenge.kind,
|
|
1162
|
+
});
|
|
1163
|
+
if (cached) {
|
|
1164
|
+
terminalOutcome = "cached";
|
|
1165
|
+
return cached;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const attempts: ResolverChainAttempt[] = [];
|
|
1169
|
+
for (const [entryIndex, entry] of supportingEntries.entries()) {
|
|
1170
|
+
const adapter = entry.createAdapter();
|
|
1171
|
+
const attemptStartedAt = Date.now();
|
|
1172
|
+
const diagnostics: ResolverAttemptDiagnostics = {};
|
|
1173
|
+
const telemetryTraceRecorder = createResolverTelemetryTraceRecorder({
|
|
1174
|
+
base: traceRecorder,
|
|
1175
|
+
vendor: adapter.id,
|
|
1176
|
+
attemptIndex: entryIndex + 1,
|
|
1177
|
+
telemetry: options.telemetry,
|
|
1178
|
+
diagnostics,
|
|
1179
|
+
});
|
|
1180
|
+
let solution: ChallengeSolution;
|
|
1181
|
+
try {
|
|
1182
|
+
const solveAttempt = () => {
|
|
1183
|
+
const requiresTransport = adapterRequiresTransport(adapter, challenge.kind);
|
|
1184
|
+
const unrestrictedTransport =
|
|
1185
|
+
options.transport ??
|
|
1186
|
+
(requiresTransport
|
|
1187
|
+
? options.createTransport?.({
|
|
1188
|
+
clientProfile: options.clientProfile,
|
|
1189
|
+
identityScope: options.identityScope,
|
|
1190
|
+
})
|
|
1191
|
+
: undefined);
|
|
1192
|
+
if (requiresTransport && unrestrictedTransport === undefined) {
|
|
1193
|
+
throw new ResolverVendorUnavailableError(adapter.id, "missing_transport");
|
|
1194
|
+
}
|
|
1195
|
+
const transport = unrestrictedTransport
|
|
1196
|
+
? restrictResolverTransport(unrestrictedTransport, [
|
|
1197
|
+
...(options.allowedHosts ?? []),
|
|
1198
|
+
...(adapter.transportAllowedHosts ?? []),
|
|
1199
|
+
])
|
|
1200
|
+
: undefined;
|
|
1201
|
+
const usage: ResolverPaidUsageContext = {
|
|
1202
|
+
// Position in the declared chain, not in the kind-filtered list.
|
|
1203
|
+
vendorIndex: options.entries.indexOf(entry) + 1,
|
|
1204
|
+
...(options.identityScope
|
|
1205
|
+
? {
|
|
1206
|
+
resolverIdentityScope: createHash("sha256")
|
|
1207
|
+
.update(options.identityScope, "utf8")
|
|
1208
|
+
.digest("hex"),
|
|
1209
|
+
}
|
|
1210
|
+
: {}),
|
|
1211
|
+
};
|
|
1212
|
+
return adapter.solve(
|
|
982
1213
|
challenge,
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1214
|
+
identity,
|
|
1215
|
+
signal,
|
|
1216
|
+
telemetryTraceRecorder,
|
|
1217
|
+
transport,
|
|
1218
|
+
usage,
|
|
986
1219
|
);
|
|
1220
|
+
};
|
|
1221
|
+
solution = traceRecorder
|
|
1222
|
+
? await traceRecorder.runSpan("resolver.vendor.attempt", solveAttempt, {
|
|
1223
|
+
attributes: {
|
|
1224
|
+
vendor: adapter.id,
|
|
1225
|
+
challenge_kind: challenge.kind,
|
|
1226
|
+
client_profile: options.clientProfile,
|
|
1227
|
+
resolver_identity_source: identityResolution.userAgentSource,
|
|
1228
|
+
},
|
|
1229
|
+
onError(error) {
|
|
1230
|
+
return error instanceof ResolverVendorUnavailableError
|
|
1231
|
+
? unavailableSpanAttributes(error)
|
|
1232
|
+
: undefined;
|
|
1233
|
+
},
|
|
1234
|
+
})
|
|
1235
|
+
: await solveAttempt();
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
const unavailable = error instanceof ResolverVendorUnavailableError ? error : undefined;
|
|
1238
|
+
const attempt = unavailable ? unavailableAttempt(unavailable, true) : undefined;
|
|
1239
|
+
options.telemetry?.recordVendorAttempt({
|
|
1240
|
+
vendor: adapter.id,
|
|
1241
|
+
phase:
|
|
1242
|
+
resolverTelemetryPhase(unavailable?.phase) ??
|
|
1243
|
+
resolverTelemetryPhase(diagnostics.phase) ??
|
|
1244
|
+
"create_task",
|
|
1245
|
+
diagnostics: {
|
|
1246
|
+
...attempt,
|
|
1247
|
+
attemptIndex: entryIndex + 1,
|
|
1248
|
+
phase: attempt?.phase ?? diagnostics.phase,
|
|
1249
|
+
},
|
|
1250
|
+
outcome: "error",
|
|
1251
|
+
ms: Date.now() - attemptStartedAt,
|
|
1252
|
+
errorClass: resolverAttemptErrorClass(error, signal),
|
|
1253
|
+
...(diagnostics.pollCount === undefined ? {} : { pollCount: diagnostics.pollCount }),
|
|
1254
|
+
...(diagnostics.vendorErrorCode
|
|
1255
|
+
? { vendorErrorCode: diagnostics.vendorErrorCode }
|
|
1256
|
+
: {}),
|
|
1257
|
+
...(diagnostics.vendorErrorDescription
|
|
1258
|
+
? { vendorErrorDescription: diagnostics.vendorErrorDescription }
|
|
1259
|
+
: {}),
|
|
1260
|
+
});
|
|
1261
|
+
signal.throwIfAborted();
|
|
1262
|
+
if (!unavailable || !attempt) throw error;
|
|
1263
|
+
attempts.push(attempt);
|
|
1264
|
+
const next = supportingEntries[entryIndex + 1];
|
|
1265
|
+
if (next) {
|
|
1266
|
+
options.telemetry?.recordFailover({
|
|
1267
|
+
from: adapter.id,
|
|
1268
|
+
to: next.id,
|
|
1269
|
+
reason: unavailable.reason,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
options.telemetry?.recordVendorAttempt({
|
|
1275
|
+
vendor: adapter.id,
|
|
1276
|
+
phase: resolverTelemetryPhase(diagnostics.phase) ?? "poll_result",
|
|
1277
|
+
diagnostics: { attemptIndex: entryIndex + 1, phase: diagnostics.phase },
|
|
1278
|
+
outcome: "ok",
|
|
1279
|
+
ms: Date.now() - attemptStartedAt,
|
|
1280
|
+
...(diagnostics.pollCount === undefined ? {} : { pollCount: diagnostics.pollCount }),
|
|
1281
|
+
});
|
|
1282
|
+
if (cache) {
|
|
1283
|
+
try {
|
|
1284
|
+
let cacheWrite: { written: boolean; reason?: "no_expires" | "not_cacheable" };
|
|
1285
|
+
if (!resolverChallengeIsCacheable(challenge)) {
|
|
1286
|
+
cacheWrite = { written: false, reason: "not_cacheable" };
|
|
1287
|
+
} else if (solution.form !== "cookies" || solutionExpiryMs(solution) === undefined) {
|
|
1288
|
+
cacheWrite = { written: false, reason: "no_expires" };
|
|
1289
|
+
} else {
|
|
1290
|
+
const issuingIdentity =
|
|
1291
|
+
adapter.getIssuingIdentity?.(solution, identity, challenge) ??
|
|
1292
|
+
resolverChallengeIssuingIdentity(challenge, {
|
|
1293
|
+
...(identity ? { proxyUrl: identity.proxyUrl } : {}),
|
|
1294
|
+
userAgent: solution.userAgent,
|
|
1295
|
+
});
|
|
1296
|
+
cacheWrite = issuingIdentity
|
|
1297
|
+
? await cacheResolverSolution(
|
|
1298
|
+
cache,
|
|
1299
|
+
challenge,
|
|
1300
|
+
solution,
|
|
1301
|
+
issuingIdentity,
|
|
1302
|
+
options.identityScope,
|
|
1303
|
+
)
|
|
1304
|
+
: { written: false, reason: "not_cacheable" };
|
|
1305
|
+
}
|
|
1306
|
+
options.telemetry?.recordCacheWrite(cacheWrite);
|
|
1307
|
+
} catch (error) {
|
|
1308
|
+
options.telemetry?.recordCacheWrite({ written: false, reason: "error" });
|
|
1309
|
+
throw error;
|
|
987
1310
|
}
|
|
988
1311
|
}
|
|
989
1312
|
rememberSolutionSource(solution, "vendor");
|
|
1313
|
+
// Adapter success includes SBSD payload acceptance with verified:false.
|
|
1314
|
+
// The caller reports protected-refetch verification separately.
|
|
1315
|
+
terminalOutcome = "solved";
|
|
990
1316
|
return solution;
|
|
991
|
-
} catch (error) {
|
|
992
|
-
signal.throwIfAborted();
|
|
993
|
-
if (!(error instanceof ResolverVendorUnavailableError)) throw error;
|
|
994
|
-
// Every vendor-unavailable result, including missing_challenge_input, falls
|
|
995
|
-
// through so another adapter can solve with a different input contract.
|
|
996
|
-
attempts.push(unavailableAttempt(error));
|
|
997
1317
|
}
|
|
998
|
-
}
|
|
999
1318
|
|
|
1000
|
-
|
|
1319
|
+
throwExhausted(challenge.kind, attempts);
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
terminalOutcome = signal.aborted
|
|
1322
|
+
? "aborted"
|
|
1323
|
+
: error instanceof ProviderError && error.code === "RESOLVER_CHAIN_EXHAUSTED"
|
|
1324
|
+
? "exhausted"
|
|
1325
|
+
: "error";
|
|
1326
|
+
throw error;
|
|
1327
|
+
} finally {
|
|
1328
|
+
options.telemetry?.recordOutcome({
|
|
1329
|
+
outcome: terminalOutcome,
|
|
1330
|
+
solveMs: Date.now() - solveStartedAt,
|
|
1331
|
+
challengeKind: challenge.kind,
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1001
1334
|
},
|
|
1002
1335
|
};
|
|
1003
|
-
resolverCaches.set(client,
|
|
1336
|
+
resolverCaches.set(client, cache ?? null);
|
|
1337
|
+
const reusableOptions = { ...options, telemetry: undefined };
|
|
1338
|
+
registerResolverTelemetryBinding(client, createChainTelemetryBinding(reusableOptions));
|
|
1004
1339
|
return client;
|
|
1005
1340
|
}
|
|
1006
1341
|
|
|
@@ -1015,6 +1350,7 @@ export function createResolverClient(options: {
|
|
|
1015
1350
|
readonly createTransport?: ResolverRuntimeOptions["createTransport"];
|
|
1016
1351
|
readonly clientProfile?: string;
|
|
1017
1352
|
readonly allowedHosts?: readonly string[];
|
|
1353
|
+
readonly telemetry?: ResolverTelemetrySink;
|
|
1018
1354
|
}): ResolverChainClient {
|
|
1019
1355
|
return createResolverChainClient({
|
|
1020
1356
|
kinds: options.kinds,
|
|
@@ -1031,6 +1367,7 @@ export function createResolverClient(options: {
|
|
|
1031
1367
|
createTransport: options.createTransport,
|
|
1032
1368
|
clientProfile: options.clientProfile,
|
|
1033
1369
|
allowedHosts: options.allowedHosts,
|
|
1370
|
+
telemetry: options.telemetry,
|
|
1034
1371
|
});
|
|
1035
1372
|
}
|
|
1036
1373
|
|
|
@@ -1058,7 +1395,9 @@ function resolveVendorAvailability(
|
|
|
1058
1395
|
? APIFUSE__RESOLVER__2CAPTCHA__API_KEY
|
|
1059
1396
|
: vendor === "capsolver"
|
|
1060
1397
|
? APIFUSE__RESOLVER__CAPSOLVER__API_KEY
|
|
1061
|
-
:
|
|
1398
|
+
: vendor === "capmonster"
|
|
1399
|
+
? APIFUSE__RESOLVER__CAPMONSTER__API_KEY
|
|
1400
|
+
: APIFUSE__RESOLVER__HYPERSOLUTIONS__API_KEY;
|
|
1062
1401
|
|
|
1063
1402
|
const configuration = normalizedEnvValue(env, envKey);
|
|
1064
1403
|
return configuration
|
|
@@ -1083,6 +1422,7 @@ export function bindResolverSignal(
|
|
|
1083
1422
|
if (resolverCaches.has(resolver)) {
|
|
1084
1423
|
resolverCaches.set(boundResolver, resolverCaches.get(resolver) ?? null);
|
|
1085
1424
|
}
|
|
1425
|
+
inheritResolverTelemetryBinding(resolver, boundResolver);
|
|
1086
1426
|
return boundResolver;
|
|
1087
1427
|
}
|
|
1088
1428
|
|
|
@@ -1103,6 +1443,7 @@ function createResolverClientFromEnvInternal(
|
|
|
1103
1443
|
kinds: config.kinds,
|
|
1104
1444
|
entries: [],
|
|
1105
1445
|
unavailableReason: "Provider resolver vendor chain is empty",
|
|
1446
|
+
telemetry: options.telemetry,
|
|
1106
1447
|
});
|
|
1107
1448
|
}
|
|
1108
1449
|
|
|
@@ -1135,6 +1476,7 @@ function createResolverClientFromEnvInternal(
|
|
|
1135
1476
|
createTransport: options.createTransport,
|
|
1136
1477
|
clientProfile: config.clientProfile,
|
|
1137
1478
|
allowedHosts,
|
|
1479
|
+
telemetry: options.telemetry,
|
|
1138
1480
|
});
|
|
1139
1481
|
}
|
|
1140
1482
|
|