@apifuse/provider-sdk 2.2.0-beta.28 → 2.2.0-beta.29
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/bin/apifuse-dev.ts +32 -3
- package/bin/apifuse-pack-types.ts +24 -1
- package/bin/apifuse-record.ts +39 -6
- package/dist/auth.d.ts +14 -0
- package/dist/auth.js +38 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/browser.js +45 -2
- package/dist/runtime/proxy-telemetry.js +3 -0
- package/dist/runtime/resolver-public.d.ts +1 -0
- package/dist/runtime/resolver-public.js +1 -0
- package/dist/runtime/resolver-vendors/browser.js +14 -4
- package/dist/runtime/resolver-vendors/twocaptcha.js +84 -17
- package/dist/runtime/resolver-vendors/types.d.ts +2 -2
- package/dist/runtime/resolver-vendors/types.js +3 -1
- package/dist/runtime/resolver.d.ts +12 -3
- package/dist/runtime/resolver.js +80 -12
- package/dist/runtime/stealth.d.ts +1 -0
- package/dist/runtime/stealth.js +1 -1
- package/dist/server/serve-implementation.js +20 -2
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +1 -0
- package/package.json +4 -4
- package/src/auth.ts +78 -0
- package/src/config/loader.ts +3 -0
- package/src/index.ts +0 -1
- package/src/runtime/browser.ts +50 -2
- package/src/runtime/proxy-telemetry.ts +5 -0
- package/src/runtime/resolver-public.ts +18 -0
- package/src/runtime/resolver-vendors/browser.ts +14 -4
- package/src/runtime/resolver-vendors/twocaptcha.ts +102 -19
- package/src/runtime/resolver-vendors/types.ts +7 -2
- package/src/runtime/resolver.ts +104 -17
- package/src/runtime/stealth.ts +1 -1
- package/src/server/serve-implementation.ts +20 -2
- package/src/testing/index.ts +1 -0
package/CHANGELOG.md
CHANGED
package/bin/apifuse-dev.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { dirname, relative, resolve } from "node:path";
|
|
5
|
-
import type { ProviderDefinition } from "../src/index.js";
|
|
6
5
|
import {
|
|
7
6
|
createCredentialContext,
|
|
8
7
|
createEnvContext,
|
|
@@ -13,12 +12,16 @@ import {
|
|
|
13
12
|
createUnsupportedResolverClient,
|
|
14
13
|
createSttClientFromEnv,
|
|
15
14
|
PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV,
|
|
15
|
+
type ProviderDefinition,
|
|
16
16
|
ProviderError,
|
|
17
|
+
type ProviderProxyPolicy,
|
|
17
18
|
} from "../src/index.js";
|
|
18
19
|
import { createBrowserClient } from "../src/runtime/browser.js";
|
|
20
|
+
import { createResolverClientFromEnv } from "../src/runtime/resolver.js";
|
|
19
21
|
import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
|
|
20
22
|
import { createStealthClient } from "../src/runtime/stealth.js";
|
|
21
23
|
import { createTraceContext } from "../src/runtime/trace.js";
|
|
24
|
+
import { getStealthProfile } from "../src/stealth/profiles.js";
|
|
22
25
|
import type { BrowserClient, ProviderContext } from "../src/types.js";
|
|
23
26
|
|
|
24
27
|
const HELP_TEXT = `Usage: apifuse dev [path]
|
|
@@ -82,6 +85,11 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
82
85
|
]);
|
|
83
86
|
const credential = createCredentialContext();
|
|
84
87
|
const state = createMemoryProviderRuntimeState();
|
|
88
|
+
const cache = createProviderCache({ providerId: provider.id });
|
|
89
|
+
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
90
|
+
const stealthProfile = provider.stealth?.profile
|
|
91
|
+
? getStealthProfile(provider.stealth.profile)
|
|
92
|
+
: undefined;
|
|
85
93
|
const ctx: ProviderContext = {
|
|
86
94
|
env,
|
|
87
95
|
credential,
|
|
@@ -94,13 +102,27 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
94
102
|
})
|
|
95
103
|
: createUnsupportedBrowserStub(),
|
|
96
104
|
http: createHttpClient(),
|
|
97
|
-
cache
|
|
105
|
+
cache,
|
|
98
106
|
state,
|
|
99
107
|
trace: createTraceContext(),
|
|
100
108
|
stealth: createStealthClient("http://localhost"),
|
|
101
109
|
ocr: createOcrClientFromEnv(provider.ocr),
|
|
102
110
|
stt: createSttClientFromEnv(provider.stt),
|
|
103
|
-
resolver:
|
|
111
|
+
resolver: provider.resolver
|
|
112
|
+
? createResolverClientFromEnv(provider.resolver, undefined, {
|
|
113
|
+
allowedHosts: provider.allowedHosts,
|
|
114
|
+
cache,
|
|
115
|
+
...(proxyPolicy
|
|
116
|
+
? {
|
|
117
|
+
proxyIntent: {
|
|
118
|
+
mode: proxyPolicy.mode,
|
|
119
|
+
upstream: { proxy: provider.proxy },
|
|
120
|
+
...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
: {}),
|
|
124
|
+
})
|
|
125
|
+
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
104
126
|
choice: createProviderChoiceContext({
|
|
105
127
|
providerId: provider.id,
|
|
106
128
|
env,
|
|
@@ -112,6 +134,13 @@ export function createProviderContext(provider: ProviderDefinition): {
|
|
|
112
134
|
return { ctx };
|
|
113
135
|
}
|
|
114
136
|
|
|
137
|
+
function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
|
|
138
|
+
if (typeof provider.proxy === "object") return provider.proxy;
|
|
139
|
+
if (provider.proxy === true) return { mode: "optional" };
|
|
140
|
+
if (provider.proxy === false) return { mode: "disabled" };
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
115
144
|
function normalizeArgs(argv: string[]): string[] {
|
|
116
145
|
return argv[0] === "dev" ? argv.slice(1) : argv;
|
|
117
146
|
}
|
|
@@ -242,6 +242,28 @@ const NEGATIVE_CONTROLS = [
|
|
|
242
242
|
"",
|
|
243
243
|
].join("\n"),
|
|
244
244
|
},
|
|
245
|
+
{
|
|
246
|
+
filename: "negative-control-resolver-default-user-agent-test-seam.ts",
|
|
247
|
+
expectedCode: "TS2305",
|
|
248
|
+
description: "the resolver subpath does not expose its default user-agent test seam",
|
|
249
|
+
source: [
|
|
250
|
+
'import { swapResolverDefaultUserAgentForTests } from "@apifuse/provider-sdk/runtime/resolver";',
|
|
251
|
+
"",
|
|
252
|
+
"export const mustNotCompile = swapResolverDefaultUserAgentForTests;",
|
|
253
|
+
"",
|
|
254
|
+
].join("\n"),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
filename: "negative-control-cache-reset-test-seam.ts",
|
|
258
|
+
expectedCode: "TS2305",
|
|
259
|
+
description: "the package root does not expose its cache-reset test seam",
|
|
260
|
+
source: [
|
|
261
|
+
'import { resetProviderCacheForTests } from "@apifuse/provider-sdk";',
|
|
262
|
+
"",
|
|
263
|
+
"export const mustNotCompile = resetProviderCacheForTests;",
|
|
264
|
+
"",
|
|
265
|
+
].join("\n"),
|
|
266
|
+
},
|
|
245
267
|
{
|
|
246
268
|
filename: "negative-control-resolver-runtime-allowed-hosts.ts",
|
|
247
269
|
expectedCode: "TS2322",
|
|
@@ -366,7 +388,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
366
388
|
'import { extractProviderContract } from "@apifuse/provider-sdk/contract";',
|
|
367
389
|
'import { AUTH_TURN_SCHEMA } from "@apifuse/provider-sdk/auth-turn";',
|
|
368
390
|
'import { serve } from "@apifuse/provider-sdk/server";',
|
|
369
|
-
'import { runStandardTests } from "@apifuse/provider-sdk/testing";',
|
|
391
|
+
'import { resetProviderCacheForTests, runStandardTests } from "@apifuse/provider-sdk/testing";',
|
|
370
392
|
"",
|
|
371
393
|
"// ProviderError must keep its inherited Error members under nodenext.",
|
|
372
394
|
"// When dist d.ts imports fail to resolve, the class type degrades and",
|
|
@@ -451,6 +473,7 @@ function setUpFixtureConsumer(consumerDir: string, tarballPath: string): void {
|
|
|
451
473
|
" extractProviderContract,",
|
|
452
474
|
" AUTH_TURN_SCHEMA,",
|
|
453
475
|
" serve,",
|
|
476
|
+
" resetProviderCacheForTests,",
|
|
454
477
|
" runStandardTests,",
|
|
455
478
|
"};",
|
|
456
479
|
"",
|
package/bin/apifuse-record.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type ProviderContext,
|
|
20
20
|
type ProviderDefinition,
|
|
21
21
|
ProviderError,
|
|
22
|
+
type ProviderProxyPolicy,
|
|
22
23
|
type RequestOptions,
|
|
23
24
|
type StealthClient,
|
|
24
25
|
TransportError,
|
|
@@ -31,13 +32,12 @@ import {
|
|
|
31
32
|
sanitizeDiagnosticText,
|
|
32
33
|
sanitizeFixtureString,
|
|
33
34
|
} from "../src/fixture-sanitization.js";
|
|
34
|
-
import {
|
|
35
|
-
import { createStealthClient } from "../src/runtime/stealth.js";
|
|
35
|
+
import { createResolverClientFromEnv } from "../src/runtime/resolver.js";
|
|
36
36
|
import {
|
|
37
|
-
REDACTED_QUERY_VALUE,
|
|
38
37
|
isSensitiveKey,
|
|
39
38
|
normalizeSensitiveParams,
|
|
40
39
|
parseHttpRequestInvocation,
|
|
40
|
+
REDACTED_QUERY_VALUE,
|
|
41
41
|
redactSensitiveError,
|
|
42
42
|
redactSensitiveText,
|
|
43
43
|
redactUrlQueryParams,
|
|
@@ -45,7 +45,10 @@ import {
|
|
|
45
45
|
requestOptionsFromHttpInvocation,
|
|
46
46
|
serializeRequestUrl,
|
|
47
47
|
} from "../src/runtime/request-options.js";
|
|
48
|
+
import { createMemoryProviderRuntimeState } from "../src/runtime/state.js";
|
|
49
|
+
import { createStealthClient } from "../src/runtime/stealth.js";
|
|
48
50
|
import { parseSchema } from "../src/schema.js";
|
|
51
|
+
import { getStealthProfile } from "../src/stealth/profiles.js";
|
|
49
52
|
import {
|
|
50
53
|
captureStreamEvidence,
|
|
51
54
|
createStreamCaptureEnvelope,
|
|
@@ -422,7 +425,18 @@ function resolveOperationBaseUrl(provider: ProviderRuntime, operationName: strin
|
|
|
422
425
|
return baseUrl;
|
|
423
426
|
}
|
|
424
427
|
|
|
425
|
-
function
|
|
428
|
+
function resolveNativeProxyPolicy(provider: ProviderDefinition): ProviderProxyPolicy | undefined {
|
|
429
|
+
if (typeof provider.proxy === "object") return provider.proxy;
|
|
430
|
+
if (provider.proxy === true) return { mode: "optional" };
|
|
431
|
+
if (provider.proxy === false) return { mode: "disabled" };
|
|
432
|
+
return undefined;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function createCaptureContext(
|
|
436
|
+
provider: ProviderRuntime,
|
|
437
|
+
baseUrl: string,
|
|
438
|
+
sanitize: boolean,
|
|
439
|
+
) {
|
|
426
440
|
let nextCaptureOrder = 0;
|
|
427
441
|
let nextStreamOrdinal = 0;
|
|
428
442
|
let capturedRaw: JsonValue | undefined;
|
|
@@ -506,12 +520,17 @@ function createCaptureContext(provider: ProviderRuntime, baseUrl: string, saniti
|
|
|
506
520
|
getScopes: () => [],
|
|
507
521
|
};
|
|
508
522
|
const state = createMemoryProviderRuntimeState();
|
|
523
|
+
const cache = createBypassProviderCache({ providerId: provider.id });
|
|
524
|
+
const proxyPolicy = resolveNativeProxyPolicy(provider);
|
|
525
|
+
const stealthProfile = provider.stealth?.profile
|
|
526
|
+
? getStealthProfile(provider.stealth.profile)
|
|
527
|
+
: undefined;
|
|
509
528
|
const ctx: ProviderContext = {
|
|
510
529
|
env,
|
|
511
530
|
credential,
|
|
512
531
|
request: { headers: {} },
|
|
513
532
|
http,
|
|
514
|
-
cache
|
|
533
|
+
cache,
|
|
515
534
|
state,
|
|
516
535
|
stealth,
|
|
517
536
|
browser: {
|
|
@@ -540,7 +559,21 @@ function createCaptureContext(provider: ProviderRuntime, baseUrl: string, saniti
|
|
|
540
559
|
},
|
|
541
560
|
ocr: createOcrClientFromEnv(provider.ocr),
|
|
542
561
|
stt: createSttClientFromEnv(provider.stt),
|
|
543
|
-
resolver:
|
|
562
|
+
resolver: provider.resolver
|
|
563
|
+
? createResolverClientFromEnv(provider.resolver, undefined, {
|
|
564
|
+
allowedHosts: provider.allowedHosts,
|
|
565
|
+
cache,
|
|
566
|
+
...(proxyPolicy
|
|
567
|
+
? {
|
|
568
|
+
proxyIntent: {
|
|
569
|
+
mode: proxyPolicy.mode,
|
|
570
|
+
upstream: { proxy: provider.proxy },
|
|
571
|
+
...(stealthProfile ? { userAgent: stealthProfile.userAgent } : {}),
|
|
572
|
+
},
|
|
573
|
+
}
|
|
574
|
+
: {}),
|
|
575
|
+
})
|
|
576
|
+
: createUnsupportedResolverClient("Provider does not declare resolver capability"),
|
|
544
577
|
choice: createProviderChoiceContext({
|
|
545
578
|
providerId: provider.id,
|
|
546
579
|
env,
|
package/dist/auth.d.ts
CHANGED
|
@@ -66,6 +66,20 @@ export interface DefineCredentialsAuthOptions<TFields extends CredentialsAuthFie
|
|
|
66
66
|
/** Extra auth-flow context keys used by custom login/challenge code. */
|
|
67
67
|
contextKeys?: readonly string[];
|
|
68
68
|
login(ctx: FlowContext, input: CredentialsAuthInput<TFields>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
69
|
+
/**
|
|
70
|
+
* Optional re-mint of an expired session from the stored credential, wired
|
|
71
|
+
* to `auth.flow.refresh`.
|
|
72
|
+
*
|
|
73
|
+
* Credential-auth upstreams routinely invalidate a session well before the
|
|
74
|
+
* `expiresAt` the provider advertised, which leaves every operation failing
|
|
75
|
+
* with a reauth error until a human repeats the whole interactive login.
|
|
76
|
+
* Implement this to re-establish the session from what is already stored on
|
|
77
|
+
* the connection; the result is resolved exactly like `login`, so it may
|
|
78
|
+
* also raise a challenge when the upstream demands one. Omit it when the
|
|
79
|
+
* upstream has no non-interactive path and re-authentication genuinely
|
|
80
|
+
* requires the user.
|
|
81
|
+
*/
|
|
82
|
+
refresh?(ctx: FlowContext, input: Partial<CredentialsAuthInput<TFields>>): CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string> | Promise<CredentialsAuthLoginResult<TCredentialKeys, keyof TChallenges & string>>;
|
|
69
83
|
}
|
|
70
84
|
export interface DefinedCredentialsAuth {
|
|
71
85
|
auth: AuthConfig;
|
package/dist/auth.js
CHANGED
|
@@ -199,6 +199,23 @@ function normalizeInput(fields, input) {
|
|
|
199
199
|
}
|
|
200
200
|
return result;
|
|
201
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Refresh variant of {@link normalizeInput}. `login` coerces every declared
|
|
204
|
+
* field to a string because the interactive turn has already enforced that they
|
|
205
|
+
* are present; refresh runs with no user present, so an absent field is omitted
|
|
206
|
+
* rather than turned into an empty string. That keeps "the user did not supply
|
|
207
|
+
* this" distinguishable from "the user supplied an empty value".
|
|
208
|
+
*/
|
|
209
|
+
function normalizePartialInput(fields, input) {
|
|
210
|
+
const result = {};
|
|
211
|
+
for (const name of Object.keys(fields)) {
|
|
212
|
+
const value = input?.[name];
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
result[name] = value;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
202
219
|
function assertCredentialKeys(credentialKeys, credential) {
|
|
203
220
|
const missing = credentialKeys.filter((key) => {
|
|
204
221
|
const value = credential[key];
|
|
@@ -413,6 +430,27 @@ export function defineCredentialsAuth(options) {
|
|
|
413
430
|
}
|
|
414
431
|
return await pollPendingChallenge(ctx, options.credentialKeys, challenges, pending, completeTurnId);
|
|
415
432
|
},
|
|
433
|
+
// Only advertise refresh when the provider implements it: the
|
|
434
|
+
// protocol treats the hook's presence as "this connection can be
|
|
435
|
+
// re-established without the user", and exposing a stub that
|
|
436
|
+
// cannot actually re-mint would turn a recoverable expiry into a
|
|
437
|
+
// silent failure.
|
|
438
|
+
...(options.refresh
|
|
439
|
+
? {
|
|
440
|
+
refresh: async (ctx, rawInput) => {
|
|
441
|
+
// A pending challenge belongs to the interactive flow that
|
|
442
|
+
// raised it; finish it there rather than restarting.
|
|
443
|
+
const pending = getPendingChallenge(ctx);
|
|
444
|
+
if (pending) {
|
|
445
|
+
return await continuePendingChallenge(ctx, options.credentialKeys, challenges, pending, rawInput, completeTurnId);
|
|
446
|
+
}
|
|
447
|
+
// Refresh runs without user input, so fields are optional
|
|
448
|
+
// here — unlike `continue`, missing ones are not a retry.
|
|
449
|
+
const result = await options.refresh(ctx, normalizePartialInput(options.fields, rawInput));
|
|
450
|
+
return await resolveAuthResult(ctx, options.credentialKeys, challenges, result, completeTurnId);
|
|
451
|
+
},
|
|
452
|
+
}
|
|
453
|
+
: {}),
|
|
416
454
|
},
|
|
417
455
|
},
|
|
418
456
|
credential: {
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -61,8 +61,10 @@ export type ProxyResolutionOptions = {
|
|
|
61
61
|
};
|
|
62
62
|
export type ProxyCacheStatus = "memory_hit" | "redis_hit" | "allocator" | "soft_stale_refresh" | "lock_wait" | "redis_error" | "redis_corrupt" | "disabled";
|
|
63
63
|
export type SmartproxyAllocatorBodyClass = "network_error" | "http_error" | "empty" | "json_without_proxies" | "text_without_proxies" | "usable_proxy_endpoints";
|
|
64
|
+
export type ProxyUserAgentSource = "declared" | "defaulted";
|
|
64
65
|
export type ProxyResolutionTelemetryEvent = {
|
|
65
66
|
provider: ProxyVendorName;
|
|
67
|
+
userAgentSource?: ProxyUserAgentSource;
|
|
66
68
|
protocol?: ProxyProtocol;
|
|
67
69
|
cacheStatus: ProxyCacheStatus;
|
|
68
70
|
cacheHit: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export * from "./recipes/gov-api.js";
|
|
|
16
16
|
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
|
-
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, type ProviderCacheOptions,
|
|
19
|
+
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, type ProviderCacheOptions, } from "./runtime/cache.js";
|
|
20
20
|
export { type CreateProviderChoiceContextOptions, createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
21
21
|
export { type CreateCredentialContextOptions, createCredentialContext, } from "./runtime/credential.js";
|
|
22
22
|
export { createEnvContext } from "./runtime/env.js";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export { lintOperation, lintProvider, } from "./lint.js";
|
|
|
14
14
|
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
|
-
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache,
|
|
17
|
+
export { APIFUSE__CACHE__KEY_PEPPER_ENV, createBypassProviderCache, createProviderCache, } from "./runtime/cache.js";
|
|
18
18
|
export { createProviderChoiceContext, createTestProviderChoiceContext, PROVIDER_RUNTIME_CHOICE_TOKEN_MASTER_SECRET_ENV, } from "./runtime/choice.js";
|
|
19
19
|
export { createCredentialContext, } from "./runtime/credential.js";
|
|
20
20
|
export { createEnvContext } from "./runtime/env.js";
|
package/dist/runtime/browser.js
CHANGED
|
@@ -141,7 +141,13 @@ function formatExpression(fn) {
|
|
|
141
141
|
}
|
|
142
142
|
function toLaunchOptions(options) {
|
|
143
143
|
return {
|
|
144
|
-
|
|
144
|
+
// `extraArgs` is optional, but playwright-extra's stealth evasions mutate
|
|
145
|
+
// `options.args` unguarded (navigator.webdriver does
|
|
146
|
+
// `options.args.findIndex(...)` in beforeLaunch). Forwarding `undefined`
|
|
147
|
+
// therefore crashes every stealth launch that omits extraArgs with
|
|
148
|
+
// "TypeError: undefined is not an object (evaluating 'options.args.findIndex')".
|
|
149
|
+
// Always hand the launcher a concrete array.
|
|
150
|
+
args: options.extraArgs ?? [],
|
|
145
151
|
executablePath: options.executablePath,
|
|
146
152
|
headless: options.headless ?? true,
|
|
147
153
|
proxy: options.proxy ? { server: options.proxy } : undefined,
|
|
@@ -684,6 +690,17 @@ function getCdpExecutionContext(params) {
|
|
|
684
690
|
id: typeof contextId === "number" ? contextId : undefined,
|
|
685
691
|
};
|
|
686
692
|
}
|
|
693
|
+
function getCdpDestroyedExecutionContextId(params) {
|
|
694
|
+
if (!isRecord(params)) {
|
|
695
|
+
return undefined;
|
|
696
|
+
}
|
|
697
|
+
return typeof params.executionContextId === "number"
|
|
698
|
+
? params.executionContextId
|
|
699
|
+
: undefined;
|
|
700
|
+
}
|
|
701
|
+
function isMissingExecutionContextError(error) {
|
|
702
|
+
return error instanceof Error && /\b(?:cannot|failed to) find context\b/i.test(error.message);
|
|
703
|
+
}
|
|
687
704
|
class CdpBrowserLocator {
|
|
688
705
|
frame;
|
|
689
706
|
selector;
|
|
@@ -809,7 +826,19 @@ class CdpPoolBrowserPage {
|
|
|
809
826
|
async evaluateInFrame(frameId, fn) {
|
|
810
827
|
await this.initialize();
|
|
811
828
|
const contextId = await this.getFrameExecutionContextId(frameId);
|
|
812
|
-
|
|
829
|
+
try {
|
|
830
|
+
return await this.evaluateWithContext(fn, contextId);
|
|
831
|
+
}
|
|
832
|
+
catch (error) {
|
|
833
|
+
if (!isMissingExecutionContextError(error)) {
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
if (this.frameExecutionContexts.get(frameId) === contextId) {
|
|
837
|
+
this.frameExecutionContexts.delete(frameId);
|
|
838
|
+
}
|
|
839
|
+
const refreshedContextId = await this.getFrameExecutionContextId(frameId);
|
|
840
|
+
return await this.evaluateWithContext(fn, refreshedContextId);
|
|
841
|
+
}
|
|
813
842
|
}
|
|
814
843
|
async waitForSelectorInFrame(frameId, selector, options) {
|
|
815
844
|
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
@@ -1010,6 +1039,20 @@ class CdpPoolBrowserPage {
|
|
|
1010
1039
|
this.frameExecutionContexts.set(context.frameId, context.id);
|
|
1011
1040
|
}
|
|
1012
1041
|
});
|
|
1042
|
+
this.pageClient.on("Runtime.executionContextDestroyed", (params) => {
|
|
1043
|
+
const destroyedContextId = getCdpDestroyedExecutionContextId(params);
|
|
1044
|
+
if (destroyedContextId === undefined) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
for (const [frameId, contextId] of this.frameExecutionContexts) {
|
|
1048
|
+
if (contextId === destroyedContextId) {
|
|
1049
|
+
this.frameExecutionContexts.delete(frameId);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
this.pageClient.on("Runtime.executionContextsCleared", () => {
|
|
1054
|
+
this.frameExecutionContexts.clear();
|
|
1055
|
+
});
|
|
1013
1056
|
await this.pageClient.send("Page.enable");
|
|
1014
1057
|
await this.pageClient.send("Runtime.enable");
|
|
1015
1058
|
this.initialized = true;
|
|
@@ -33,6 +33,7 @@ export class ProxyTelemetryCollector {
|
|
|
33
33
|
recordProxyResolution(event) {
|
|
34
34
|
this.#events.push({
|
|
35
35
|
provider: event.provider,
|
|
36
|
+
...(event.userAgentSource ? { userAgentSource: event.userAgentSource } : {}),
|
|
36
37
|
...(event.protocol ? { protocol: event.protocol } : {}),
|
|
37
38
|
cacheStatus: event.cacheStatus,
|
|
38
39
|
cacheHit: event.cacheHit,
|
|
@@ -99,6 +100,7 @@ export class ProxyTelemetryCollector {
|
|
|
99
100
|
}
|
|
100
101
|
const aggregate = rest.reduce((acc, event) => ({
|
|
101
102
|
provider: event.provider,
|
|
103
|
+
userAgentSource: event.userAgentSource ?? acc.userAgentSource,
|
|
102
104
|
cacheStatus: worseStatus(acc.cacheStatus, event.cacheStatus),
|
|
103
105
|
cacheHit: acc.cacheHit && event.cacheHit,
|
|
104
106
|
resolutionMs: acc.resolutionMs + event.resolutionMs,
|
|
@@ -118,6 +120,7 @@ export class ProxyTelemetryCollector {
|
|
|
118
120
|
v: 1,
|
|
119
121
|
proxy: {
|
|
120
122
|
provider: serving.provider,
|
|
123
|
+
...(aggregate.userAgentSource ? { userAgentSource: aggregate.userAgentSource } : {}),
|
|
121
124
|
...(serving.protocol ? { protocol: serving.protocol } : {}),
|
|
122
125
|
cacheStatus: aggregate.cacheStatus,
|
|
123
126
|
cacheHit: aggregate.cacheHit,
|
|
@@ -0,0 +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_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, type ResolverAdapterFactory, type ResolverInstrumentationMetadata, type ResolverRuntimeOptions, } from "./resolver.js";
|
|
@@ -0,0 +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_TIMEOUT_MS, invalidateResolverSolution, RESOLVER_ADAPTER_REGISTRY, RESOLVER_INSTRUMENTATION_METADATA, } from "./resolver.js";
|
|
@@ -204,8 +204,9 @@ export function createBrowserResolverVendorAdapter(options) {
|
|
|
204
204
|
});
|
|
205
205
|
},
|
|
206
206
|
async solve(challenge, identity, callerSignal, traceRecorder) {
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
const cdpUrl = options.cdpUrl?.trim();
|
|
208
|
+
const proxyUrl = identity?.proxyUrl;
|
|
209
|
+
if (!cdpUrl && proxyUrl === undefined) {
|
|
209
210
|
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "missing_credentials");
|
|
210
211
|
}
|
|
211
212
|
if (!isSupportedKind(challenge.kind)) {
|
|
@@ -214,6 +215,14 @@ export function createBrowserResolverVendorAdapter(options) {
|
|
|
214
215
|
assertResolverHostAllowed(challenge.pageUrl, options.allowedHosts);
|
|
215
216
|
const challengeKind = challenge.kind;
|
|
216
217
|
callerSignal.throwIfAborted();
|
|
218
|
+
if (proxyUrl !== undefined && proxyUrl.length === 0) {
|
|
219
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "missing_proxy_identity");
|
|
220
|
+
}
|
|
221
|
+
if (cdpUrl && proxyUrl !== undefined) {
|
|
222
|
+
// The current pool acquire protocol cannot bind a proxy to its browser context.
|
|
223
|
+
// Refuse so the chain can choose a vendor that can honor the resolved identity.
|
|
224
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "not_implemented");
|
|
225
|
+
}
|
|
217
226
|
const solveController = new AbortController();
|
|
218
227
|
const onCallerAbort = () => solveController.abort(abortReason(callerSignal));
|
|
219
228
|
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
@@ -223,8 +232,9 @@ export function createBrowserResolverVendorAdapter(options) {
|
|
|
223
232
|
try {
|
|
224
233
|
client = createClient({
|
|
225
234
|
allowedHosts: [...options.allowedHosts],
|
|
226
|
-
cdpUrl:
|
|
227
|
-
|
|
235
|
+
cdpUrl: cdpUrl ?? "",
|
|
236
|
+
...(proxyUrl === undefined ? {} : { proxy: proxyUrl }),
|
|
237
|
+
requireCdpPool: cdpUrl !== undefined,
|
|
228
238
|
});
|
|
229
239
|
const contextOperation = client.withIsolatedContext(async (page) => {
|
|
230
240
|
handlerEntered = true;
|