@apifuse/provider-sdk 2.2.0-beta.47 → 2.2.0-beta.48
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 +4 -0
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/capsolver.js +6 -0
- package/dist/runtime/resolver-vendors/twocaptcha.js +1 -0
- package/dist/runtime/resolver.d.ts +12 -0
- package/dist/runtime/resolver.js +42 -8
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/runtime/resolver-public.ts +3 -0
- package/src/runtime/resolver-vendors/capsolver.ts +8 -1
- package/src/runtime/resolver-vendors/twocaptcha.ts +1 -0
- package/src/runtime/resolver.ts +65 -16
- package/src/types.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, } from "./resolver.js";
|
|
1
|
+
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, type ResolverSolutionSource, } from "./resolver.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
|
|
1
|
+
export { APIFUSE__CDP_POOL__URL, APIFUSE__RESOLVER__2CAPTCHA__API_KEY, APIFUSE__RESOLVER__CAPMONSTER__API_KEY, APIFUSE__RESOLVER__CAPSOLVER__API_KEY, APIFUSE__RESOLVER__TIMEOUT_MS, bindResolverSignal, createResolverClient, createResolverClientFromEnv, createUnsupportedResolverClient, DEFAULT_RESOLVER_VENDOR_PREFERENCE, DEFAULT_RESOLVER_TIMEOUT_MS, getResolverSolutionSource, invalidateCachedResolverSolution, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, resolveProviderResolverVendors, } from "./resolver.js";
|
|
@@ -7,6 +7,11 @@ const CAPSOLVER_VENDOR_ID = "capsolver";
|
|
|
7
7
|
const DEFAULT_CAPSOLVER_BASE_URL = "https://api.capsolver.com";
|
|
8
8
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
9
9
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
10
|
+
const SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND = {
|
|
11
|
+
// CapSolver omits expiry, so use one conservative hour despite measured
|
|
12
|
+
// AWS WAF lifetimes of days.
|
|
13
|
+
aws_waf: 60 * 60 * 1_000,
|
|
14
|
+
};
|
|
10
15
|
class CapsolverSolveTimeoutError extends Error {
|
|
11
16
|
constructor() {
|
|
12
17
|
super("Capsolver resolver solve budget elapsed");
|
|
@@ -483,6 +488,7 @@ export function createCapsolverResolverVendorAdapter(options) {
|
|
|
483
488
|
form: "cookies",
|
|
484
489
|
cookies: { "aws-waf-token": solutionValue },
|
|
485
490
|
userAgent: identity?.userAgent ?? getStealthProfile(DEFAULT_PROFILE).userAgent,
|
|
491
|
+
sdkEstimatedExpires: (now() + SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND.aws_waf) / 1_000,
|
|
486
492
|
}
|
|
487
493
|
: { form: "token", token: solutionValue };
|
|
488
494
|
}
|
|
@@ -365,6 +365,7 @@ export function createTwoCaptchaResolverVendorAdapter(options) {
|
|
|
365
365
|
if (!token?.trim()) {
|
|
366
366
|
throw new ResolverVendorUnavailableError(TWOCAPTCHA_VENDOR_ID, "transport_failure", { phase });
|
|
367
367
|
}
|
|
368
|
+
// AWS WAF remains a token solution here, so resolver cookie caching does not apply.
|
|
368
369
|
return { form: "token", token };
|
|
369
370
|
}
|
|
370
371
|
};
|
|
@@ -31,6 +31,7 @@ export interface ResolverRuntimeOptions {
|
|
|
31
31
|
readonly identityScope?: string;
|
|
32
32
|
}) => ResolverVendorTransport;
|
|
33
33
|
}
|
|
34
|
+
export type ResolverSolutionSource = "cache" | "vendor";
|
|
34
35
|
export type ResolverInstrumentationMetadata = {
|
|
35
36
|
readonly target: ResolverContext;
|
|
36
37
|
readonly traceRecorder: TraceRecorder;
|
|
@@ -40,8 +41,19 @@ export declare const RESOLVER_ADAPTER_REGISTRY: Partial<Readonly<Record<Provider
|
|
|
40
41
|
export declare function swapResolverAdapterFactoryForTests(vendor: ProviderResolverVendor, factory: ResolverAdapterFactory | undefined): () => void;
|
|
41
42
|
/** Internal test seam; deliberately not re-exported from the package root. */
|
|
42
43
|
export declare function swapResolverDefaultUserAgentForTests(resolver: (() => string | undefined) | undefined): () => void;
|
|
44
|
+
/**
|
|
45
|
+
* Identify whether this exact SDK-returned solution object came from the cache
|
|
46
|
+
* or a vendor solve. Returns undefined for copied or caller-created objects.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getResolverSolutionSource(solution: ChallengeSolution): ResolverSolutionSource | undefined;
|
|
43
49
|
/** Remove the cached entry for the exact solution object returned by this resolver. */
|
|
44
50
|
export declare function invalidateResolverSolution(resolver: ResolverContext, challenge: ProviderChallenge, solution: ChallengeSolution): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Remove a solution only when it was returned from the resolver cache. Providers
|
|
53
|
+
* should call this when an upstream serves the same challenge after a cached
|
|
54
|
+
* solution was applied, so the next `solve()` mints a fresh solution.
|
|
55
|
+
*/
|
|
56
|
+
export declare function invalidateCachedResolverSolution(resolver: ResolverContext, challenge: ProviderChallenge, solution: ChallengeSolution): Promise<boolean>;
|
|
45
57
|
export declare function createResolverClient(options: {
|
|
46
58
|
readonly kinds: readonly ProviderChallengeKind[];
|
|
47
59
|
readonly adapters: readonly ResolverVendorAdapter[];
|
package/dist/runtime/resolver.js
CHANGED
|
@@ -19,6 +19,7 @@ const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
|
|
|
19
19
|
const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
|
|
20
20
|
const resolverCaches = new WeakMap();
|
|
21
21
|
const solutionIssuerDigests = new WeakMap();
|
|
22
|
+
const resolverSolutionSources = new WeakMap();
|
|
22
23
|
const SAFE_CAUSE_MESSAGE_WORDS = new Set([
|
|
23
24
|
"abort",
|
|
24
25
|
"aborted",
|
|
@@ -376,7 +377,7 @@ function isResolverCacheIndex(value) {
|
|
|
376
377
|
function solutionExpiryMs(solution) {
|
|
377
378
|
if (solution.form !== "cookies")
|
|
378
379
|
return undefined;
|
|
379
|
-
const expires = solution.expires;
|
|
380
|
+
const expires = solution.expires ?? solution.sdkEstimatedExpires;
|
|
380
381
|
if (typeof expires !== "number" || !Number.isFinite(expires))
|
|
381
382
|
return undefined;
|
|
382
383
|
return expires * 1_000;
|
|
@@ -386,14 +387,33 @@ function rememberSolutionIssuer(solution, issuerDigest) {
|
|
|
386
387
|
solutionIssuerDigests.set(solution, issuerDigest);
|
|
387
388
|
}
|
|
388
389
|
}
|
|
390
|
+
function rememberSolutionSource(solution, source) {
|
|
391
|
+
if (typeof solution === "object" && solution !== null) {
|
|
392
|
+
resolverSolutionSources.set(solution, source);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Identify whether this exact SDK-returned solution object came from the cache
|
|
397
|
+
* or a vendor solve. Returns undefined for copied or caller-created objects.
|
|
398
|
+
*/
|
|
399
|
+
export function getResolverSolutionSource(solution) {
|
|
400
|
+
return resolverSolutionSources.get(solution);
|
|
401
|
+
}
|
|
389
402
|
async function readCachedSolution(cache, challenge, issuerDigest, now) {
|
|
390
403
|
const cached = await cache.get(resolverSolutionCacheKey(cache, challenge, issuerDigest));
|
|
391
404
|
if (!cached || !isCachedResolverSolution(cached.value))
|
|
392
405
|
return undefined;
|
|
393
406
|
if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
|
|
394
407
|
return undefined;
|
|
395
|
-
|
|
396
|
-
|
|
408
|
+
if (cached.value.solution.form !== "cookies")
|
|
409
|
+
return undefined;
|
|
410
|
+
const solution = {
|
|
411
|
+
...cached.value.solution,
|
|
412
|
+
cookies: { ...cached.value.solution.cookies },
|
|
413
|
+
};
|
|
414
|
+
rememberSolutionIssuer(solution, issuerDigest);
|
|
415
|
+
rememberSolutionSource(solution, "cache");
|
|
416
|
+
return solution;
|
|
397
417
|
}
|
|
398
418
|
async function findCachedSolution(cache, challenge, identity, identityScope) {
|
|
399
419
|
const now = Date.now();
|
|
@@ -457,8 +477,7 @@ async function cacheResolverSolution(cache, challenge, solution, identity, ident
|
|
|
457
477
|
{ direct: true, expiresAtMs, issuerDigest },
|
|
458
478
|
], now);
|
|
459
479
|
}
|
|
460
|
-
|
|
461
|
-
export async function invalidateResolverSolution(resolver, challenge, solution) {
|
|
480
|
+
async function invalidateResolverSolutionWithOutcome(resolver, challenge, solution) {
|
|
462
481
|
const metadata = resolver[RESOLVER_INSTRUMENTATION_METADATA];
|
|
463
482
|
const cacheOwner = metadata?.target ?? resolver;
|
|
464
483
|
const invalidate = async () => {
|
|
@@ -484,14 +503,28 @@ export async function invalidateResolverSolution(resolver, challenge, solution)
|
|
|
484
503
|
return "index_entry_deleted";
|
|
485
504
|
};
|
|
486
505
|
if (!metadata) {
|
|
487
|
-
await invalidate();
|
|
488
|
-
return;
|
|
506
|
+
return await invalidate();
|
|
489
507
|
}
|
|
490
|
-
await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
508
|
+
return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
491
509
|
attributes: { challenge_kind: challenge.kind },
|
|
492
510
|
onSuccess: (outcome) => ({ outcome }),
|
|
493
511
|
});
|
|
494
512
|
}
|
|
513
|
+
/** Remove the cached entry for the exact solution object returned by this resolver. */
|
|
514
|
+
export async function invalidateResolverSolution(resolver, challenge, solution) {
|
|
515
|
+
await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Remove a solution only when it was returned from the resolver cache. Providers
|
|
519
|
+
* should call this when an upstream serves the same challenge after a cached
|
|
520
|
+
* solution was applied, so the next `solve()` mints a fresh solution.
|
|
521
|
+
*/
|
|
522
|
+
export async function invalidateCachedResolverSolution(resolver, challenge, solution) {
|
|
523
|
+
if (getResolverSolutionSource(solution) !== "cache")
|
|
524
|
+
return false;
|
|
525
|
+
const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
526
|
+
return outcome === "entry_deleted" || outcome === "index_entry_deleted";
|
|
527
|
+
}
|
|
495
528
|
async function resolveResolverIdentity(proxyIntent) {
|
|
496
529
|
const userAgentSource = proxyIntent.userAgent ? "declared" : "defaulted";
|
|
497
530
|
let proxyUrl;
|
|
@@ -618,6 +651,7 @@ function createResolverChainClient(options) {
|
|
|
618
651
|
await cacheResolverSolution(options.cache, challenge, solution, issuingIdentity, options.identityScope);
|
|
619
652
|
}
|
|
620
653
|
}
|
|
654
|
+
rememberSolutionSource(solution, "vendor");
|
|
621
655
|
return solution;
|
|
622
656
|
}
|
|
623
657
|
catch (error) {
|
package/dist/types.d.ts
CHANGED
|
@@ -338,6 +338,11 @@ export type ChallengeSolution = {
|
|
|
338
338
|
readonly userAgent: string;
|
|
339
339
|
/** Epoch seconds copied from the upstream cookie's own expiry attribute; never a constant. */
|
|
340
340
|
readonly expires?: number;
|
|
341
|
+
/**
|
|
342
|
+
* Epoch seconds conservatively estimated by the SDK when a resolver vendor omits
|
|
343
|
+
* the upstream cookie's expiry. `expires` takes precedence when both are present.
|
|
344
|
+
*/
|
|
345
|
+
readonly sdkEstimatedExpires?: number;
|
|
341
346
|
};
|
|
342
347
|
export interface ProviderResolverConfig {
|
|
343
348
|
/** Optional ordered override for the SDK-owned vendor fallback chain. */
|
package/package.json
CHANGED
|
@@ -10,6 +10,8 @@ export {
|
|
|
10
10
|
createUnsupportedResolverClient,
|
|
11
11
|
DEFAULT_RESOLVER_VENDOR_PREFERENCE,
|
|
12
12
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
13
|
+
getResolverSolutionSource,
|
|
14
|
+
invalidateCachedResolverSolution,
|
|
13
15
|
invalidateResolverSolution,
|
|
14
16
|
RESOLVER_ADAPTER_REGISTRY,
|
|
15
17
|
RESOLVER_INSTRUMENTATION_METADATA,
|
|
@@ -17,4 +19,5 @@ export {
|
|
|
17
19
|
type ResolverAdapterFactory,
|
|
18
20
|
type ResolverInstrumentationMetadata,
|
|
19
21
|
type ResolverRuntimeOptions,
|
|
22
|
+
type ResolverSolutionSource,
|
|
20
23
|
} from "./resolver.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getStealthProfile } from "../../stealth/profiles.js";
|
|
2
|
-
import type { ChallengeSolution, ProviderChallenge } from "../../types.js";
|
|
2
|
+
import type { ChallengeSolution, ProviderChallenge, ProviderChallengeKind } from "../../types.js";
|
|
3
3
|
import { redactSensitiveText } from "../request-options.js";
|
|
4
4
|
import { DEFAULT_PROFILE } from "../stealth.js";
|
|
5
5
|
import type { TraceRecorder } from "../trace.js";
|
|
@@ -17,6 +17,11 @@ const CAPSOLVER_VENDOR_ID = "capsolver" as const;
|
|
|
17
17
|
const DEFAULT_CAPSOLVER_BASE_URL = "https://api.capsolver.com";
|
|
18
18
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
19
19
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
20
|
+
const SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND = {
|
|
21
|
+
// CapSolver omits expiry, so use one conservative hour despite measured
|
|
22
|
+
// AWS WAF lifetimes of days.
|
|
23
|
+
aws_waf: 60 * 60 * 1_000,
|
|
24
|
+
} satisfies Partial<Record<ProviderChallengeKind, number>>;
|
|
20
25
|
|
|
21
26
|
type Delay = (ms: number, signal: AbortSignal) => Promise<void>;
|
|
22
27
|
type CapsolverOperationPhase = "create_task" | "poll_result";
|
|
@@ -657,6 +662,8 @@ export function createCapsolverResolverVendorAdapter(
|
|
|
657
662
|
form: "cookies" as const,
|
|
658
663
|
cookies: { "aws-waf-token": solutionValue },
|
|
659
664
|
userAgent: identity?.userAgent ?? getStealthProfile(DEFAULT_PROFILE).userAgent,
|
|
665
|
+
sdkEstimatedExpires:
|
|
666
|
+
(now() + SDK_ESTIMATED_COOKIE_TTL_MS_BY_CHALLENGE_KIND.aws_waf) / 1_000,
|
|
660
667
|
}
|
|
661
668
|
: { form: "token" as const, token: solutionValue };
|
|
662
669
|
}
|
package/src/runtime/resolver.ts
CHANGED
|
@@ -143,11 +143,21 @@ type ResolverCacheIndex = {
|
|
|
143
143
|
}[];
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
+
export type ResolverSolutionSource = "cache" | "vendor";
|
|
147
|
+
|
|
148
|
+
type ResolverSolutionInvalidationOutcome =
|
|
149
|
+
| "cache_disabled"
|
|
150
|
+
| "entry_deleted"
|
|
151
|
+
| "index_entry_deleted"
|
|
152
|
+
| "not_cookie_solution"
|
|
153
|
+
| "solution_not_cached";
|
|
154
|
+
|
|
146
155
|
const RESOLVER_SOLUTION_CACHE_NAMESPACE = "resolver-solution";
|
|
147
156
|
const RESOLVER_SOLUTION_INDEX_CACHE_NAMESPACE = "resolver-solution-index";
|
|
148
157
|
const MIN_RESOLVER_CACHE_TTL_MS = 1_000;
|
|
149
158
|
const resolverCaches = new WeakMap<object, ProviderCache | null>();
|
|
150
159
|
const solutionIssuerDigests = new WeakMap<object, string>();
|
|
160
|
+
const resolverSolutionSources = new WeakMap<object, ResolverSolutionSource>();
|
|
151
161
|
const SAFE_CAUSE_MESSAGE_WORDS: ReadonlySet<string> = new Set([
|
|
152
162
|
"abort",
|
|
153
163
|
"aborted",
|
|
@@ -597,7 +607,7 @@ function isResolverCacheIndex(value: unknown): value is ResolverCacheIndex {
|
|
|
597
607
|
|
|
598
608
|
function solutionExpiryMs(solution: ChallengeSolution): number | undefined {
|
|
599
609
|
if (solution.form !== "cookies") return undefined;
|
|
600
|
-
const expires = solution.expires;
|
|
610
|
+
const expires = solution.expires ?? solution.sdkEstimatedExpires;
|
|
601
611
|
if (typeof expires !== "number" || !Number.isFinite(expires)) return undefined;
|
|
602
612
|
return expires * 1_000;
|
|
603
613
|
}
|
|
@@ -608,6 +618,22 @@ function rememberSolutionIssuer(solution: ChallengeSolution, issuerDigest: strin
|
|
|
608
618
|
}
|
|
609
619
|
}
|
|
610
620
|
|
|
621
|
+
function rememberSolutionSource(solution: ChallengeSolution, source: ResolverSolutionSource): void {
|
|
622
|
+
if (typeof solution === "object" && solution !== null) {
|
|
623
|
+
resolverSolutionSources.set(solution, source);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Identify whether this exact SDK-returned solution object came from the cache
|
|
629
|
+
* or a vendor solve. Returns undefined for copied or caller-created objects.
|
|
630
|
+
*/
|
|
631
|
+
export function getResolverSolutionSource(
|
|
632
|
+
solution: ChallengeSolution,
|
|
633
|
+
): ResolverSolutionSource | undefined {
|
|
634
|
+
return resolverSolutionSources.get(solution);
|
|
635
|
+
}
|
|
636
|
+
|
|
611
637
|
async function readCachedSolution(
|
|
612
638
|
cache: ProviderCache,
|
|
613
639
|
challenge: ProviderChallenge,
|
|
@@ -618,8 +644,14 @@ async function readCachedSolution(
|
|
|
618
644
|
if (!cached || !isCachedResolverSolution(cached.value)) return undefined;
|
|
619
645
|
if (cached.value.issuerDigest !== issuerDigest || cached.value.expiresAtMs <= now)
|
|
620
646
|
return undefined;
|
|
621
|
-
|
|
622
|
-
|
|
647
|
+
if (cached.value.solution.form !== "cookies") return undefined;
|
|
648
|
+
const solution = {
|
|
649
|
+
...cached.value.solution,
|
|
650
|
+
cookies: { ...cached.value.solution.cookies },
|
|
651
|
+
};
|
|
652
|
+
rememberSolutionIssuer(solution, issuerDigest);
|
|
653
|
+
rememberSolutionSource(solution, "cache");
|
|
654
|
+
return solution;
|
|
623
655
|
}
|
|
624
656
|
|
|
625
657
|
async function findCachedSolution(
|
|
@@ -720,25 +752,18 @@ async function cacheResolverSolution(
|
|
|
720
752
|
);
|
|
721
753
|
}
|
|
722
754
|
|
|
723
|
-
|
|
724
|
-
export async function invalidateResolverSolution(
|
|
755
|
+
async function invalidateResolverSolutionWithOutcome(
|
|
725
756
|
resolver: ResolverContext,
|
|
726
757
|
challenge: ProviderChallenge,
|
|
727
758
|
solution: ChallengeSolution,
|
|
728
|
-
): Promise<
|
|
759
|
+
): Promise<ResolverSolutionInvalidationOutcome> {
|
|
729
760
|
const metadata = (
|
|
730
761
|
resolver as ResolverContext & {
|
|
731
762
|
readonly [RESOLVER_INSTRUMENTATION_METADATA]?: ResolverInstrumentationMetadata;
|
|
732
763
|
}
|
|
733
764
|
)[RESOLVER_INSTRUMENTATION_METADATA];
|
|
734
765
|
const cacheOwner = metadata?.target ?? resolver;
|
|
735
|
-
const invalidate = async (): Promise<
|
|
736
|
-
| "cache_disabled"
|
|
737
|
-
| "entry_deleted"
|
|
738
|
-
| "index_entry_deleted"
|
|
739
|
-
| "not_cookie_solution"
|
|
740
|
-
| "solution_not_cached"
|
|
741
|
-
> => {
|
|
766
|
+
const invalidate = async (): Promise<ResolverSolutionInvalidationOutcome> => {
|
|
742
767
|
if (solution.form !== "cookies") return "not_cookie_solution";
|
|
743
768
|
if (!resolverCaches.has(cacheOwner)) {
|
|
744
769
|
throw new Error("Resolver cache registration lookup failed during solution invalidation");
|
|
@@ -764,15 +789,38 @@ export async function invalidateResolverSolution(
|
|
|
764
789
|
};
|
|
765
790
|
|
|
766
791
|
if (!metadata) {
|
|
767
|
-
await invalidate();
|
|
768
|
-
return;
|
|
792
|
+
return await invalidate();
|
|
769
793
|
}
|
|
770
|
-
await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
794
|
+
return await metadata.traceRecorder.runSpan("resolver.cache.invalidate", invalidate, {
|
|
771
795
|
attributes: { challenge_kind: challenge.kind },
|
|
772
796
|
onSuccess: (outcome) => ({ outcome }),
|
|
773
797
|
});
|
|
774
798
|
}
|
|
775
799
|
|
|
800
|
+
/** Remove the cached entry for the exact solution object returned by this resolver. */
|
|
801
|
+
export async function invalidateResolverSolution(
|
|
802
|
+
resolver: ResolverContext,
|
|
803
|
+
challenge: ProviderChallenge,
|
|
804
|
+
solution: ChallengeSolution,
|
|
805
|
+
): Promise<void> {
|
|
806
|
+
await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Remove a solution only when it was returned from the resolver cache. Providers
|
|
811
|
+
* should call this when an upstream serves the same challenge after a cached
|
|
812
|
+
* solution was applied, so the next `solve()` mints a fresh solution.
|
|
813
|
+
*/
|
|
814
|
+
export async function invalidateCachedResolverSolution(
|
|
815
|
+
resolver: ResolverContext,
|
|
816
|
+
challenge: ProviderChallenge,
|
|
817
|
+
solution: ChallengeSolution,
|
|
818
|
+
): Promise<boolean> {
|
|
819
|
+
if (getResolverSolutionSource(solution) !== "cache") return false;
|
|
820
|
+
const outcome = await invalidateResolverSolutionWithOutcome(resolver, challenge, solution);
|
|
821
|
+
return outcome === "entry_deleted" || outcome === "index_entry_deleted";
|
|
822
|
+
}
|
|
823
|
+
|
|
776
824
|
async function resolveResolverIdentity(
|
|
777
825
|
proxyIntent: NonNullable<ResolverRuntimeOptions["proxyIntent"]>,
|
|
778
826
|
): Promise<{
|
|
@@ -938,6 +986,7 @@ function createResolverChainClient(options: {
|
|
|
938
986
|
);
|
|
939
987
|
}
|
|
940
988
|
}
|
|
989
|
+
rememberSolutionSource(solution, "vendor");
|
|
941
990
|
return solution;
|
|
942
991
|
} catch (error) {
|
|
943
992
|
signal.throwIfAborted();
|
package/src/types.ts
CHANGED
|
@@ -409,6 +409,11 @@ export type ChallengeSolution =
|
|
|
409
409
|
readonly userAgent: string;
|
|
410
410
|
/** Epoch seconds copied from the upstream cookie's own expiry attribute; never a constant. */
|
|
411
411
|
readonly expires?: number;
|
|
412
|
+
/**
|
|
413
|
+
* Epoch seconds conservatively estimated by the SDK when a resolver vendor omits
|
|
414
|
+
* the upstream cookie's expiry. `expires` takes precedence when both are present.
|
|
415
|
+
*/
|
|
416
|
+
readonly sdkEstimatedExpires?: number;
|
|
412
417
|
};
|
|
413
418
|
|
|
414
419
|
export interface ProviderResolverConfig {
|