@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/bin/apifuse-dev.ts +4 -0
  3. package/bin/apifuse-pack-types.ts +234 -38
  4. package/bin/apifuse-perf.ts +15 -12
  5. package/bin/apifuse-record.ts +4 -0
  6. package/dist/config/loader.d.ts +8 -19
  7. package/dist/config/loader.js +28 -86
  8. package/dist/contract-types.d.ts +1 -0
  9. package/dist/contract.js +2 -0
  10. package/dist/define.d.ts +5 -1
  11. package/dist/define.js +79 -6
  12. package/dist/error-resolution.js +5 -0
  13. package/dist/index.d.ts +4 -2
  14. package/dist/index.js +2 -0
  15. package/dist/provider.d.ts +1 -1
  16. package/dist/runtime/auth-flow.d.ts +2 -1
  17. package/dist/runtime/auth-flow.js +4 -0
  18. package/dist/runtime/browser.js +78 -9
  19. package/dist/runtime/http.js +0 -1
  20. package/dist/runtime/instrumentation.js +26 -1
  21. package/dist/runtime/ocr.d.ts +29 -0
  22. package/dist/runtime/ocr.js +440 -0
  23. package/dist/runtime/resolver-vendors/bindings.d.ts +8 -0
  24. package/dist/runtime/resolver-vendors/bindings.js +15 -0
  25. package/dist/runtime/resolver-vendors/browser.d.ts +24 -0
  26. package/dist/runtime/resolver-vendors/browser.js +287 -0
  27. package/dist/runtime/resolver-vendors/types.d.ts +42 -0
  28. package/dist/runtime/resolver-vendors/types.js +57 -0
  29. package/dist/runtime/resolver.d.ts +39 -0
  30. package/dist/runtime/resolver.js +414 -0
  31. package/dist/runtime/state.d.ts +3 -0
  32. package/dist/runtime/state.js +245 -141
  33. package/dist/runtime/stealth.js +3 -6
  34. package/dist/runtime/stt.js +1 -12
  35. package/dist/runtime/timeout.d.ts +5 -0
  36. package/dist/runtime/timeout.js +12 -0
  37. package/dist/server/serve.d.ts +6 -1
  38. package/dist/server/serve.js +39 -8
  39. package/dist/testing/run.js +10 -0
  40. package/dist/types.d.ts +163 -4
  41. package/package.json +1 -1
  42. package/src/config/loader.ts +35 -111
  43. package/src/contract-types.ts +1 -0
  44. package/src/contract.ts +2 -0
  45. package/src/define.ts +121 -7
  46. package/src/error-resolution.ts +5 -0
  47. package/src/index.ts +45 -1
  48. package/src/provider.ts +1 -0
  49. package/src/runtime/auth-flow.ts +6 -0
  50. package/src/runtime/browser.ts +139 -19
  51. package/src/runtime/http.ts +0 -1
  52. package/src/runtime/instrumentation.ts +36 -2
  53. package/src/runtime/ocr.ts +523 -0
  54. package/src/runtime/resolver-vendors/bindings.ts +31 -0
  55. package/src/runtime/resolver-vendors/browser.ts +420 -0
  56. package/src/runtime/resolver-vendors/types.ts +113 -0
  57. package/src/runtime/resolver.ts +668 -0
  58. package/src/runtime/state.ts +323 -166
  59. package/src/runtime/stealth.ts +3 -6
  60. package/src/runtime/stt.ts +1 -19
  61. package/src/runtime/timeout.ts +18 -0
  62. package/src/server/serve.ts +80 -5
  63. package/src/testing/run.ts +15 -0
  64. package/src/types.ts +188 -4
@@ -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 JsonRpcId = number;
46
+ type WebSocketCommandId = number;
46
47
 
47
- type JsonRpcError = {
48
+ type WebSocketCommandError = {
48
49
  code?: number;
49
50
  message?: string;
50
51
  };
51
52
 
52
- type JsonRpcMessage = {
53
- error?: JsonRpcError;
54
- id?: JsonRpcId;
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 JsonRpcWebSocketClient {
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
- JsonRpcId,
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 JsonRpcMessage;
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
- pending.reject(new Error(payload.error.message ?? "JSON-RPC command failed"));
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: JsonRpcWebSocketClient,
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 JsonRpcWebSocketClient(acquireResult.wsEndpoint);
1458
+ const pageClient = new CdpWebSocketClient(acquireResult.wsEndpoint);
1339
1459
  const page = new CdpPoolBrowserPage(
1340
1460
  acquireResult.pageId,
1341
1461
  acquireResult.browserContextId,
@@ -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)) {