@apifuse/provider-sdk 2.2.0-beta.36 → 2.2.0-beta.37
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 +22 -8
- package/CHANGELOG.md +4 -0
- package/README.md +20 -8
- package/bin/apifuse-dev.ts +1 -1
- package/bin/apifuse-pack-types.ts +6 -5
- package/bin/apifuse-record.ts +1 -1
- package/bin/apifuse-submit-check.ts +23 -10
- package/dist/cli/templates/provider/index.ts.tpl +6 -3
- package/dist/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/dist/define.d.ts +39 -21
- package/dist/define.js +28 -9
- package/dist/index.d.ts +2 -2
- package/dist/provider.d.ts +2 -1
- package/dist/runtime/browser.js +19 -11
- package/dist/runtime/resolver-public.d.ts +1 -1
- package/dist/runtime/resolver-public.js +1 -1
- package/dist/runtime/resolver-vendors/browser.js +57 -14
- package/dist/runtime/resolver-vendors/types.d.ts +9 -1
- package/dist/runtime/resolver-vendors/types.js +15 -0
- package/dist/runtime/resolver.d.ts +1 -0
- package/dist/runtime/resolver.js +13 -7
- package/dist/server/serve-implementation.js +25 -0
- package/dist/types.d.ts +25 -17
- package/package.json +1 -1
- package/src/cli/templates/provider/index.ts.tpl +6 -3
- package/src/cli/templates/provider/operations/ping.ts.tpl +2 -1
- package/src/define.ts +135 -51
- package/src/index.ts +4 -2
- package/src/provider.ts +6 -1
- package/src/runtime/browser.ts +34 -11
- package/src/runtime/resolver-public.ts +2 -0
- package/src/runtime/resolver-vendors/browser.ts +69 -11
- package/src/runtime/resolver-vendors/types.ts +21 -0
- package/src/runtime/resolver.ts +17 -5
- package/src/server/serve-implementation.ts +39 -1
- package/src/testing/run.ts +3 -3
- package/src/types.ts +41 -17
package/src/define.ts
CHANGED
|
@@ -31,6 +31,8 @@ import type {
|
|
|
31
31
|
ProviderAccessConfig,
|
|
32
32
|
ProviderChallengeKind,
|
|
33
33
|
ProviderDefinition,
|
|
34
|
+
ProviderContext,
|
|
35
|
+
ProviderContextFor,
|
|
34
36
|
ProviderOcrConfig,
|
|
35
37
|
ProviderDeploymentOverrides,
|
|
36
38
|
ProviderHealthMonitorConfig,
|
|
@@ -197,54 +199,65 @@ function parsePositiveMsDuration(value: string): number | undefined {
|
|
|
197
199
|
return parsed;
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
type ProviderOperation = OperationDefinition<
|
|
201
|
-
type OperationConfig<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
type ProviderOperation = OperationDefinition<any, any, any>;
|
|
203
|
+
type OperationConfig<
|
|
204
|
+
TInput extends SchemaLike,
|
|
205
|
+
TOutput extends SchemaLike,
|
|
206
|
+
TContext = ProviderContext,
|
|
207
|
+
> = Omit<OperationDefinition<TInput, TOutput, TContext>, "handler"> & {
|
|
205
208
|
handler(
|
|
206
|
-
ctx:
|
|
209
|
+
ctx: TContext,
|
|
207
210
|
input: InferSchemaOutput<TInput>,
|
|
208
211
|
):
|
|
209
212
|
| OperationHandlerResult<InferSchemaOutput<TOutput>>
|
|
210
213
|
| Promise<OperationHandlerResult<InferSchemaOutput<TOutput>>>;
|
|
211
214
|
};
|
|
212
|
-
type OperationMapConfig<
|
|
215
|
+
type OperationMapConfig<
|
|
216
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
217
|
+
TContext = ProviderContext,
|
|
218
|
+
> = {
|
|
213
219
|
[K in keyof TOperations]: TOperations[K] extends OperationDefinition<infer TInput, infer TOutput>
|
|
214
|
-
? OperationConfig<TInput, TOutput> | OperationDefinition<TInput, TOutput>
|
|
220
|
+
? OperationConfig<TInput, TOutput, TContext> | OperationDefinition<TInput, TOutput, TContext>
|
|
215
221
|
: never;
|
|
216
222
|
};
|
|
217
|
-
type StreamOperationConfig<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
type StreamOperationConfig<
|
|
224
|
+
TInput extends SchemaLike,
|
|
225
|
+
TOutput extends SchemaLike,
|
|
226
|
+
TContext = ProviderContext,
|
|
227
|
+
> =
|
|
228
|
+
| SseOperationConfig<TInput, TOutput, TContext>
|
|
229
|
+
| HttpStreamOperationConfig<TInput, TOutput, TContext>
|
|
230
|
+
| WebSocketOperationConfig<TInput, TOutput, TContext>;
|
|
231
|
+
type SseOperationConfig<
|
|
232
|
+
TInput extends SchemaLike,
|
|
233
|
+
TOutput extends SchemaLike,
|
|
234
|
+
TContext = ProviderContext,
|
|
235
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
225
236
|
transport: OperationSseTransport;
|
|
226
237
|
handler(
|
|
227
|
-
ctx:
|
|
238
|
+
ctx: TContext,
|
|
228
239
|
input: InferSchemaOutput<TInput>,
|
|
229
240
|
): AsyncIterable<ProviderStreamEvent> | Promise<AsyncIterable<ProviderStreamEvent>>;
|
|
230
241
|
};
|
|
231
|
-
type HttpStreamOperationConfig<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
type HttpStreamOperationConfig<
|
|
243
|
+
TInput extends SchemaLike,
|
|
244
|
+
TOutput extends SchemaLike,
|
|
245
|
+
TContext = ProviderContext,
|
|
246
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
235
247
|
transport: OperationHttpStreamTransport;
|
|
236
248
|
handler(
|
|
237
|
-
ctx:
|
|
249
|
+
ctx: TContext,
|
|
238
250
|
input: InferSchemaOutput<TInput>,
|
|
239
251
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
240
252
|
};
|
|
241
|
-
type WebSocketOperationConfig<
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
type WebSocketOperationConfig<
|
|
254
|
+
TInput extends SchemaLike,
|
|
255
|
+
TOutput extends SchemaLike,
|
|
256
|
+
TContext = ProviderContext,
|
|
257
|
+
> = Omit<OperationConfig<TInput, TOutput, TContext>, "handler" | "transport"> & {
|
|
245
258
|
transport: OperationWebSocketTransport;
|
|
246
259
|
handler(
|
|
247
|
-
ctx:
|
|
260
|
+
ctx: TContext,
|
|
248
261
|
input: InferSchemaOutput<TInput>,
|
|
249
262
|
): Response | ReadableStream<Uint8Array> | Promise<Response | ReadableStream<Uint8Array>>;
|
|
250
263
|
};
|
|
@@ -562,7 +575,7 @@ function authStartHasHiddenInput(start: unknown): boolean {
|
|
|
562
575
|
return /\s=\s/.test(second);
|
|
563
576
|
}
|
|
564
577
|
|
|
565
|
-
export interface
|
|
578
|
+
export interface ProviderDeclaration {
|
|
566
579
|
id: string;
|
|
567
580
|
version: string;
|
|
568
581
|
runtime: "standard" | "shared" | "browser";
|
|
@@ -573,6 +586,8 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
573
586
|
* resolves omitted fields against the runtime deployment profiles.
|
|
574
587
|
*/
|
|
575
588
|
deployment?: ProviderDeploymentOverrides;
|
|
589
|
+
/** Declares that provider operations use the SDK HTTP client. */
|
|
590
|
+
http?: true;
|
|
576
591
|
allowedHosts?: string[];
|
|
577
592
|
native?: NativeProviderConfig;
|
|
578
593
|
stealth?: {
|
|
@@ -585,11 +600,21 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
585
600
|
resolver?: ProviderResolverConfig;
|
|
586
601
|
browser?: { engine: BrowserEngine };
|
|
587
602
|
auth?: AuthConfig;
|
|
603
|
+
/** Declares that provider operations issue and consume SDK choice tokens. */
|
|
604
|
+
choice?: true;
|
|
588
605
|
reviewed?: ProviderReviewed;
|
|
589
606
|
access?: ProviderAccessConfig;
|
|
590
607
|
secrets?: ProviderSecretDeclaration[];
|
|
608
|
+
/** Declares that provider operations read SDK-managed environment values. */
|
|
609
|
+
env?: true;
|
|
591
610
|
credential?: CredentialDeclaration;
|
|
592
611
|
context?: ContextDeclaration;
|
|
612
|
+
/** Declares that provider operations use SDK-managed persistent state. */
|
|
613
|
+
state?: true;
|
|
614
|
+
/** Declares that provider operations use the SDK provider cache. */
|
|
615
|
+
cache?: true;
|
|
616
|
+
/** Declares that provider operations access runtime-resolvable files. */
|
|
617
|
+
files?: true;
|
|
593
618
|
meta: {
|
|
594
619
|
displayName: string;
|
|
595
620
|
displayNameKey?: string;
|
|
@@ -611,25 +636,35 @@ export interface ProviderConfig<TOperations extends Record<string, ProviderOpera
|
|
|
611
636
|
publicSchemaFieldNames?: "normalized";
|
|
612
637
|
};
|
|
613
638
|
};
|
|
614
|
-
operations: OperationMapConfig<TOperations>;
|
|
615
639
|
healthMonitor?: ProviderHealthMonitorConfig;
|
|
616
640
|
/** New name for `healthMonitor` (transitional alias); declaring both is a ValidationError. */
|
|
617
641
|
healthProbe?: ProviderHealthMonitorConfig;
|
|
618
642
|
healthJourneys?: readonly HealthJourneyDefinition[];
|
|
619
643
|
}
|
|
620
644
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
645
|
+
interface ProviderConfig<
|
|
646
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
647
|
+
TContext = ProviderContext,
|
|
648
|
+
> extends ProviderDeclaration {
|
|
649
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Define one factored provider operation with schema-driven handler inference. */
|
|
653
|
+
export function defineOperation<TContext>() {
|
|
654
|
+
return function operation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
655
|
+
config: OperationConfig<TInput, TOutput, TContext>,
|
|
656
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
657
|
+
return config;
|
|
658
|
+
};
|
|
626
659
|
}
|
|
627
660
|
|
|
628
|
-
/** Define a non-JSON
|
|
629
|
-
export function defineStreamOperation<
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
661
|
+
/** Define a factored non-JSON operation with explicit transport metadata. */
|
|
662
|
+
export function defineStreamOperation<TContext>() {
|
|
663
|
+
return function streamOperation<TInput extends SchemaLike, TOutput extends SchemaLike>(
|
|
664
|
+
config: StreamOperationConfig<TInput, TOutput, TContext>,
|
|
665
|
+
): OperationDefinition<TInput, TOutput, TContext> {
|
|
666
|
+
return config;
|
|
667
|
+
};
|
|
633
668
|
}
|
|
634
669
|
|
|
635
670
|
function assertObjectConfig(value: unknown): asserts value is Record<string, unknown> {
|
|
@@ -780,6 +815,14 @@ function validateProviderShape(config: unknown): void {
|
|
|
780
815
|
assertRequiredField(config, "operations", String(config.id));
|
|
781
816
|
if (typeof config.runtime === "string")
|
|
782
817
|
assertLiteralField(config.runtime, "runtime", VALID_RUNTIMES, String(config.id));
|
|
818
|
+
if (config.native !== undefined && config.runtime === "browser") {
|
|
819
|
+
throw new ValidationError(
|
|
820
|
+
`Provider "${String(config.id)}" cannot declare capability "native" with runtime "browser"`,
|
|
821
|
+
{
|
|
822
|
+
fix: 'Use runtime: "standard" or runtime: "shared", or remove the native declaration.',
|
|
823
|
+
},
|
|
824
|
+
);
|
|
825
|
+
}
|
|
783
826
|
const auth = config.auth;
|
|
784
827
|
if (auth && typeof auth === "object" && "mode" in auth && typeof auth.mode === "string")
|
|
785
828
|
assertLiteralField(auth.mode, "auth.mode", VALID_AUTH_MODES, String(config.id));
|
|
@@ -1087,12 +1130,14 @@ function validateProviderResolver(config: { id: string; resolver?: ProviderResol
|
|
|
1087
1130
|
"resolver",
|
|
1088
1131
|
config.id,
|
|
1089
1132
|
);
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1133
|
+
if (resolver.vendors !== undefined) {
|
|
1134
|
+
validateResolverLiteralArray(
|
|
1135
|
+
resolver.vendors,
|
|
1136
|
+
"resolver.vendors",
|
|
1137
|
+
VALID_PROVIDER_RESOLVER_VENDORS,
|
|
1138
|
+
config.id,
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1096
1141
|
validateResolverLiteralArray(
|
|
1097
1142
|
resolver.kinds,
|
|
1098
1143
|
"resolver.kinds",
|
|
@@ -2783,12 +2828,48 @@ function validateProviderDeployment(providerId: string, deployment: unknown): vo
|
|
|
2783
2828
|
});
|
|
2784
2829
|
}
|
|
2785
2830
|
|
|
2786
|
-
|
|
2831
|
+
/** The second authoring phase for a declaration established by defineProvider. */
|
|
2832
|
+
export type ProviderBuilder<TDeclaration extends ProviderDeclaration> = <
|
|
2833
|
+
TOperations extends Record<string, ProviderOperation>,
|
|
2834
|
+
>(
|
|
2835
|
+
implementation: {
|
|
2836
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2837
|
+
},
|
|
2838
|
+
) => ProviderDefinition & {
|
|
2839
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2840
|
+
};
|
|
2841
|
+
|
|
2842
|
+
/** Extract the declaration-derived operation context from a provider builder. */
|
|
2843
|
+
export type ProviderContextOf<TBuilder> = TBuilder extends ProviderBuilder<infer TDeclaration>
|
|
2844
|
+
? ProviderContextFor<TDeclaration>
|
|
2845
|
+
: never;
|
|
2846
|
+
|
|
2847
|
+
/** Establish a provider declaration before its operations are contextually typed. */
|
|
2848
|
+
export function defineProvider<const TDeclaration extends ProviderDeclaration>(
|
|
2849
|
+
declaration: TDeclaration &
|
|
2850
|
+
Record<Exclude<keyof TDeclaration, keyof ProviderDeclaration>, never> &
|
|
2851
|
+
AuthStartNoInputGuard<TDeclaration>,
|
|
2852
|
+
): ProviderBuilder<TDeclaration> {
|
|
2853
|
+
const buildProvider = <TOperations extends Record<string, ProviderOperation>>(
|
|
2854
|
+
implementation: {
|
|
2855
|
+
operations: OperationMapConfig<TOperations, ProviderContextFor<TDeclaration>>;
|
|
2856
|
+
},
|
|
2857
|
+
) =>
|
|
2858
|
+
finalizeProvider({
|
|
2859
|
+
...declaration,
|
|
2860
|
+
...implementation,
|
|
2861
|
+
} as ProviderConfig<TOperations, ProviderContextFor<TDeclaration>>);
|
|
2862
|
+
return buildProvider as ProviderBuilder<TDeclaration>;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
function finalizeProvider<
|
|
2787
2866
|
TOperations extends Record<string, ProviderOperation>,
|
|
2788
|
-
|
|
2867
|
+
TContext,
|
|
2789
2868
|
>(
|
|
2790
|
-
config:
|
|
2791
|
-
): ProviderDefinition & {
|
|
2869
|
+
config: ProviderConfig<TOperations, TContext>,
|
|
2870
|
+
): ProviderDefinition & {
|
|
2871
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2872
|
+
} {
|
|
2792
2873
|
validateProviderShape(config);
|
|
2793
2874
|
const operations = resolveOperationFixtureRequests(config.operations);
|
|
2794
2875
|
if (!CONNECTOR_ID_REGEX.test(config.id))
|
|
@@ -2849,7 +2930,9 @@ export function defineProvider<
|
|
|
2849
2930
|
`Provider "${config.id}" cannot define browser config unless runtime is "browser"`,
|
|
2850
2931
|
{ fix: 'Set runtime: "browser" or remove the browser config' },
|
|
2851
2932
|
);
|
|
2852
|
-
const provider: ProviderDefinition & {
|
|
2933
|
+
const provider: ProviderDefinition & {
|
|
2934
|
+
operations: OperationMapConfig<TOperations, TContext>;
|
|
2935
|
+
} = {
|
|
2853
2936
|
id: config.id,
|
|
2854
2937
|
version: config.version,
|
|
2855
2938
|
runtime: config.runtime,
|
|
@@ -2871,7 +2954,8 @@ export function defineProvider<
|
|
|
2871
2954
|
credential: config.credential,
|
|
2872
2955
|
context: config.context,
|
|
2873
2956
|
meta: config.meta,
|
|
2874
|
-
operations
|
|
2957
|
+
operations: operations as ProviderDefinition["operations"] &
|
|
2958
|
+
OperationMapConfig<TOperations, TContext>,
|
|
2875
2959
|
// Transitional healthMonitor → healthProbe alias: mirror whichever field
|
|
2876
2960
|
// was declared onto both so old and new consumers keep working.
|
|
2877
2961
|
healthMonitor: config.healthMonitor ?? config.healthProbe,
|
package/src/index.ts
CHANGED
|
@@ -34,7 +34,9 @@ export {
|
|
|
34
34
|
defineStreamOperation,
|
|
35
35
|
every,
|
|
36
36
|
type AuthStartNoInputGuard,
|
|
37
|
-
type
|
|
37
|
+
type ProviderBuilder,
|
|
38
|
+
type ProviderContextOf,
|
|
39
|
+
type ProviderDeclaration,
|
|
38
40
|
} from "./define.js";
|
|
39
41
|
export type { DevServerOptions } from "./dev.js";
|
|
40
42
|
export { createDevServer, startDevServer } from "./dev.js";
|
|
@@ -244,7 +246,6 @@ export type {
|
|
|
244
246
|
NativeNetworkDynamicGrantOptions,
|
|
245
247
|
NativeNetworkEgressGrant,
|
|
246
248
|
NativeProviderConfig,
|
|
247
|
-
NativeProviderContext,
|
|
248
249
|
NativeProxyDrainHandler,
|
|
249
250
|
NativeProxyEgressInfo,
|
|
250
251
|
NativeProxyExpiringEvent,
|
|
@@ -301,6 +302,7 @@ export type {
|
|
|
301
302
|
ProviderChallenge,
|
|
302
303
|
ProviderChallengeKind,
|
|
303
304
|
ProviderContext,
|
|
305
|
+
ProviderContextFor,
|
|
304
306
|
ProviderDefinition,
|
|
305
307
|
ProviderDeploymentOverrides,
|
|
306
308
|
ProviderFileRef,
|
package/src/provider.ts
CHANGED
|
@@ -35,6 +35,11 @@ export {
|
|
|
35
35
|
defineSmsOtpMatcher,
|
|
36
36
|
every,
|
|
37
37
|
} from "./define.js";
|
|
38
|
+
export type {
|
|
39
|
+
ProviderBuilder,
|
|
40
|
+
ProviderContextOf,
|
|
41
|
+
ProviderDeclaration,
|
|
42
|
+
} from "./define.js";
|
|
38
43
|
export {
|
|
39
44
|
AuthError,
|
|
40
45
|
HttpRedirectError,
|
|
@@ -107,7 +112,6 @@ export type {
|
|
|
107
112
|
NativeNetworkDynamicGrantOptions,
|
|
108
113
|
NativeNetworkEgressGrant,
|
|
109
114
|
NativeProviderConfig,
|
|
110
|
-
NativeProviderContext,
|
|
111
115
|
NativeProxyDrainHandler,
|
|
112
116
|
NativeProxyEgressInfo,
|
|
113
117
|
NativeProxyExpiringEvent,
|
|
@@ -142,6 +146,7 @@ export type {
|
|
|
142
146
|
ProviderChoiceIssueOptions,
|
|
143
147
|
ProviderChoiceParseOptions,
|
|
144
148
|
ProviderContext,
|
|
149
|
+
ProviderContextFor,
|
|
145
150
|
ProviderDefinition,
|
|
146
151
|
ProviderDeploymentOverrides,
|
|
147
152
|
ProviderFileRef,
|
package/src/runtime/browser.ts
CHANGED
|
@@ -804,8 +804,11 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
804
804
|
private readonly proxy: LaunchOptions["proxy"] = undefined,
|
|
805
805
|
) {}
|
|
806
806
|
|
|
807
|
-
async goto(
|
|
808
|
-
|
|
807
|
+
async goto(
|
|
808
|
+
url: string,
|
|
809
|
+
options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
|
|
810
|
+
): Promise<void> {
|
|
811
|
+
await this.page.goto(url, options);
|
|
809
812
|
}
|
|
810
813
|
|
|
811
814
|
async evaluate<T>(fn: string | (() => T)): Promise<T> {
|
|
@@ -1492,17 +1495,33 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1492
1495
|
return this.pageId;
|
|
1493
1496
|
}
|
|
1494
1497
|
|
|
1495
|
-
async goto(
|
|
1498
|
+
async goto(
|
|
1499
|
+
url: string,
|
|
1500
|
+
options?: { readonly timeout?: number; readonly waitUntil?: "load" | "domcontentloaded" },
|
|
1501
|
+
): Promise<void> {
|
|
1496
1502
|
await this.initialize();
|
|
1497
1503
|
const startedAt = Date.now();
|
|
1498
|
-
|
|
1499
|
-
const
|
|
1500
|
-
|
|
1504
|
+
const timeout = options?.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;
|
|
1505
|
+
const waitUntil = options?.waitUntil ?? "load";
|
|
1506
|
+
let expectedEventSeen = false;
|
|
1507
|
+
const eventName =
|
|
1508
|
+
waitUntil === "domcontentloaded" ? "Page.domContentEventFired" : "Page.loadEventFired";
|
|
1509
|
+
const unsubscribe = this.pageClient.on(eventName, () => {
|
|
1510
|
+
expectedEventSeen = true;
|
|
1501
1511
|
});
|
|
1502
1512
|
|
|
1503
1513
|
try {
|
|
1504
|
-
await this.pageClient.send("Page.navigate", { url })
|
|
1505
|
-
|
|
1514
|
+
const navigation = (await this.pageClient.send("Page.navigate", { url })) as {
|
|
1515
|
+
readonly errorText?: unknown;
|
|
1516
|
+
};
|
|
1517
|
+
if (typeof navigation.errorText === "string" && navigation.errorText.length > 0) {
|
|
1518
|
+
throw new Error(`Page.navigate failed: ${navigation.errorText} at ${url}`);
|
|
1519
|
+
}
|
|
1520
|
+
await this.waitForDocumentReady(
|
|
1521
|
+
startedAt + timeout,
|
|
1522
|
+
() => expectedEventSeen,
|
|
1523
|
+
waitUntil,
|
|
1524
|
+
);
|
|
1506
1525
|
} finally {
|
|
1507
1526
|
unsubscribe();
|
|
1508
1527
|
}
|
|
@@ -1866,12 +1885,16 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1866
1885
|
|
|
1867
1886
|
private async waitForDocumentReady(
|
|
1868
1887
|
deadline: number,
|
|
1869
|
-
|
|
1888
|
+
isExpectedEventSeen: () => boolean,
|
|
1889
|
+
waitUntil: "load" | "domcontentloaded",
|
|
1870
1890
|
): Promise<void> {
|
|
1871
1891
|
while (Date.now() < deadline) {
|
|
1872
1892
|
const readyState = await this.evaluate<string>("document.readyState");
|
|
1873
|
-
|
|
1874
|
-
|
|
1893
|
+
const documentReady =
|
|
1894
|
+
readyState === "complete" ||
|
|
1895
|
+
(waitUntil === "domcontentloaded" && readyState === "interactive");
|
|
1896
|
+
if (documentReady) {
|
|
1897
|
+
if (isExpectedEventSeen() || readyState === "complete") {
|
|
1875
1898
|
return;
|
|
1876
1899
|
}
|
|
1877
1900
|
}
|
|
@@ -8,10 +8,12 @@ export {
|
|
|
8
8
|
createResolverClient,
|
|
9
9
|
createResolverClientFromEnv,
|
|
10
10
|
createUnsupportedResolverClient,
|
|
11
|
+
DEFAULT_RESOLVER_VENDOR_PREFERENCE,
|
|
11
12
|
DEFAULT_RESOLVER_TIMEOUT_MS,
|
|
12
13
|
invalidateResolverSolution,
|
|
13
14
|
RESOLVER_ADAPTER_REGISTRY,
|
|
14
15
|
RESOLVER_INSTRUMENTATION_METADATA,
|
|
16
|
+
resolveProviderResolverVendors,
|
|
15
17
|
type ResolverAdapterFactory,
|
|
16
18
|
type ResolverInstrumentationMetadata,
|
|
17
19
|
type ResolverRuntimeOptions,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
const BROWSER_VENDOR_ID = "browser" as const;
|
|
20
20
|
const DEFAULT_COOKIE_POLL_INTERVAL_MS = 100;
|
|
21
|
+
const NAVIGATION_BLOCKED_ERROR_TEXT = "net::ERR_BLOCKED_BY_CLIENT";
|
|
21
22
|
const AWS_WAF_CHALLENGE_INFRASTRUCTURE_HOST_SUFFIX = ".awswaf.com";
|
|
22
23
|
const RESOLVER_DOCUMENT_CONTENT_SECURITY_POLICY = "connect-src http: https:; worker-src 'none'";
|
|
23
24
|
|
|
@@ -65,12 +66,41 @@ export interface BrowserResolverVendorAdapter extends ResolverVendorAdapter {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
class BrowserSolveTimeoutError extends Error {
|
|
68
|
-
constructor() {
|
|
69
|
-
super(
|
|
69
|
+
constructor(blockedRequests: readonly string[]) {
|
|
70
|
+
super(
|
|
71
|
+
`Browser resolver solve budget elapsed${formatBlockedRequests(blockedRequests)}`,
|
|
72
|
+
);
|
|
70
73
|
this.name = "BrowserSolveTimeoutError";
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
class BrowserNavigationBlockedError extends Error {
|
|
78
|
+
readonly code = "RESOLVER_BROWSER_NAVIGATION_BLOCKED";
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
readonly navigationUrl: string,
|
|
82
|
+
readonly blockedUrls: readonly string[],
|
|
83
|
+
options: ErrorOptions,
|
|
84
|
+
) {
|
|
85
|
+
super(
|
|
86
|
+
`Browser resolver navigation was blocked for ${navigationUrl}${formatBlockedRequests(blockedUrls)}`,
|
|
87
|
+
options,
|
|
88
|
+
);
|
|
89
|
+
this.name = "BrowserNavigationBlockedError";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatBlockedRequests(blockedRequests: readonly string[]): string {
|
|
94
|
+
if (blockedRequests.length === 0) return "";
|
|
95
|
+
const displayed = blockedRequests.slice(0, 5);
|
|
96
|
+
const remainder = blockedRequests.length - displayed.length;
|
|
97
|
+
return `; blocked ${blockedRequests.length} requests: [${displayed.join(", ")}]${remainder > 0 ? ` (+${remainder} more)` : ""}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isNavigationBlockedError(error: unknown): error is Error {
|
|
101
|
+
return error instanceof Error && error.message.includes(NAVIGATION_BLOCKED_ERROR_TEXT);
|
|
102
|
+
}
|
|
103
|
+
|
|
74
104
|
class BrowserCleanupTimeoutError extends Error {
|
|
75
105
|
constructor(timeoutMs: number) {
|
|
76
106
|
super(`Browser resolver cleanup exceeded ${timeoutMs}ms`);
|
|
@@ -223,6 +253,8 @@ async function solveInPage(
|
|
|
223
253
|
allowedHosts: readonly string[],
|
|
224
254
|
successCookieName: string,
|
|
225
255
|
pollIntervalMs: number,
|
|
256
|
+
gotoTimeoutMs: number,
|
|
257
|
+
blockedRequests: Set<string>,
|
|
226
258
|
signal: AbortSignal,
|
|
227
259
|
): Promise<Extract<ChallengeSolution, { readonly form: "cookies" }>> {
|
|
228
260
|
return await page.withResourcePolicy(
|
|
@@ -232,11 +264,13 @@ async function solveInPage(
|
|
|
232
264
|
routes: [
|
|
233
265
|
{
|
|
234
266
|
match: () => true,
|
|
235
|
-
handle: (request) =>
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
267
|
+
handle: (request) => {
|
|
268
|
+
if (isResolverBrowserRequestAllowed(request.url, challengeKind, allowedHosts)) {
|
|
269
|
+
return { action: "continue" };
|
|
270
|
+
}
|
|
271
|
+
blockedRequests.add(request.url);
|
|
272
|
+
return { action: "block" };
|
|
273
|
+
},
|
|
240
274
|
},
|
|
241
275
|
],
|
|
242
276
|
},
|
|
@@ -245,7 +279,19 @@ async function solveInPage(
|
|
|
245
279
|
() => page.userAgent(),
|
|
246
280
|
signal,
|
|
247
281
|
);
|
|
248
|
-
|
|
282
|
+
try {
|
|
283
|
+
await raceWithAbort(
|
|
284
|
+
() =>
|
|
285
|
+
page.goto(pageUrl, {
|
|
286
|
+
timeout: gotoTimeoutMs,
|
|
287
|
+
waitUntil: "domcontentloaded",
|
|
288
|
+
}),
|
|
289
|
+
signal,
|
|
290
|
+
);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (!isNavigationBlockedError(error)) throw error;
|
|
293
|
+
throw new BrowserNavigationBlockedError(pageUrl, [...blockedRequests], { cause: error });
|
|
294
|
+
}
|
|
249
295
|
|
|
250
296
|
while (true) {
|
|
251
297
|
const cookies = await raceWithAbort(() => page.cookies(), signal);
|
|
@@ -304,6 +350,7 @@ function poolErrorCode(error: Error): number | undefined {
|
|
|
304
350
|
|
|
305
351
|
function knownUnavailableReason(
|
|
306
352
|
error: unknown,
|
|
353
|
+
beforePageSolve = false,
|
|
307
354
|
): "allocation_exhausted" | "missing_credentials" | "transport_failure" | undefined {
|
|
308
355
|
// Source-grounded mappings:
|
|
309
356
|
// - apps/cdp-pool/src/index.ts: the JSON-RPC codes and messages below.
|
|
@@ -338,7 +385,10 @@ function knownUnavailableReason(
|
|
|
338
385
|
return "transport_failure";
|
|
339
386
|
}
|
|
340
387
|
|
|
341
|
-
|
|
388
|
+
// Browser creation, Playwright launch, and CDP connection all happen before the
|
|
389
|
+
// isolated-page handler is entered. Errors after that boundary belong to the
|
|
390
|
+
// challenge solve and must retain their existing classification.
|
|
391
|
+
return beforePageSolve ? "transport_failure" : undefined;
|
|
342
392
|
}
|
|
343
393
|
|
|
344
394
|
async function closeBrowserClient(
|
|
@@ -400,11 +450,12 @@ export function createBrowserResolverVendorAdapter(
|
|
|
400
450
|
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "not_implemented");
|
|
401
451
|
}
|
|
402
452
|
|
|
453
|
+
const blockedRequests = new Set<string>();
|
|
403
454
|
const solveController = new AbortController();
|
|
404
455
|
const onCallerAbort = () => solveController.abort(abortReason(callerSignal));
|
|
405
456
|
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
406
457
|
const timeout = setTimeout(
|
|
407
|
-
() => solveController.abort(new BrowserSolveTimeoutError()),
|
|
458
|
+
() => solveController.abort(new BrowserSolveTimeoutError([...blockedRequests])),
|
|
408
459
|
options.timeoutMs,
|
|
409
460
|
);
|
|
410
461
|
|
|
@@ -427,6 +478,8 @@ export function createBrowserResolverVendorAdapter(
|
|
|
427
478
|
options.allowedHosts,
|
|
428
479
|
SUCCESS_COOKIE_NAMES[challengeKind],
|
|
429
480
|
pollIntervalMs,
|
|
481
|
+
options.timeoutMs,
|
|
482
|
+
blockedRequests,
|
|
430
483
|
solveController.signal,
|
|
431
484
|
);
|
|
432
485
|
});
|
|
@@ -457,10 +510,15 @@ export function createBrowserResolverVendorAdapter(
|
|
|
457
510
|
cause: error,
|
|
458
511
|
});
|
|
459
512
|
}
|
|
513
|
+
if (error instanceof BrowserNavigationBlockedError) {
|
|
514
|
+
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, "transport_failure", {
|
|
515
|
+
cause: error,
|
|
516
|
+
});
|
|
517
|
+
}
|
|
460
518
|
if (error instanceof ResolverVendorUnavailableError) {
|
|
461
519
|
throw error;
|
|
462
520
|
}
|
|
463
|
-
const reason = knownUnavailableReason(error);
|
|
521
|
+
const reason = knownUnavailableReason(error, !handlerEntered);
|
|
464
522
|
if (reason) {
|
|
465
523
|
throw new ResolverVendorUnavailableError(BROWSER_VENDOR_ID, reason, { cause: error });
|
|
466
524
|
}
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ChallengeSolution,
|
|
3
3
|
ProviderChallenge,
|
|
4
4
|
ProviderChallengeKind,
|
|
5
|
+
ProviderResolverConfig,
|
|
5
6
|
ProviderResolverVendor,
|
|
6
7
|
} from "../../types.js";
|
|
7
8
|
import type { TraceRecorder } from "../trace.js";
|
|
@@ -42,6 +43,16 @@ export const RESOLVER_VENDOR_CAPABILITIES = {
|
|
|
42
43
|
],
|
|
43
44
|
} as const satisfies Readonly<Record<ProviderResolverVendor, readonly ProviderChallengeKind[]>>;
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* SDK-owned fallback policy for hosted resolver vendors. Capability support is
|
|
48
|
+
* applied separately, so each provider receives only vendors that support one
|
|
49
|
+
* or more of its declared challenge kinds.
|
|
50
|
+
*/
|
|
51
|
+
export const DEFAULT_RESOLVER_VENDOR_PREFERENCE = [
|
|
52
|
+
"capsolver",
|
|
53
|
+
"2captcha",
|
|
54
|
+
] as const satisfies readonly ProviderResolverVendor[];
|
|
55
|
+
|
|
45
56
|
export function resolverVendorSupports(
|
|
46
57
|
vendor: ProviderResolverVendor,
|
|
47
58
|
kind: ProviderChallengeKind,
|
|
@@ -49,6 +60,16 @@ export function resolverVendorSupports(
|
|
|
49
60
|
return (RESOLVER_VENDOR_CAPABILITIES[vendor] as readonly ProviderChallengeKind[]).includes(kind);
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
/** Resolves an explicit provider override or the SDK-owned default vendor chain. */
|
|
64
|
+
export function resolveProviderResolverVendors(
|
|
65
|
+
config: ProviderResolverConfig,
|
|
66
|
+
): readonly ProviderResolverVendor[] {
|
|
67
|
+
if (config.vendors !== undefined) return config.vendors;
|
|
68
|
+
return DEFAULT_RESOLVER_VENDOR_PREFERENCE.filter((vendor) =>
|
|
69
|
+
config.kinds.some((kind) => resolverVendorSupports(vendor, kind)),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
export interface ResolverIdentity {
|
|
53
74
|
readonly proxyUrl: string;
|
|
54
75
|
readonly userAgent: string;
|