@apifuse/provider-sdk 2.2.0-beta.27 → 2.2.0-beta.28

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 (81) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/bin/apifuse-dev.ts +2 -2
  3. package/bin/apifuse-pack-smoke.ts +1 -1
  4. package/bin/apifuse-pack-types.ts +2 -1
  5. package/bin/apifuse-perf.ts +2 -4
  6. package/bin/apifuse-record.ts +1 -1
  7. package/dist/auth-turn/index.d.ts +1 -1
  8. package/dist/auth-turn/index.js +1 -1
  9. package/dist/ceremonies/index.js +52 -11
  10. package/dist/config/loader.d.ts +1 -1
  11. package/dist/config/loader.js +4 -2
  12. package/dist/index.d.ts +7 -6
  13. package/dist/index.js +4 -6
  14. package/dist/provider.d.ts +2 -1
  15. package/dist/provider.js +1 -1
  16. package/dist/runtime/auth-flow.js +1 -1
  17. package/dist/runtime/http.d.ts +1 -0
  18. package/dist/runtime/http.js +135 -12
  19. package/dist/runtime/instrumentation.js +1 -1
  20. package/dist/runtime/native-network-errors.d.ts +33 -0
  21. package/dist/runtime/native-network-errors.js +69 -0
  22. package/dist/runtime/native-network.d.ts +2 -33
  23. package/dist/runtime/native-network.js +2 -68
  24. package/dist/runtime/redis.d.ts +1 -1
  25. package/dist/runtime/redis.js +4 -2
  26. package/dist/runtime/resolver-config.d.ts +6 -0
  27. package/dist/runtime/resolver-config.js +6 -0
  28. package/dist/runtime/resolver-shared.d.ts +3 -0
  29. package/dist/runtime/resolver-shared.js +12 -0
  30. package/dist/runtime/resolver-vendors/twocaptcha.d.ts +2 -1
  31. package/dist/runtime/resolver-vendors/twocaptcha.js +80 -43
  32. package/dist/runtime/resolver-vendors/types.d.ts +1 -1
  33. package/dist/runtime/resolver.d.ts +6 -10
  34. package/dist/runtime/resolver.js +19 -18
  35. package/dist/runtime/state.js +5 -115
  36. package/dist/runtime/stealth-cookies.d.ts +20 -0
  37. package/dist/runtime/stealth-cookies.js +111 -0
  38. package/dist/runtime/stealth.js +7 -130
  39. package/dist/serve.d.ts +1 -1
  40. package/dist/serve.js +1 -1
  41. package/dist/server/index.d.ts +1 -1
  42. package/dist/server/index.js +1 -1
  43. package/dist/server/self-test.d.ts +13 -0
  44. package/dist/server/self-test.js +124 -46
  45. package/dist/server/serve-implementation.d.ts +199 -0
  46. package/dist/server/serve-implementation.js +2054 -0
  47. package/dist/server/serve.d.ts +1 -187
  48. package/dist/server/serve.js +1 -1827
  49. package/dist/stateful/errors.d.ts +5 -0
  50. package/dist/stateful/errors.js +10 -0
  51. package/dist/stateful/stateful-provider-session-routing.d.ts +1 -5
  52. package/dist/stateful/stateful-provider-session-routing.js +2 -10
  53. package/dist/stream.js +7 -1
  54. package/package.json +27 -2
  55. package/src/auth-turn/index.ts +1 -1
  56. package/src/ceremonies/index.ts +68 -18
  57. package/src/config/loader.ts +5 -2
  58. package/src/index.ts +18 -23
  59. package/src/provider.ts +12 -14
  60. package/src/runtime/auth-flow.ts +1 -1
  61. package/src/runtime/http.ts +155 -11
  62. package/src/runtime/instrumentation.ts +1 -1
  63. package/src/runtime/native-network-errors.ts +99 -0
  64. package/src/runtime/native-network.ts +16 -97
  65. package/src/runtime/redis.ts +7 -2
  66. package/src/runtime/resolver-config.ts +6 -0
  67. package/src/runtime/resolver-shared.ts +17 -0
  68. package/src/runtime/resolver-vendors/twocaptcha.ts +100 -49
  69. package/src/runtime/resolver-vendors/types.ts +1 -0
  70. package/src/runtime/resolver.ts +47 -22
  71. package/src/runtime/state.ts +5 -144
  72. package/src/runtime/stealth-cookies.ts +132 -0
  73. package/src/runtime/stealth.ts +14 -157
  74. package/src/serve.ts +6 -1
  75. package/src/server/index.ts +1 -0
  76. package/src/server/self-test.ts +184 -59
  77. package/src/server/serve-implementation.ts +3024 -0
  78. package/src/server/serve.ts +1 -2661
  79. package/src/stateful/errors.ts +12 -0
  80. package/src/stateful/stateful-provider-session-routing.ts +2 -11
  81. package/src/stream.ts +8 -1
