@apifuse/provider-sdk 2.2.0-beta.22 → 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 +8 -0
- package/bin/apifuse-dev.ts +4 -0
- package/bin/apifuse-pack-types.ts +234 -38
- package/bin/apifuse-perf.ts +15 -12
- package/bin/apifuse-record.ts +4 -0
- package/dist/config/loader.d.ts +8 -19
- package/dist/config/loader.js +28 -86
- package/dist/contract-types.d.ts +1 -0
- package/dist/contract.js +2 -0
- package/dist/define.d.ts +5 -1
- package/dist/define.js +79 -6
- package/dist/error-resolution.js +5 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/provider.d.ts +1 -1
- package/dist/runtime/auth-flow.d.ts +2 -1
- package/dist/runtime/auth-flow.js +4 -0
- package/dist/runtime/browser.js +78 -9
- package/dist/runtime/http.js +0 -1
- package/dist/runtime/instrumentation.js +26 -1
- package/dist/runtime/ocr.d.ts +29 -0
- package/dist/runtime/ocr.js +440 -0
- 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/runtime/stt.js +1 -12
- package/dist/runtime/timeout.d.ts +5 -0
- package/dist/runtime/timeout.js +12 -0
- package/dist/server/serve.d.ts +6 -1
- package/dist/server/serve.js +39 -8
- package/dist/testing/run.js +10 -0
- package/dist/types.d.ts +163 -4
- package/package.json +1 -1
- package/src/config/loader.ts +35 -111
- package/src/contract-types.ts +1 -0
- package/src/contract.ts +2 -0
- package/src/define.ts +121 -7
- package/src/error-resolution.ts +5 -0
- package/src/index.ts +45 -1
- package/src/provider.ts +1 -0
- package/src/runtime/auth-flow.ts +6 -0
- package/src/runtime/browser.ts +139 -19
- package/src/runtime/http.ts +0 -1
- package/src/runtime/instrumentation.ts +36 -2
- package/src/runtime/ocr.ts +523 -0
- 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/runtime/stt.ts +1 -19
- package/src/runtime/timeout.ts +18 -0
- package/src/server/serve.ts +80 -5
- package/src/testing/run.ts +15 -0
- package/src/types.ts +188 -4
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,
|
|
@@ -42,19 +43,35 @@ type PoolReleaseRequest = {
|
|
|
42
43
|
pageId: string;
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
type
|
|
46
|
+
type WebSocketCommandId = number;
|
|
46
47
|
|
|
47
|
-
type
|
|
48
|
+
type WebSocketCommandError = {
|
|
48
49
|
code?: number;
|
|
49
50
|
message?: string;
|
|
50
51
|
};
|
|
51
52
|
|
|
52
|
-
type
|
|
53
|
-
error?:
|
|
54
|
-
id?:
|
|
53
|
+
type WebSocketCommandMessage = {
|
|
54
|
+
error?: WebSocketCommandError;
|
|
55
|
+
id?: WebSocketCommandId;
|
|
56
|
+
jsonrpc?: "2.0";
|
|
55
57
|
method?: string;
|
|
56
58
|
params?: Record<string, unknown>;
|
|
57
59
|
result?: Record<string, unknown>;
|
|
60
|
+
sessionId?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
type CdpCommandFrame = {
|
|
64
|
+
id: WebSocketCommandId;
|
|
65
|
+
method: string;
|
|
66
|
+
params: Record<string, unknown>;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type JsonRpcCommandFrame = {
|
|
71
|
+
id: WebSocketCommandId;
|
|
72
|
+
jsonrpc: "2.0";
|
|
73
|
+
method: string;
|
|
74
|
+
params: Record<string, unknown>;
|
|
58
75
|
};
|
|
59
76
|
|
|
60
77
|
type CdpFrameTreeNode = {
|
|
@@ -529,6 +546,10 @@ class PlaywrightBrowserPage implements BrowserPageContract {
|
|
|
529
546
|
await this.page.close();
|
|
530
547
|
}
|
|
531
548
|
|
|
549
|
+
async cookies(): Promise<readonly BrowserCookie[]> {
|
|
550
|
+
return (await this.page.context().cookies()).map(toBrowserCookie);
|
|
551
|
+
}
|
|
552
|
+
|
|
532
553
|
async withResourcePolicy<T>(policy: BrowserResourcePolicy, run: () => Promise<T>): Promise<T> {
|
|
533
554
|
const allowedMethods = new Set(policy.allowedMethods ?? DEFAULT_RESOURCE_METHODS);
|
|
534
555
|
const handler = async (route: Route): Promise<void> => {
|
|
@@ -641,12 +662,12 @@ function normalizeWebSocketEndpoint(endpoint: string): string {
|
|
|
641
662
|
throw new Error(`Unsupported WebSocket endpoint protocol: ${url.protocol}`);
|
|
642
663
|
}
|
|
643
664
|
|
|
644
|
-
class
|
|
665
|
+
abstract class WebSocketCommandClient {
|
|
645
666
|
private nextId = 1;
|
|
646
667
|
private readonly endpoint: string;
|
|
647
668
|
private readonly listeners = new Map<string, Set<(params: unknown) => void>>();
|
|
648
669
|
private readonly pending = new Map<
|
|
649
|
-
|
|
670
|
+
WebSocketCommandId,
|
|
650
671
|
{
|
|
651
672
|
reject: (reason?: unknown) => void;
|
|
652
673
|
resolve: (value: Record<string, unknown>) => void;
|
|
@@ -682,14 +703,7 @@ class JsonRpcWebSocketClient {
|
|
|
682
703
|
return await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
683
704
|
this.pending.set(id, { resolve, reject });
|
|
684
705
|
|
|
685
|
-
socket.send(
|
|
686
|
-
JSON.stringify({
|
|
687
|
-
id,
|
|
688
|
-
jsonrpc: "2.0",
|
|
689
|
-
method,
|
|
690
|
-
params,
|
|
691
|
-
}),
|
|
692
|
-
);
|
|
706
|
+
socket.send(JSON.stringify(this.createCommandFrame(id, method, params)));
|
|
693
707
|
});
|
|
694
708
|
}
|
|
695
709
|
|
|
@@ -705,6 +719,12 @@ class JsonRpcWebSocketClient {
|
|
|
705
719
|
this.socketPromise = undefined;
|
|
706
720
|
}
|
|
707
721
|
|
|
722
|
+
protected abstract createCommandFrame(
|
|
723
|
+
id: WebSocketCommandId,
|
|
724
|
+
method: string,
|
|
725
|
+
params: Record<string, unknown>,
|
|
726
|
+
): CdpCommandFrame | JsonRpcCommandFrame;
|
|
727
|
+
|
|
708
728
|
private async getSocket(): Promise<WebSocket> {
|
|
709
729
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
710
730
|
return this.socket;
|
|
@@ -727,7 +747,7 @@ class JsonRpcWebSocketClient {
|
|
|
727
747
|
typeof event.data === "string"
|
|
728
748
|
? event.data
|
|
729
749
|
: Buffer.from(event.data as ArrayBufferLike).toString("utf8");
|
|
730
|
-
const payload = JSON.parse(rawData) as
|
|
750
|
+
const payload = JSON.parse(rawData) as WebSocketCommandMessage;
|
|
731
751
|
|
|
732
752
|
if (typeof payload.id === "number") {
|
|
733
753
|
const pending = this.pending.get(payload.id);
|
|
@@ -738,7 +758,11 @@ class JsonRpcWebSocketClient {
|
|
|
738
758
|
this.pending.delete(payload.id);
|
|
739
759
|
|
|
740
760
|
if (payload.error) {
|
|
741
|
-
|
|
761
|
+
const error = new Error(payload.error.message ?? "JSON-RPC command failed");
|
|
762
|
+
if (typeof payload.error.code === "number") {
|
|
763
|
+
Object.assign(error, { code: payload.error.code });
|
|
764
|
+
}
|
|
765
|
+
pending.reject(error);
|
|
742
766
|
return;
|
|
743
767
|
}
|
|
744
768
|
|
|
@@ -774,6 +798,38 @@ class JsonRpcWebSocketClient {
|
|
|
774
798
|
}
|
|
775
799
|
}
|
|
776
800
|
|
|
801
|
+
class CdpWebSocketClient extends WebSocketCommandClient {
|
|
802
|
+
constructor(
|
|
803
|
+
endpoint: string,
|
|
804
|
+
private readonly sessionId?: string,
|
|
805
|
+
) {
|
|
806
|
+
super(endpoint);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
protected createCommandFrame(
|
|
810
|
+
id: WebSocketCommandId,
|
|
811
|
+
method: string,
|
|
812
|
+
params: Record<string, unknown>,
|
|
813
|
+
): CdpCommandFrame {
|
|
814
|
+
return {
|
|
815
|
+
id,
|
|
816
|
+
method,
|
|
817
|
+
params,
|
|
818
|
+
...(this.sessionId === undefined ? {} : { sessionId: this.sessionId }),
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
class JsonRpcWebSocketClient extends WebSocketCommandClient {
|
|
824
|
+
protected createCommandFrame(
|
|
825
|
+
id: WebSocketCommandId,
|
|
826
|
+
method: string,
|
|
827
|
+
params: Record<string, unknown>,
|
|
828
|
+
): JsonRpcCommandFrame {
|
|
829
|
+
return { id, jsonrpc: "2.0", method, params };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
777
833
|
function flattenCdpFrameTree(
|
|
778
834
|
node: CdpFrameTreeNode | undefined,
|
|
779
835
|
out: CdpFrameTreeNode["frame"][] = [],
|
|
@@ -794,6 +850,64 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
794
850
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
795
851
|
}
|
|
796
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
|
+
|
|
797
911
|
function parsePoolAcquireResponse(value: unknown): PoolAcquireResponse {
|
|
798
912
|
if (
|
|
799
913
|
!isRecord(value) ||
|
|
@@ -974,7 +1088,7 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
974
1088
|
constructor(
|
|
975
1089
|
readonly pageId: string,
|
|
976
1090
|
private readonly browserContextId: string | undefined,
|
|
977
|
-
private readonly pageClient:
|
|
1091
|
+
private readonly pageClient: CdpWebSocketClient,
|
|
978
1092
|
private readonly release: (request: PoolReleaseRequest) => Promise<void>,
|
|
979
1093
|
) {}
|
|
980
1094
|
|
|
@@ -1149,6 +1263,12 @@ class CdpPoolBrowserPage implements BrowserPageContract {
|
|
|
1149
1263
|
return Buffer.from(String(result.data ?? ""), "base64");
|
|
1150
1264
|
}
|
|
1151
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
|
+
|
|
1152
1272
|
async close(): Promise<void> {
|
|
1153
1273
|
if (this.closed) {
|
|
1154
1274
|
return;
|
|
@@ -1335,7 +1455,7 @@ class CdpPoolBrowserClient implements SupportedBrowserClient {
|
|
|
1335
1455
|
...(options?.isolatedContext ? { isolationMode: "browserContext" } : {}),
|
|
1336
1456
|
}),
|
|
1337
1457
|
);
|
|
1338
|
-
const pageClient = new
|
|
1458
|
+
const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
|
|
1339
1459
|
const page = new CdpPoolBrowserPage(
|
|
1340
1460
|
acquireResult.pageId,
|
|
1341
1461
|
acquireResult.browserContextId,
|
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)) {
|