@apifuse/provider-sdk 2.2.0-beta.23 → 2.2.0-beta.24
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 +2 -0
- package/bin/apifuse-pack-types.ts +234 -38
- package/bin/apifuse-perf.ts +15 -12
- package/bin/apifuse-record.ts +2 -0
- package/dist/config/loader.d.ts +8 -19
- package/dist/config/loader.js +28 -86
- package/dist/define.d.ts +4 -1
- package/dist/define.js +64 -6
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/provider.d.ts +1 -1
- package/dist/runtime/auth-flow.js +2 -0
- package/dist/runtime/browser.js +50 -0
- package/dist/runtime/http.js +0 -1
- package/dist/runtime/instrumentation.js +26 -1
- package/dist/runtime/resolver-vendors/bindings.d.ts +8 -0
- package/dist/runtime/resolver-vendors/bindings.js +15 -0
- package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
- package/dist/runtime/resolver-vendors/browser.js +287 -0
- package/dist/runtime/resolver-vendors/types.d.ts +42 -0
- package/dist/runtime/resolver-vendors/types.js +57 -0
- package/dist/runtime/resolver.d.ts +39 -0
- package/dist/runtime/resolver.js +414 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +245 -141
- package/dist/runtime/stealth.js +3 -6
- package/dist/server/serve.d.ts +4 -1
- package/dist/server/serve.js +35 -8
- package/dist/testing/run.js +7 -0
- package/dist/types.d.ts +112 -4
- package/package.json +1 -1
- package/src/config/loader.ts +35 -111
- package/src/define.ts +105 -8
- package/src/index.ts +20 -1
- package/src/provider.ts +1 -0
- package/src/runtime/auth-flow.ts +2 -0
- package/src/runtime/browser.ts +69 -0
- package/src/runtime/http.ts +0 -1
- package/src/runtime/instrumentation.ts +36 -2
- package/src/runtime/resolver-vendors/bindings.ts +31 -0
- package/src/runtime/resolver-vendors/browser.ts +420 -0
- package/src/runtime/resolver-vendors/types.ts +113 -0
- package/src/runtime/resolver.ts +668 -0
- package/src/runtime/state.ts +323 -166
- package/src/runtime/stealth.ts +3 -6
- package/src/server/serve.ts +73 -5
- package/src/testing/run.ts +8 -0
- package/src/types.ts +130 -4
package/src/define.ts
CHANGED
|
@@ -20,21 +20,24 @@ import type {
|
|
|
20
20
|
HealthJourneySchedule,
|
|
21
21
|
HealthScheduleRandomization,
|
|
22
22
|
InferSchemaOutput,
|
|
23
|
+
NativeProviderConfig,
|
|
23
24
|
OperationDefinition,
|
|
24
25
|
OperationHandlerResult,
|
|
25
26
|
OperationHttpStreamTransport,
|
|
26
27
|
OperationSseTransport,
|
|
27
28
|
OperationTransport,
|
|
28
29
|
OperationWebSocketTransport,
|
|
29
|
-
NativeProviderConfig,
|
|
30
|
-
ProviderOcrConfig,
|
|
31
30
|
ProviderAccessConfig,
|
|
31
|
+
ProviderChallengeKind,
|
|
32
32
|
ProviderDefinition,
|
|
33
|
+
ProviderOcrConfig,
|
|
33
34
|
ProviderDeploymentOverrides,
|
|
34
35
|
ProviderHealthMonitorConfig,
|
|
35
36
|
ProviderProxyConfig,
|
|
36
37
|
ProviderProxyProvider,
|
|
37
38
|
ProviderPublicProfile,
|
|
39
|
+
ProviderResolverConfig,
|
|
40
|
+
ProviderResolverVendor,
|
|
38
41
|
ProviderReviewed,
|
|
39
42
|
ProviderSecretDeclaration,
|
|
40
43
|
ProviderStreamEvent,
|
|
@@ -126,6 +129,30 @@ const VALID_PROVIDER_PROXY_AFFINITIES = [
|
|
|
126
129
|
] as const;
|
|
127
130
|
const VALID_PROVIDER_OCR_MODES = ["optional", "required"] as const;
|
|
128
131
|
const VALID_PROVIDER_STT_MODES = ["optional", "required"] as const;
|
|
132
|
+
function exhaustiveLiteralArray<TUnion extends string>() {
|
|
133
|
+
return <const TValues extends readonly TUnion[]>(
|
|
134
|
+
values: TValues,
|
|
135
|
+
..._missing: Exclude<TUnion, TValues[number]> extends never
|
|
136
|
+
? []
|
|
137
|
+
: ["Missing runtime values", Exclude<TUnion, TValues[number]>]
|
|
138
|
+
): TValues => values;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const VALID_PROVIDER_RESOLVER_VENDORS = exhaustiveLiteralArray<ProviderResolverVendor>()([
|
|
142
|
+
"browser",
|
|
143
|
+
"capsolver",
|
|
144
|
+
"capmonster",
|
|
145
|
+
"2captcha",
|
|
146
|
+
"custom",
|
|
147
|
+
] as const);
|
|
148
|
+
export const VALID_PROVIDER_CHALLENGE_KINDS = exhaustiveLiteralArray<ProviderChallengeKind>()([
|
|
149
|
+
"turnstile",
|
|
150
|
+
"recaptcha_v2",
|
|
151
|
+
"recaptcha_v3",
|
|
152
|
+
"hcaptcha",
|
|
153
|
+
"cloudflare_interstitial",
|
|
154
|
+
"aws_waf",
|
|
155
|
+
] as const);
|
|
129
156
|
const SMARTPROXY_APP_KEY_SECRET = "APIFUSE__PROXY__SMARTPROXY_APP_KEY";
|
|
130
157
|
const NODEMAVEN_USERNAME_SECRET = "APIFUSE__PROXY__NODEMAVEN_USERNAME";
|
|
131
158
|
const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
|
|
@@ -134,9 +161,8 @@ const NODEMAVEN_PASSWORD_SECRET = "APIFUSE__PROXY__NODEMAVEN_PASSWORD";
|
|
|
134
161
|
// credential fails at build/validation time rather than during a live outage: a
|
|
135
162
|
// declared-but-uncredentialed fallback leg is a silently dead SPOF, which is
|
|
136
163
|
// exactly the failure class the multi-vendor chain exists to remove. Vendors
|
|
137
|
-
// absent from this map (
|
|
138
|
-
//
|
|
139
|
-
// no declaration requirement.
|
|
164
|
+
// absent from this map (the deprecated `custom`/`decodo` values have no managed
|
|
165
|
+
// adapter) impose no declaration requirement.
|
|
140
166
|
const VENDOR_REQUIRED_SECRETS: Partial<Record<ProviderProxyProvider, readonly string[]>> = {
|
|
141
167
|
smartproxy: [SMARTPROXY_APP_KEY_SECRET],
|
|
142
168
|
nodemaven: [NODEMAVEN_USERNAME_SECRET, NODEMAVEN_PASSWORD_SECRET],
|
|
@@ -252,6 +278,7 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
252
278
|
proxy?: ProviderProxyConfig;
|
|
253
279
|
ocr?: ProviderOcrConfig;
|
|
254
280
|
stt?: ProviderSttConfig;
|
|
281
|
+
resolver?: ProviderResolverConfig;
|
|
255
282
|
browser?: { engine: BrowserEngine };
|
|
256
283
|
auth?: AuthConfig;
|
|
257
284
|
reviewed?: ProviderReviewed;
|
|
@@ -681,9 +708,21 @@ function validateProviderProxy(config: {
|
|
|
681
708
|
const deprecatedVendors = vendorChain.filter(
|
|
682
709
|
(vendor) => vendor === "decodo" || vendor === "custom",
|
|
683
710
|
);
|
|
711
|
+
if (
|
|
712
|
+
proxy.mode === "required" &&
|
|
713
|
+
vendorChain.length > 0 &&
|
|
714
|
+
deprecatedVendors.length === vendorChain.length
|
|
715
|
+
) {
|
|
716
|
+
throw new ValidationError(
|
|
717
|
+
`Provider "${config.id}" requires proxy egress but declares only deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}.`,
|
|
718
|
+
{
|
|
719
|
+
fix: `Use proxy.provider or proxy.providers with "smartproxy" and/or "nodemaven".`,
|
|
720
|
+
},
|
|
721
|
+
);
|
|
722
|
+
}
|
|
684
723
|
if (deprecatedVendors.length > 0) {
|
|
685
724
|
console.warn(
|
|
686
|
-
`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven"
|
|
725
|
+
`[provider-sdk] Provider "${config.id}" uses deprecated proxy vendor(s): ${deprecatedVendors.join(", ")}. Use "smartproxy"/"nodemaven".`,
|
|
687
726
|
);
|
|
688
727
|
}
|
|
689
728
|
}
|
|
@@ -712,6 +751,53 @@ function validateProviderOcr(config: { id: string; ocr?: ProviderOcrConfig }): v
|
|
|
712
751
|
assertLiteralField(ocr.mode, "ocr.mode", VALID_PROVIDER_OCR_MODES, config.id);
|
|
713
752
|
}
|
|
714
753
|
|
|
754
|
+
function validateProviderResolver(config: { id: string; resolver?: ProviderResolverConfig }): void {
|
|
755
|
+
const resolver = config.resolver;
|
|
756
|
+
if (resolver === undefined) return;
|
|
757
|
+
if (!resolver || typeof resolver !== "object" || Array.isArray(resolver)) {
|
|
758
|
+
throw new ValidationError(`Provider "${config.id}" has invalid resolver: must be an object.`, {
|
|
759
|
+
fix: `Set resolver for provider "${config.id}" to { vendors: ["2captcha"], kinds: ["turnstile"] }.`,
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
rejectUnknownFields(resolver, new Set(["vendors", "kinds"]), "resolver", config.id);
|
|
763
|
+
validateResolverLiteralArray(
|
|
764
|
+
resolver.vendors,
|
|
765
|
+
"resolver.vendors",
|
|
766
|
+
VALID_PROVIDER_RESOLVER_VENDORS,
|
|
767
|
+
config.id,
|
|
768
|
+
);
|
|
769
|
+
validateResolverLiteralArray(
|
|
770
|
+
resolver.kinds,
|
|
771
|
+
"resolver.kinds",
|
|
772
|
+
VALID_PROVIDER_CHALLENGE_KINDS,
|
|
773
|
+
config.id,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function validateResolverLiteralArray<TValue extends string>(
|
|
778
|
+
value: readonly TValue[],
|
|
779
|
+
field: string,
|
|
780
|
+
validValues: readonly TValue[],
|
|
781
|
+
providerId: string,
|
|
782
|
+
): void {
|
|
783
|
+
if (!Array.isArray(value)) {
|
|
784
|
+
throw new ValidationError(`Provider "${providerId}" has invalid ${field}: must be an array.`, {
|
|
785
|
+
fix: `Set ${field} for provider "${providerId}" to an array containing only: ${validValues.join(", ")}.`,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
for (const [index, item] of value.entries()) {
|
|
789
|
+
if (typeof item === "string" && validValues.some((validValue) => validValue === item)) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
throw new ValidationError(
|
|
793
|
+
`Provider "${providerId}" has invalid ${field}[${index}]: ${JSON.stringify(item)}. Expected one of: ${validValues.join(", ")}`,
|
|
794
|
+
{
|
|
795
|
+
fix: `Set ${field}[${index}] for provider "${providerId}" to one of ${validValues.map((validValue) => `"${validValue}"`).join(", ")}.`,
|
|
796
|
+
},
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
715
801
|
function validateOperationIds(
|
|
716
802
|
providerId: string,
|
|
717
803
|
operations: Record<string, ProviderOperation>,
|
|
@@ -1309,7 +1395,12 @@ function suggestField(unknown: string, candidates: ReadonlySet<string>): string
|
|
|
1309
1395
|
return best;
|
|
1310
1396
|
}
|
|
1311
1397
|
|
|
1312
|
-
function rejectUnknownFields(
|
|
1398
|
+
function rejectUnknownFields(
|
|
1399
|
+
value: object,
|
|
1400
|
+
allowed: ReadonlySet<string>,
|
|
1401
|
+
fieldPath: string,
|
|
1402
|
+
providerId?: string,
|
|
1403
|
+
): void {
|
|
1313
1404
|
for (const key of Object.keys(value)) {
|
|
1314
1405
|
if (allowed.has(key)) continue;
|
|
1315
1406
|
const hint = suggestField(key, allowed);
|
|
@@ -1317,7 +1408,11 @@ function rejectUnknownFields(value: object, allowed: ReadonlySet<string>, fieldP
|
|
|
1317
1408
|
hint
|
|
1318
1409
|
? `Unknown field "${key}" on ${fieldPath}. Did you mean "${hint}"?`
|
|
1319
1410
|
: `Unknown field "${key}" on ${fieldPath}.`,
|
|
1320
|
-
{
|
|
1411
|
+
{
|
|
1412
|
+
fix: providerId
|
|
1413
|
+
? `Remove ${fieldPath}.${key} from provider "${providerId}" or rename it.`
|
|
1414
|
+
: `Remove ${fieldPath}.${key} or rename it.`,
|
|
1415
|
+
},
|
|
1321
1416
|
);
|
|
1322
1417
|
}
|
|
1323
1418
|
}
|
|
@@ -2403,6 +2498,7 @@ export function defineProvider<
|
|
|
2403
2498
|
validateProviderProxy(config);
|
|
2404
2499
|
validateProviderOcr(config);
|
|
2405
2500
|
validateProviderStt(config);
|
|
2501
|
+
validateProviderResolver(config);
|
|
2406
2502
|
if (config.runtime === "browser" && !config.browser)
|
|
2407
2503
|
throw new ProviderError(
|
|
2408
2504
|
`Provider "${config.id}" must define browser.engine when runtime is "browser"`,
|
|
@@ -2428,6 +2524,7 @@ export function defineProvider<
|
|
|
2428
2524
|
proxy: config.proxy,
|
|
2429
2525
|
ocr: config.ocr,
|
|
2430
2526
|
stt: config.stt,
|
|
2527
|
+
resolver: config.resolver,
|
|
2431
2528
|
browser: config.browser,
|
|
2432
2529
|
auth: config.auth,
|
|
2433
2530
|
reviewed: config.reviewed,
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ export * from "./choice-token.js";
|
|
|
6
6
|
export type {
|
|
7
7
|
ApiFuseConfig,
|
|
8
8
|
BrowserConfig,
|
|
9
|
-
ProxyConfig,
|
|
10
9
|
ProxyProtocol,
|
|
11
10
|
ProxyResolutionOptions,
|
|
12
11
|
ProxyResolutionSource,
|
|
@@ -101,6 +100,18 @@ export {
|
|
|
101
100
|
} from "./runtime/instrumentation.js";
|
|
102
101
|
export { type PrevalidateResult, prevalidate } from "./runtime/prevalidate.js";
|
|
103
102
|
export { getProviderBaseUrl } from "./runtime/provider.js";
|
|
103
|
+
export {
|
|
104
|
+
APIFUSE__CDP_POOL__URL,
|
|
105
|
+
APIFUSE__RESOLVER__2CAPTCHA__API_KEY,
|
|
106
|
+
APIFUSE__RESOLVER__CAPMONSTER__API_KEY,
|
|
107
|
+
APIFUSE__RESOLVER__CAPSOLVER__API_KEY,
|
|
108
|
+
APIFUSE__RESOLVER__TIMEOUT_MS,
|
|
109
|
+
createResolverClientFromEnv,
|
|
110
|
+
createUnsupportedResolverClient,
|
|
111
|
+
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
112
|
+
invalidateResolverSolution,
|
|
113
|
+
type ResolverRuntimeOptions,
|
|
114
|
+
} from "./runtime/resolver.js";
|
|
104
115
|
export {
|
|
105
116
|
assertRequiredSecretsPresent,
|
|
106
117
|
listMissingRequiredSecrets,
|
|
@@ -177,6 +188,7 @@ export type {
|
|
|
177
188
|
AuthMode,
|
|
178
189
|
AuthTurn,
|
|
179
190
|
Bcp47Locale,
|
|
191
|
+
BrowserCookie,
|
|
180
192
|
BrowserEngine,
|
|
181
193
|
BrowserOptions,
|
|
182
194
|
BrowserResourceBody,
|
|
@@ -185,6 +197,7 @@ export type {
|
|
|
185
197
|
BrowserResourcePolicy,
|
|
186
198
|
BrowserResourceRequest,
|
|
187
199
|
BrowserResourceRoute,
|
|
200
|
+
ChallengeSolution,
|
|
188
201
|
ConnectionMode,
|
|
189
202
|
ContextDeclaration,
|
|
190
203
|
CookieJar,
|
|
@@ -285,6 +298,8 @@ export type {
|
|
|
285
298
|
ProviderChoiceContext,
|
|
286
299
|
ProviderChoiceIssueOptions,
|
|
287
300
|
ProviderChoiceParseOptions,
|
|
301
|
+
ProviderChallenge,
|
|
302
|
+
ProviderChallengeKind,
|
|
288
303
|
ProviderContext,
|
|
289
304
|
ProviderDefinition,
|
|
290
305
|
ProviderDeploymentOverrides,
|
|
@@ -309,6 +324,8 @@ export type {
|
|
|
309
324
|
ProviderPublicProfile,
|
|
310
325
|
ProviderReviewed,
|
|
311
326
|
ProviderResolvedFile,
|
|
327
|
+
ProviderResolverConfig,
|
|
328
|
+
ProviderResolverVendor,
|
|
312
329
|
ProviderRuntimeState,
|
|
313
330
|
ProviderSecretDeclaration,
|
|
314
331
|
ProviderStateDurationString,
|
|
@@ -319,6 +336,7 @@ export type {
|
|
|
319
336
|
ProviderSupportLevel,
|
|
320
337
|
RequestOptions,
|
|
321
338
|
RedirectRunReason,
|
|
339
|
+
ResolverContext,
|
|
322
340
|
Rfc3339Instant,
|
|
323
341
|
SchemaLike,
|
|
324
342
|
SmsOrigin,
|
|
@@ -328,6 +346,7 @@ export type {
|
|
|
328
346
|
StandardSchemaV1,
|
|
329
347
|
StateCasResult,
|
|
330
348
|
StateNamespaceOptions,
|
|
349
|
+
StateNamespaceScope,
|
|
331
350
|
StateValue,
|
|
332
351
|
StateWriteOptions,
|
|
333
352
|
StealthClient,
|
package/src/provider.ts
CHANGED
package/src/runtime/auth-flow.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SttContext,
|
|
11
11
|
} from "../types.js";
|
|
12
12
|
import { createUnsupportedOcrClient } from "./ocr.js";
|
|
13
|
+
import { createUnsupportedResolverClient } from "./resolver.js";
|
|
13
14
|
import { createUnsupportedSttClient } from "./stt.js";
|
|
14
15
|
|
|
15
16
|
function normalizeAllowedKeys(allowedKeys: string[]): Set<string> {
|
|
@@ -75,6 +76,7 @@ export function createFlowContext(options: {
|
|
|
75
76
|
context: createScratchpad(options.allowedKeys, options.initialContext),
|
|
76
77
|
ocr: options.ocr ?? createUnsupportedOcrClient(),
|
|
77
78
|
stt: options.stt ?? createUnsupportedSttClient(),
|
|
79
|
+
resolver: createUnsupportedResolverClient("Resolver is not available in auth flow context"),
|
|
78
80
|
auth: createAuthFlowHelpers(),
|
|
79
81
|
};
|
|
80
82
|
}
|
package/src/runtime/browser.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
BrowserChallengeRequest,
|
|
7
7
|
BrowserChallengeResult,
|
|
8
8
|
BrowserClient as BrowserClientContract,
|
|
9
|
+
BrowserCookie,
|
|
9
10
|
BrowserEngine,
|
|
10
11
|
BrowserFrame,
|
|
11
12
|
BrowserLocator,
|
|
@@ -545,6 +546,10 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
545
546
|
await this.page.close();
|
|
546
547
|
}
|
|
547
548
|
|
|
549
|
+
async cookies(): Promise<readonly BrowserCookie[]> {
|
|
550
|
+
return (await this.page.context().cookies()).map(toBrowserCookie);
|
|
551
|
+
}
|
|
552
|
+
|
|
548
553
|
async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
|
|
549
554
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
550
555
|
const handler = async (route: Route): Promise<void> => {
|
|
@@ -845,6 +850,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
845
850
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
846
851
|
}
|
|
847
852
|
|
|
853
|
+
function isBrowserCookieSameSite(value: unknown): value is BrowserCookie["sameSite"] {
|
|
854
|
+
return value === "Strict" || value === "Lax" || value === "None";
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function toBrowserCookie(cookie: {
|
|
858
|
+
readonly name: string;
|
|
859
|
+
readonly value: string;
|
|
860
|
+
readonly domain: string;
|
|
861
|
+
readonly path: string;
|
|
862
|
+
readonly expires?: number;
|
|
863
|
+
readonly httpOnly: boolean;
|
|
864
|
+
readonly secure: boolean;
|
|
865
|
+
readonly sameSite?: unknown;
|
|
866
|
+
}): BrowserCookie {
|
|
867
|
+
return {
|
|
868
|
+
name: cookie.name,
|
|
869
|
+
value: cookie.value,
|
|
870
|
+
domain: cookie.domain,
|
|
871
|
+
path: cookie.path,
|
|
872
|
+
...(cookie.expires !== undefined && cookie.expires > 0 ? { expires: cookie.expires } : {}),
|
|
873
|
+
httpOnly: cookie.httpOnly,
|
|
874
|
+
secure: cookie.secure,
|
|
875
|
+
...(isBrowserCookieSameSite(cookie.sameSite) ? { sameSite: cookie.sameSite } : {}),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function parseCdpCookies(value: unknown): readonly BrowserCookie[] {
|
|
880
|
+
if (!Array.isArray(value)) {
|
|
881
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie list");
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return value.map((cookie) => {
|
|
885
|
+
if (
|
|
886
|
+
!isRecord(cookie) ||
|
|
887
|
+
typeof cookie.name !== "string" ||
|
|
888
|
+
typeof cookie.value !== "string" ||
|
|
889
|
+
typeof cookie.domain !== "string" ||
|
|
890
|
+
typeof cookie.path !== "string" ||
|
|
891
|
+
typeof cookie.expires !== "number" ||
|
|
892
|
+
typeof cookie.httpOnly !== "boolean" ||
|
|
893
|
+
typeof cookie.secure !== "boolean"
|
|
894
|
+
) {
|
|
895
|
+
throw new Error("CDP Network.getCookies returned an invalid cookie");
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return toBrowserCookie({
|
|
899
|
+
name: cookie.name,
|
|
900
|
+
value: cookie.value,
|
|
901
|
+
domain: cookie.domain,
|
|
902
|
+
path: cookie.path,
|
|
903
|
+
expires: cookie.expires,
|
|
904
|
+
httpOnly: cookie.httpOnly,
|
|
905
|
+
secure: cookie.secure,
|
|
906
|
+
sameSite: cookie.sameSite,
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
848
911
|
function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
|
|
849
912
|
if (
|
|
850
913
|
!isRecord(value) ||
|
|
@@ -1200,6 +1263,12 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1200
1263
|
return Buffer.from(String(result.data ?? ""), "base64");
|
|
1201
1264
|
}
|
|
1202
1265
|
|
|
1266
|
+
async cookies(): Promise<readonly BrowserCookie[]> {
|
|
1267
|
+
await this.initialize();
|
|
1268
|
+
const result = await this.pageClient.send("Network.getCookies");
|
|
1269
|
+
return parseCdpCookies(result.cookies);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1203
1272
|
async close(): Promise<void> {
|
|
1204
1273
|
if (this.closed) {
|
|
1205
1274
|
return;
|
package/src/runtime/http.ts
CHANGED
|
@@ -517,7 +517,6 @@ async function resolveNativeProxy(
|
|
|
517
517
|
const resolvedProxy = await resolveProxyConfigAsync({
|
|
518
518
|
proxy: options.proxy ?? clientOptions.proxy,
|
|
519
519
|
upstream: clientOptions.upstream,
|
|
520
|
-
apifuseConfig: clientOptions.apifuseConfig,
|
|
521
520
|
proxyPolicy: clientOptions.proxyPolicy,
|
|
522
521
|
affinityKey: clientOptions.affinityKey,
|
|
523
522
|
proxyAttempt: computeProxyAttemptIndex({
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
getTraceRecorder,
|
|
24
24
|
type TraceContext,
|
|
25
25
|
} from "./trace.js";
|
|
26
|
+
import { RESOLVER_INSTRUMENTATION_METADATA } from "./resolver.js";
|
|
26
27
|
|
|
27
28
|
export interface InstrumentationOptions extends CreateTraceContextOptions {}
|
|
28
29
|
|
|
@@ -30,7 +31,7 @@ export type InstrumentedProviderContext<T extends ProviderContext> = Omit<T, "tr
|
|
|
30
31
|
trace: TraceContext;
|
|
31
32
|
};
|
|
32
33
|
|
|
33
|
-
type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state";
|
|
34
|
+
type InstrumentedNamespace = "http" | "stealth" | "browser" | "session" | "state" | "resolver";
|
|
34
35
|
|
|
35
36
|
const BROWSER_PAGE_METHODS = new Set(["goto", "fill", "click", "type", "waitForSelector"]);
|
|
36
37
|
const DIAGNOSTIC_BASE_URL = "http://apifuse-instrumentation.invalid";
|
|
@@ -621,15 +622,47 @@ function wrapNamespace<T extends object>(
|
|
|
621
622
|
}
|
|
622
623
|
|
|
623
624
|
const wrappedMethods = new Map<PropertyKey, unknown>();
|
|
625
|
+
const resolverMetadata =
|
|
626
|
+
namespace === "resolver" ? { target, traceRecorder: recorder } : undefined;
|
|
624
627
|
|
|
625
628
|
return new Proxy(target, {
|
|
626
629
|
get(namespaceTarget, property, receiver) {
|
|
630
|
+
if (property === RESOLVER_INSTRUMENTATION_METADATA && resolverMetadata) {
|
|
631
|
+
return resolverMetadata;
|
|
632
|
+
}
|
|
627
633
|
const value = Reflect.get(namespaceTarget, property, receiver);
|
|
628
634
|
|
|
629
635
|
if (typeof value !== "function" || property === "constructor") {
|
|
630
636
|
return value;
|
|
631
637
|
}
|
|
632
638
|
|
|
639
|
+
if (namespace === "resolver" && property === "solve") {
|
|
640
|
+
if (wrappedMethods.has(property)) {
|
|
641
|
+
return wrappedMethods.get(property);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const wrapped = (...args: unknown[]) => {
|
|
645
|
+
const challenge = args[0];
|
|
646
|
+
const challengeKind =
|
|
647
|
+
typeof challenge === "object" &&
|
|
648
|
+
challenge !== null &&
|
|
649
|
+
"kind" in challenge &&
|
|
650
|
+
typeof challenge.kind === "string"
|
|
651
|
+
? challenge.kind
|
|
652
|
+
: undefined;
|
|
653
|
+
return recorder.runSpan(
|
|
654
|
+
"resolver.solve",
|
|
655
|
+
() => Reflect.apply(value, namespaceTarget, [args[0], args[1], recorder]),
|
|
656
|
+
{
|
|
657
|
+
attributes: challengeKind ? { challenge_kind: challengeKind } : undefined,
|
|
658
|
+
},
|
|
659
|
+
);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
wrappedMethods.set(property, wrapped);
|
|
663
|
+
return wrapped;
|
|
664
|
+
}
|
|
665
|
+
|
|
633
666
|
if (namespace === "browser" && property === "newPage") {
|
|
634
667
|
if (wrappedMethods.has(property)) {
|
|
635
668
|
return wrappedMethods.get(property);
|
|
@@ -838,7 +871,8 @@ export function wrapWithInstrumentation<T extends ProviderContext>(
|
|
|
838
871
|
property === "stealth" ||
|
|
839
872
|
property === "browser" ||
|
|
840
873
|
property === "session" ||
|
|
841
|
-
property === "state"
|
|
874
|
+
property === "state" ||
|
|
875
|
+
property === "resolver"
|
|
842
876
|
) {
|
|
843
877
|
const namespace = property;
|
|
844
878
|
if (wrappedTargets.has(namespace)) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ProviderChallenge, ProviderChallengeKind } from "../../types.js";
|
|
2
|
+
import type { ResolverIssuingIdentity } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const RESOLVER_CHALLENGE_BINDINGS = {
|
|
5
|
+
aws_waf: "portable",
|
|
6
|
+
cloudflare_interstitial: "identity_scoped",
|
|
7
|
+
} as const satisfies Partial<
|
|
8
|
+
Readonly<Record<ProviderChallengeKind, "identity_scoped" | "portable">>
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
export function resolverChallengeIsIdentityScoped(challenge: ProviderChallenge): boolean {
|
|
12
|
+
return (
|
|
13
|
+
RESOLVER_CHALLENGE_BINDINGS[challenge.kind as keyof typeof RESOLVER_CHALLENGE_BINDINGS] ===
|
|
14
|
+
"identity_scoped"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolverChallengeIssuingIdentity(
|
|
19
|
+
challenge: ProviderChallenge,
|
|
20
|
+
identity: ResolverIssuingIdentity,
|
|
21
|
+
): ResolverIssuingIdentity {
|
|
22
|
+
const binding = (
|
|
23
|
+
RESOLVER_CHALLENGE_BINDINGS as Partial<
|
|
24
|
+
Readonly<Record<ProviderChallengeKind, "identity_scoped" | "portable">>
|
|
25
|
+
>
|
|
26
|
+
)[challenge.kind];
|
|
27
|
+
if (binding === "portable") {
|
|
28
|
+
return { userAgent: identity.userAgent };
|
|
29
|
+
}
|
|
30
|
+
return identity;
|
|
31
|
+
}
|