@@ -0,0 +1,132 @@
1
+ import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
2
+
3
+ import { SDKError, StealthCookieStoreVersionError } from "../errors.js";
4
+ import type {
5
+ CookieJar,
6
+ StealthCookieStore,
7
+ StealthCookieStoreV1,
8
+ StealthSessionCookies,
9
+ } from "../types.js";
10
+
11
+ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
12
+ return typeof value === "object" && value !== null;
13
+ }
14
+
15
+ const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
16
+
17
+ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
18
+ private cookies: ToughCookieJar;
19
+ private readonly defaultUrl: string;
20
+
21
+ constructor(cookieStrings: readonly string[], defaultUrl = LEGACY_COOKIE_ORIGIN) {
22
+ this.cookies = new ToughCookieJar(undefined, {
23
+ allowSecureOnLocal: false,
24
+ rejectPublicSuffixes: true,
25
+ });
26
+ this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
27
+ this.setFromCookieStrings(cookieStrings);
28
+ }
29
+
30
+ setFromCookieStrings(cookieStrings: readonly string[], url = this.defaultUrl): void {
31
+ const cookieUrl = this.normalizeUrl(url);
32
+ if (!cookieUrl) return;
33
+ for (const cookieString of cookieStrings) {
34
+ this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
35
+ }
36
+ }
37
+
38
+ get(name: string, url?: string): string | undefined {
39
+ return this.getAll(url)[name];
40
+ }
41
+
42
+ getAll(url?: string): Record<string, string> {
43
+ return Object.fromEntries(
44
+ this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => [cookie.key, cookie.value]),
45
+ );
46
+ }
47
+
48
+ has(name: string, url?: string): boolean {
49
+ return Object.hasOwn(this.getAll(url), name);
50
+ }
51
+
52
+ toString(url?: string): string {
53
+ return this.getUniqueCookies(url ?? this.defaultUrl)
54
+ .map((cookie) => cookie.cookieString())
55
+ .join("; ");
56
+ }
57
+
58
+ toHeader(url?: string): string {
59
+ return this.toString(url);
60
+ }
61
+
62
+ snapshot(): Record<string, string> {
63
+ const entries: [string, string][] = [];
64
+ for (const cookie of this.serialize().jar.cookies) {
65
+ if (typeof cookie.key === "string" && typeof cookie.value === "string" && cookie.key) {
66
+ entries.push([cookie.key, cookie.value]);
67
+ }
68
+ }
69
+ return Object.fromEntries(entries);
70
+ }
71
+
72
+ restore(cookies: Record<string, string>): void {
73
+ this.clear();
74
+ for (const [name, value] of Object.entries(cookies)) {
75
+ if (!name) continue;
76
+ this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
77
+ ignoreError: true,
78
+ });
79
+ }
80
+ }
81
+
82
+ serialize(): StealthCookieStoreV1 {
83
+ const jar = this.cookies.serializeSync();
84
+ if (!jar) {
85
+ throw new SDKError("Stealth cookie store could not be serialized", {
86
+ code: "stealth_cookie_store_serialize_failed",
87
+ });
88
+ }
89
+ return { version: 1, jar };
90
+ }
91
+
92
+ deserialize(state: StealthCookieStore): void {
93
+ const version = isRecord(state) ? state.version : undefined;
94
+ if (version !== 1) {
95
+ throw new StealthCookieStoreVersionError(version);
96
+ }
97
+ const restored = ToughCookieJar.deserializeSync(state.jar);
98
+ Reflect.set(restored, "allowSecureOnLocal", false);
99
+ this.cookies = restored;
100
+ }
101
+
102
+ clear(): void {
103
+ this.cookies.removeAllCookiesSync();
104
+ }
105
+
106
+ find(predicate: (cookie: string) => boolean, url?: string): string | undefined {
107
+ for (const cookie of this.getUniqueCookies(url ?? this.defaultUrl)) {
108
+ const cookieString = cookie.cookieString();
109
+ if (predicate(cookieString)) return cookieString;
110
+ }
111
+ return undefined;
112
+ }
113
+
114
+ private normalizeUrl(url: string): string | undefined {
115
+ try {
116
+ return new URL(url).toString();
117
+ } catch {
118
+ return undefined;
119
+ }
120
+ }
121
+
122
+ private getUniqueCookies(url: string): Cookie[] {
123
+ const cookieUrl = this.normalizeUrl(url);
124
+ if (!cookieUrl) return [];
125
+ const names = new Set<string>();
126
+ return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
127
+ if (names.has(cookie.key)) return false;
128
+ names.add(cookie.key);
129
+ return true;
130
+ });
131
+ }
132
+ }
@@ -1,5 +1,4 @@
1
1
  import { createHash } from "node:crypto";
2
- import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
3
2
  import type {
4
3
  BrowserProfile,
5
4
  EmulationOS,
@@ -18,19 +17,17 @@ import {
18
17
  resolveProxyConfigAsync,
19
18
  vendorFromResolvedSource,
20
19
  } from "../config/loader.js";
21
- import { SDKError, StealthCookieStoreVersionError, TransportError } from "../errors.js";
20
+ import { SDKError, TransportError } from "../errors.js";
22
21
  import { getStealthProfile } from "../stealth/profiles.js";
23
22
  import type {
24
- CookieJar,
25
23
  HttpMethod,
26
24
  StealthClient,
27
- StealthCookieStore,
28
- StealthCookieStoreV1,
29
25
  StealthFetchOptions,
30
26
  StealthRedirectHop,
31
27
  StealthResponse,
32
28
  StealthSession,
33
29
  } from "../types.js";
30
+ import { StealthCookieJar } from "./stealth-cookies.js";
34
31
  import {
35
32
  createProxyAuthIpDeniedError,
36
33
  createProxyEdgeAuthRejectedError,
@@ -90,6 +87,10 @@ const REDIRECT_BODY_HEADERS = new Set([
90
87
  "content-type",
91
88
  ]);
92
89
 
90
+ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
91
+ return typeof value === "object" && value !== null;
92
+ }
93
+
93
94
  function sensitiveQueryParamNames(url: string): string[] {
94
95
  const queryStart = url.indexOf("?");
95
96
  if (queryStart === -1) return [];
@@ -156,151 +157,6 @@ type WreqSessionCacheEntry = {
156
157
  tail: Promise<void>;
157
158
  };
158
159
 
159
- function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
160
- return typeof value === "object" && value !== null;
161
- }
162
-
163
- const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
164
-
165
- class CookieJarImpl implements CookieJar {
166
- private cookies: ToughCookieJar;
167
- private readonly defaultUrl: string;
168
-
169
- constructor(cookieStrings: readonly string[], defaultUrl = LEGACY_COOKIE_ORIGIN) {
170
- this.cookies = new ToughCookieJar(undefined, {
171
- allowSecureOnLocal: false,
172
- rejectPublicSuffixes: true,
173
- });
174
- this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
175
- this.setFromCookieStrings(cookieStrings);
176
- }
177
-
178
- /**
179
- * URL-less legacy operations are scoped to this jar's default URL. Session
180
- * jars use the client's base URL and response jars use the response URL. A
181
- * flat restore has no attributes to recover, so it creates host-only Path=/
182
- * cookies for that default URL instead of making them visible to every host.
183
- */
184
- setFromCookieStrings(cookieStrings: readonly string[], url = this.defaultUrl): void {
185
- const cookieUrl = this.normalizeUrl(url);
186
- if (!cookieUrl) return;
187
-
188
- for (const cookieString of cookieStrings) {
189
- this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
190
- }
191
- }
192
-
193
- get(name: string, url?: string): string | undefined {
194
- return this.getAll(url)[name];
195
- }
196
-
197
- getAll(url?: string): Record<string, string> {
198
- return Object.fromEntries(
199
- this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => [cookie.key, cookie.value]),
200
- );
201
- }
202
-
203
- has(name: string, url?: string): boolean {
204
- return Object.hasOwn(this.getAll(url), name);
205
- }
206
-
207
- toString(url?: string): string {
208
- return this.getUniqueCookies(url ?? this.defaultUrl)
209
- .map((cookie) => cookie.cookieString())
210
- .join("; ");
211
- }
212
-
213
- toHeader(url?: string): string {
214
- return this.toString(url);
215
- }
216
-
217
- snapshot(): Record<string, string> {
218
- // This compatibility view deliberately enumerates the serialized store,
219
- // not getAll(defaultUrl): persistence must include sibling hosts and paths.
220
- // Duplicate names still collapse because a flat map cannot represent them.
221
- const entries: [string, string][] = [];
222
- for (const cookie of this.serialize().jar.cookies) {
223
- if (typeof cookie.key === "string" && typeof cookie.value === "string" && cookie.key) {
224
- entries.push([cookie.key, cookie.value]);
225
- }
226
- }
227
- return Object.fromEntries(entries);
228
- }
229
-
230
- restore(cookies: Record<string, string>): void {
231
- this.clear();
232
- for (const [name, value] of Object.entries(cookies)) {
233
- if (!name) continue;
234
- this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
235
- ignoreError: true,
236
- });
237
- }
238
- }
239
-
240
- serialize(): StealthCookieStoreV1 {
241
- const jar = this.cookies.serializeSync();
242
- if (!jar) {
243
- throw new SDKError("Stealth cookie store could not be serialized", {
244
- code: "stealth_cookie_store_serialize_failed",
245
- });
246
- }
247
- return { version: 1, jar };
248
- }
249
-
250
- deserialize(state: StealthCookieStore): void {
251
- const version = isRecord(state) ? state.version : undefined;
252
- if (version !== 1) {
253
- throw new StealthCookieStoreVersionError(version);
254
- }
255
-
256
- // Deserialize into a new jar first so invalid state cannot partially clear
257
- // or replace a live session. tough-cookie restores the cookie attributes and
258
- // matching semantics represented in its own serialized format.
259
- const restored = ToughCookieJar.deserializeSync(state.jar);
260
- // tough-cookie 6 does not include this option in serializeSync(). Preserve
261
- // the SDK's stricter setting across restoration.
262
- Reflect.set(restored, "allowSecureOnLocal", false);
263
- this.cookies = restored;
264
- }
265
-
266
- clear(): void {
267
- this.cookies.removeAllCookiesSync();
268
- }
269
-
270
- find(predicate: (cookie: string) => boolean, url?: string): string | undefined {
271
- for (const cookie of this.getUniqueCookies(url ?? this.defaultUrl)) {
272
- const cookieString = cookie.cookieString();
273
- if (predicate(cookieString)) {
274
- return cookieString;
275
- }
276
- }
277
-
278
- return undefined;
279
- }
280
-
281
- private normalizeUrl(url: string): string | undefined {
282
- try {
283
- return new URL(url).toString();
284
- } catch {
285
- return undefined;
286
- }
287
- }
288
-
289
- private getUniqueCookies(url: string): Cookie[] {
290
- const cookieUrl = this.normalizeUrl(url);
291
- if (!cookieUrl) return [];
292
-
293
- // tough-cookie returns longer (more-specific) paths first. Keeping the
294
- // first cookie for each name prevents ambiguous duplicate-name headers.
295
- const names = new Set<string>();
296
- return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
297
- if (names.has(cookie.key)) return false;
298
- names.add(cookie.key);
299
- return true;
300
- });
301
- }
302
- }
303
-
304
160
  type WreqModule = typeof import("wreq-js");
305
161
 
306
162
  let wreqModulePromise: Promise<WreqModule> | undefined;
@@ -325,9 +181,10 @@ function parseProfileIdentifier(identifier: string): {
325
181
  family: string;
326
182
  version: number[];
327
183
  } | null {
328
- const match = /^(safari_ios|safari_ipad|firefox_android|firefox_private|chrome|edge|firefox|opera|safari|okhttp)_(\d+(?:[._]\d+)*)$/.exec(
329
- identifier.toLowerCase(),
330
- );
184
+ const match =
185
+ /^(safari_ios|safari_ipad|firefox_android|firefox_private|chrome|edge|firefox|opera|safari|okhttp)_(\d+(?:[._]\d+)*)$/.exec(
186
+ identifier.toLowerCase(),
187
+ );
331
188
  if (!match?.[1] || !match[2]) return null;
332
189
  return {
333
190
  family: match[1],
@@ -499,7 +356,7 @@ export async function normalizeResponse(
499
356
  maxBodyBytes?: number,
500
357
  ): Promise<StealthResponse> {
501
358
  const headers = Object.fromEntries(response.headers.entries());
502
- const cookies = new CookieJarImpl(
359
+ const cookies = new StealthCookieJar(
503
360
  setCookieHeadersFromResponse(response.headers),
504
361
  response.url ?? requestUrl,
505
362
  );
@@ -826,7 +683,7 @@ function discardStealthRedirectBody(response: StealthTransportResponse): void {
826
683
 
827
684
  async function fetchStealthRedirectChain(
828
685
  transport: WreqSession,
829
- cookieJar: CookieJarImpl,
686
+ cookieJar: StealthCookieJar,
830
687
  requestUrl: string,
831
688
  method: StealthMethod,
832
689
  options: StealthFetchOptions,
@@ -915,7 +772,7 @@ function createSessionFetcher(
915
772
  let closed = false;
916
773
  let hasWarnedMissingProxy = false;
917
774
  const warn = clientOptions.warn ?? console.warn;
918
- const cookieJar = new CookieJarImpl([], baseUrl);
775
+ const cookieJar = new StealthCookieJar([], baseUrl);
919
776
 
920
777
  async function getClientEntry(
921
778
  profileName: string,
@@ -962,7 +819,7 @@ function createSessionFetcher(
962
819
  return await operation(await entry.session);
963
820
  } finally {
964
821
  release();
965
- }
822
+ }
966
823
  }
967
824
 
968
825
  async function resolveRequestProxy(
package/src/serve.ts CHANGED
@@ -1 +1,6 @@
1
- export { createServerApp, type ServeOptions, serve } from "./server/serve.js";
1
+ export {
2
+ createServerApp,
3
+ createServerAppAsync,
4
+ type ServeOptions,
5
+ serve,
6
+ } from "./server/serve.js";
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  createServerApp,
3
+ createServerAppAsync,
3
4
  ERROR_OBSERVABILITY_HEADER,
4
5
  type ErrorObservabilityDetails,
5
6
  type ProviderServerCloseOptions,