@apifuse/provider-sdk 2.2.0-beta.10 → 2.2.0-beta.12
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 +37 -0
- package/CHANGELOG.md +8 -0
- package/README.md +18 -0
- package/bin/apifuse-pack-smoke.ts +14 -0
- package/bin/apifuse-pack-types.ts +11 -1
- package/dist/config/loader.d.ts +9 -1
- package/dist/config/loader.js +9 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +15 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/runtime/stealth.js +113 -47
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/serve.d.ts +98 -2
- package/dist/server/serve.js +485 -23
- package/dist/stateful/errors.d.ts +14 -0
- package/dist/stateful/errors.js +14 -0
- package/dist/stateful/http-provider-event-emitter.d.ts +40 -0
- package/dist/stateful/http-provider-event-emitter.js +237 -0
- package/dist/stateful/http-session-owner-registry.d.ts +44 -0
- package/dist/stateful/http-session-owner-registry.js +210 -0
- package/dist/stateful/index.d.ts +18 -0
- package/dist/stateful/index.js +18 -0
- package/dist/stateful/provider-event-delivery-failures.d.ts +32 -0
- package/dist/stateful/provider-event-delivery-failures.js +43 -0
- package/dist/stateful/provider-event-pipeline-metrics.d.ts +46 -0
- package/dist/stateful/provider-event-pipeline-metrics.js +48 -0
- package/dist/stateful/provider-event-pipeline.d.ts +50 -0
- package/dist/stateful/provider-event-pipeline.js +1 -0
- package/dist/stateful/provider-events.d.ts +101 -0
- package/dist/stateful/provider-events.js +289 -0
- package/dist/stateful/session-key.d.ts +15 -0
- package/dist/stateful/session-key.js +86 -0
- package/dist/stateful/stateful-provider-adapter-context.d.ts +5 -0
- package/dist/stateful/stateful-provider-adapter-context.js +42 -0
- package/dist/stateful/stateful-provider-adapter-metrics.d.ts +15 -0
- package/dist/stateful/stateful-provider-adapter-metrics.js +21 -0
- package/dist/stateful/stateful-provider-adapter.d.ts +98 -0
- package/dist/stateful/stateful-provider-adapter.js +287 -0
- package/dist/stateful/stateful-provider-observability.d.ts +62 -0
- package/dist/stateful/stateful-provider-observability.js +161 -0
- package/dist/stateful/stateful-provider-owner-forwarder.d.ts +41 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +207 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +32 -0
- package/dist/stateful/stateful-provider-runtime-context.js +60 -0
- package/dist/stateful/stateful-provider-runtime-executor.d.ts +34 -0
- package/dist/stateful/stateful-provider-runtime-executor.js +52 -0
- package/dist/stateful/stateful-provider-session-routing.d.ts +71 -0
- package/dist/stateful/stateful-provider-session-routing.js +353 -0
- package/dist/stateful/stateful-provider-session-runtime.d.ts +98 -0
- package/dist/stateful/stateful-provider-session-runtime.js +245 -0
- package/dist/stateful-signing.d.ts +18 -0
- package/dist/stateful-signing.js +27 -0
- package/dist/types.d.ts +39 -7
- package/package.json +7 -1
- package/src/config/loader.ts +22 -1
- package/src/errors.ts +15 -0
- package/src/index.ts +8 -1
- package/src/runtime/stealth.ts +127 -48
- package/src/server/index.ts +13 -1
- package/src/server/serve.ts +691 -25
- package/src/stateful/README.md +146 -0
- package/src/stateful/errors.ts +23 -0
- package/src/stateful/http-provider-event-emitter.ts +314 -0
- package/src/stateful/http-session-owner-registry.ts +306 -0
- package/src/stateful/index.ts +18 -0
- package/src/stateful/provider-event-delivery-failures.ts +80 -0
- package/src/stateful/provider-event-pipeline-metrics.ts +95 -0
- package/src/stateful/provider-event-pipeline.ts +61 -0
- package/src/stateful/provider-events.ts +462 -0
- package/src/stateful/session-key.ts +111 -0
- package/src/stateful/stateful-provider-adapter-context.ts +59 -0
- package/src/stateful/stateful-provider-adapter-metrics.ts +48 -0
- package/src/stateful/stateful-provider-adapter.ts +562 -0
- package/src/stateful/stateful-provider-observability.ts +261 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +279 -0
- package/src/stateful/stateful-provider-runtime-context.ts +92 -0
- package/src/stateful/stateful-provider-runtime-executor.ts +96 -0
- package/src/stateful/stateful-provider-session-routing.ts +555 -0
- package/src/stateful/stateful-provider-session-runtime.ts +403 -0
- package/src/stateful-signing.ts +46 -0
- package/src/types.ts +41 -7
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const STATEFUL_SIGNATURE_HEADER = "x-apifuse-stateful-signature";
|
|
2
|
+
export declare const STATEFUL_TIMESTAMP_HEADER = "x-apifuse-stateful-timestamp";
|
|
3
|
+
export declare const STATEFUL_NONCE_HEADER = "x-apifuse-stateful-nonce";
|
|
4
|
+
export type StatefulSigningInput = {
|
|
5
|
+
readonly secret: string;
|
|
6
|
+
readonly timestamp: string;
|
|
7
|
+
readonly rawBody: string;
|
|
8
|
+
readonly method: string;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
readonly nonce: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function signStatefulRequestBody(input: StatefulSigningInput): string;
|
|
13
|
+
export declare function verifyStatefulRequestSignature(input: StatefulSigningInput & {
|
|
14
|
+
readonly signature: string;
|
|
15
|
+
}): boolean;
|
|
16
|
+
export declare function statefulSignedHeaders(input: Omit<StatefulSigningInput, "nonce"> & {
|
|
17
|
+
readonly nonce?: string;
|
|
18
|
+
}): Record<string, string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
|
|
2
|
+
export const STATEFUL_SIGNATURE_HEADER = "x-apifuse-stateful-signature";
|
|
3
|
+
export const STATEFUL_TIMESTAMP_HEADER = "x-apifuse-stateful-timestamp";
|
|
4
|
+
export const STATEFUL_NONCE_HEADER = "x-apifuse-stateful-nonce";
|
|
5
|
+
export function signStatefulRequestBody(input) {
|
|
6
|
+
return `v1=${createHmac("sha256", input.secret)
|
|
7
|
+
.update(`v1:${input.method.toUpperCase()}:${input.path}:${input.timestamp}:${input.nonce}.${input.rawBody}`)
|
|
8
|
+
.digest("hex")}`;
|
|
9
|
+
}
|
|
10
|
+
export function verifyStatefulRequestSignature(input) {
|
|
11
|
+
return safeEqualAscii(input.signature, signStatefulRequestBody(input));
|
|
12
|
+
}
|
|
13
|
+
export function statefulSignedHeaders(input) {
|
|
14
|
+
const nonce = input.nonce ?? randomUUID();
|
|
15
|
+
return {
|
|
16
|
+
[STATEFUL_SIGNATURE_HEADER]: signStatefulRequestBody({ ...input, nonce }),
|
|
17
|
+
[STATEFUL_TIMESTAMP_HEADER]: input.timestamp,
|
|
18
|
+
[STATEFUL_NONCE_HEADER]: nonce,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function safeEqualAscii(actual, expected) {
|
|
22
|
+
const actualBytes = Buffer.from(actual);
|
|
23
|
+
const expectedBytes = Buffer.from(expected);
|
|
24
|
+
if (actualBytes.byteLength !== expectedBytes.byteLength)
|
|
25
|
+
return false;
|
|
26
|
+
return timingSafeEqual(actualBytes, expectedBytes);
|
|
27
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type ms from "ms";
|
|
2
|
+
import type { SerializedCookieJar } from "tough-cookie";
|
|
2
3
|
import type { infer as ZodInfer, ZodType } from "zod";
|
|
3
4
|
/** Minimal Standard Schema v1 shape accepted by provider operations. */
|
|
4
5
|
export interface StandardSchemaV1<Input = unknown, Output = Input> {
|
|
@@ -872,17 +873,42 @@ export interface StealthFetchOptions extends RequestOptions {
|
|
|
872
873
|
};
|
|
873
874
|
}
|
|
874
875
|
export interface CookieJar {
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
876
|
+
/** URL-less reads use the jar's response URL or session base URL. */
|
|
877
|
+
get(name: string, url?: string): string | undefined;
|
|
878
|
+
getAll(url?: string): Record<string, string>;
|
|
879
|
+
toString(url?: string): string;
|
|
880
|
+
find?(predicate: (cookie: string) => boolean, url?: string): string | undefined;
|
|
879
881
|
}
|
|
882
|
+
/**
|
|
883
|
+
* Version 1 of the JSON-safe, attribute-preserving stealth cookie store.
|
|
884
|
+
* The nested jar is tough-cookie's serialized form and retains cookie origin,
|
|
885
|
+
* Path, Secure, expiry, host-only, and other RFC attributes.
|
|
886
|
+
*/
|
|
887
|
+
export interface StealthCookieStoreV1 {
|
|
888
|
+
readonly version: 1;
|
|
889
|
+
readonly jar: SerializedCookieJar;
|
|
890
|
+
}
|
|
891
|
+
/** Cookie persistence formats understood by this SDK version. */
|
|
892
|
+
export type StealthCookieStore = StealthCookieStoreV1;
|
|
880
893
|
export interface StealthSessionCookies extends CookieJar {
|
|
881
|
-
has(name: string): boolean;
|
|
882
|
-
|
|
883
|
-
|
|
894
|
+
has(name: string, url?: string): boolean;
|
|
895
|
+
/** URL-less writes are scoped to the session base URL. */
|
|
896
|
+
setFromCookieStrings(cookieStrings: readonly string[], url?: string): void;
|
|
897
|
+
toHeader(url?: string): string;
|
|
898
|
+
/**
|
|
899
|
+
* Returns every cookie as a flat name/value map, collapsing duplicate names.
|
|
900
|
+
* @deprecated Use serialize() for lossless, attribute-preserving persistence.
|
|
901
|
+
*/
|
|
884
902
|
snapshot(): Record<string, string>;
|
|
903
|
+
/**
|
|
904
|
+
* Restores flat values as host-only, Path=/ cookies on the session base URL.
|
|
905
|
+
* @deprecated Use deserialize() with state produced by serialize().
|
|
906
|
+
*/
|
|
885
907
|
restore(cookies: Record<string, string>): void;
|
|
908
|
+
/** Returns a versioned, JSON-safe, attribute-preserving representation of every cookie. */
|
|
909
|
+
serialize(): StealthCookieStoreV1;
|
|
910
|
+
/** Replaces the jar with a previously serialized, attribute-preserving cookie store. */
|
|
911
|
+
deserialize(state: StealthCookieStore): void;
|
|
886
912
|
clear(): void;
|
|
887
913
|
}
|
|
888
914
|
export interface DeclarativeStealthResponse {
|
|
@@ -925,7 +951,13 @@ export interface StealthRedirectRunResult {
|
|
|
925
951
|
final: StealthResponse;
|
|
926
952
|
hops: StealthRedirectHop[];
|
|
927
953
|
reason: "completed" | "stopped" | "max_hops" | "missing_location" | "loop";
|
|
954
|
+
/**
|
|
955
|
+
* Complete flat view across all redirect hosts. Attributes and duplicate names are lost.
|
|
956
|
+
* @deprecated Use cookieStore for lossless persistence.
|
|
957
|
+
*/
|
|
928
958
|
cookies: Record<string, string>;
|
|
959
|
+
/** Versioned, attribute-preserving cookie state accumulated across the redirect chain. */
|
|
960
|
+
cookieStore: StealthCookieStoreV1;
|
|
929
961
|
}
|
|
930
962
|
export interface StealthSession {
|
|
931
963
|
fetch(url: string, options?: StealthFetchOptions): Promise<StealthResponse>;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.2.0-beta.
|
|
2
|
+
"version": "2.2.0-beta.12",
|
|
3
3
|
"name": "@apifuse/provider-sdk",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
@@ -61,6 +61,11 @@
|
|
|
61
61
|
"import": "./dist/server/index.js",
|
|
62
62
|
"default": "./dist/server/index.js"
|
|
63
63
|
},
|
|
64
|
+
"./stateful": {
|
|
65
|
+
"types": "./dist/stateful/index.d.ts",
|
|
66
|
+
"import": "./dist/stateful/index.js",
|
|
67
|
+
"default": "./dist/stateful/index.js"
|
|
68
|
+
},
|
|
64
69
|
"./testing": {
|
|
65
70
|
"types": "./dist/testing/index.d.ts",
|
|
66
71
|
"import": "./dist/testing/index.js",
|
|
@@ -109,6 +114,7 @@
|
|
|
109
114
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
110
115
|
"re2-wasm": "^1.0",
|
|
111
116
|
"safe-regex": "^2.1",
|
|
117
|
+
"tough-cookie": "^6.0.2",
|
|
112
118
|
"zod": "^4.4.3"
|
|
113
119
|
},
|
|
114
120
|
"repository": {
|
package/src/config/loader.ts
CHANGED
|
@@ -156,10 +156,19 @@ export type ProxyTelemetrySink = {
|
|
|
156
156
|
recordProxyVendorFailover?(event: ProxyVendorFailoverTelemetryEvent): void;
|
|
157
157
|
};
|
|
158
158
|
|
|
159
|
+
export type ProxyResolutionSource =
|
|
160
|
+
| "explicit"
|
|
161
|
+
| "env"
|
|
162
|
+
| "config"
|
|
163
|
+
| "smartproxy-allocator"
|
|
164
|
+
| "nodemaven-gateway";
|
|
165
|
+
|
|
159
166
|
export type ResolvedProxyConfig = {
|
|
160
167
|
shouldWarn: boolean;
|
|
161
168
|
url?: string;
|
|
162
|
-
|
|
169
|
+
/** SDK-native vendor that supplied the URL, when applicable. */
|
|
170
|
+
vendor?: ProxyVendorName;
|
|
171
|
+
source?: ProxyResolutionSource;
|
|
163
172
|
protocol?: ProxyProtocol;
|
|
164
173
|
diagnostics?: Record<string, string | number | boolean>;
|
|
165
174
|
};
|
|
@@ -656,6 +665,18 @@ export async function resolveProxyConfigAsync(
|
|
|
656
665
|
return { shouldWarn: true };
|
|
657
666
|
}
|
|
658
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Resolve the proxy URL for a provider-owned consumer such as a CAPTCHA solver.
|
|
670
|
+
* Vendor allocation and failover remain owned by the SDK.
|
|
671
|
+
*/
|
|
672
|
+
export async function resolveProxy(
|
|
673
|
+
options: ProxyResolutionOptions = {},
|
|
674
|
+
): Promise<ResolvedProxyConfig> {
|
|
675
|
+
const resolved = await resolveProxyConfigAsync(options);
|
|
676
|
+
const vendor = vendorFromResolvedSource(resolved.source);
|
|
677
|
+
return vendor ? { ...resolved, vendor } : resolved;
|
|
678
|
+
}
|
|
679
|
+
|
|
659
680
|
/**
|
|
660
681
|
* Each vendor's default egress protocol, chosen from live KR benchmarks. HTTP
|
|
661
682
|
* CONNECT wins for nodemaven (socks5 adds ~500ms through the gateway) and ties
|
package/src/errors.ts
CHANGED
|
@@ -79,6 +79,21 @@ export class SDKError extends ProviderError {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/** Raised when persisted stealth cookies use a store version this SDK cannot read. */
|
|
83
|
+
export class StealthCookieStoreVersionError extends SDKError {
|
|
84
|
+
constructor(public readonly version: unknown) {
|
|
85
|
+
const displayedVersion =
|
|
86
|
+
typeof version === "string" || typeof version === "number"
|
|
87
|
+
? String(version)
|
|
88
|
+
: "missing or invalid";
|
|
89
|
+
super(`Unsupported stealth cookie store version: ${displayedVersion}`, {
|
|
90
|
+
code: "unsupported_stealth_cookie_store_version",
|
|
91
|
+
details: { receivedVersion: version, supportedVersions: [1] },
|
|
92
|
+
});
|
|
93
|
+
this.name = "StealthCookieStoreVersionError";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
export class AuthError extends ProviderError {
|
|
83
98
|
constructor(message: string, options?: ProviderErrorOptions) {
|
|
84
99
|
super(message, options);
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,14 @@ export type {
|
|
|
7
7
|
ApiFuseConfig,
|
|
8
8
|
BrowserConfig,
|
|
9
9
|
ProxyConfig,
|
|
10
|
+
ProxyProtocol,
|
|
11
|
+
ProxyResolutionOptions,
|
|
12
|
+
ProxyResolutionSource,
|
|
13
|
+
ProxyVendorName,
|
|
14
|
+
ResolvedProxyConfig,
|
|
10
15
|
SessionConfig,
|
|
11
16
|
} from "./config/loader.js";
|
|
12
|
-
export { defineConfig, loadApiFuseConfig } from "./config/loader.js";
|
|
17
|
+
export { defineConfig, loadApiFuseConfig, resolveProxy } from "./config/loader.js";
|
|
13
18
|
export {
|
|
14
19
|
canonicalJson,
|
|
15
20
|
digestProviderContract,
|
|
@@ -245,6 +250,8 @@ export type {
|
|
|
245
250
|
StateValue,
|
|
246
251
|
StateWriteOptions,
|
|
247
252
|
StealthClient,
|
|
253
|
+
StealthCookieStore,
|
|
254
|
+
StealthCookieStoreV1,
|
|
248
255
|
StealthFetchOptions,
|
|
249
256
|
StealthPlatform,
|
|
250
257
|
StealthProfile,
|
package/src/runtime/stealth.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { Browser, ImpitOptions, ImpitResponse, RequestInit } from "impit";
|
|
3
3
|
import { Impit } from "impit";
|
|
4
|
+
import { Cookie, CookieJar as ToughCookieJar } from "tough-cookie";
|
|
4
5
|
|
|
5
6
|
import type { ProxyResolutionOptions, ProxyVendorName } from "../config/loader.js";
|
|
6
7
|
import {
|
|
7
8
|
DEFAULT_SMARTPROXY_POOL_SIZE,
|
|
8
9
|
invalidateProxyResolutionCacheAsync,
|
|
9
|
-
policyResolvesRegistryVendorChain,
|
|
10
10
|
ProxyResolutionError,
|
|
11
|
+
policyResolvesRegistryVendorChain,
|
|
11
12
|
resolvePolicyProxyPoolSpan,
|
|
12
13
|
resolvePolicyTransportAttemptCap,
|
|
13
14
|
resolveProxyConfigAsync,
|
|
14
15
|
vendorFromResolvedSource,
|
|
15
16
|
} from "../config/loader.js";
|
|
16
|
-
import { SDKError, TransportError } from "../errors.js";
|
|
17
|
+
import { SDKError, StealthCookieStoreVersionError, TransportError } from "../errors.js";
|
|
17
18
|
import { getStealthProfile } from "../stealth/profiles.js";
|
|
18
19
|
import type {
|
|
19
20
|
CookieJar,
|
|
20
21
|
HttpMethod,
|
|
21
22
|
StealthClient,
|
|
23
|
+
StealthCookieStore,
|
|
24
|
+
StealthCookieStoreV1,
|
|
22
25
|
StealthFetchOptions,
|
|
23
26
|
StealthRedirectHop,
|
|
24
27
|
StealthResponse,
|
|
@@ -127,81 +130,145 @@ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
|
127
130
|
return typeof value === "object" && value !== null;
|
|
128
131
|
}
|
|
129
132
|
|
|
133
|
+
const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
|
|
134
|
+
|
|
130
135
|
class CookieJarImpl implements CookieJar {
|
|
131
|
-
private
|
|
136
|
+
private cookies: ToughCookieJar;
|
|
137
|
+
private readonly defaultUrl: string;
|
|
132
138
|
|
|
133
|
-
constructor(cookieStrings: string[]) {
|
|
134
|
-
this.cookies = {
|
|
139
|
+
constructor(cookieStrings: readonly string[], defaultUrl = LEGACY_COOKIE_ORIGIN) {
|
|
140
|
+
this.cookies = new ToughCookieJar(undefined, {
|
|
141
|
+
allowSecureOnLocal: false,
|
|
142
|
+
rejectPublicSuffixes: true,
|
|
143
|
+
});
|
|
144
|
+
this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
|
|
135
145
|
this.setFromCookieStrings(cookieStrings);
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
148
|
+
/**
|
|
149
|
+
* URL-less legacy operations are scoped to this jar's default URL. Session
|
|
150
|
+
* jars use the client's base URL and response jars use the response URL. A
|
|
151
|
+
* flat restore has no attributes to recover, so it creates host-only Path=/
|
|
152
|
+
* cookies for that default URL instead of making them visible to every host.
|
|
153
|
+
*/
|
|
154
|
+
setFromCookieStrings(cookieStrings: readonly string[], url = this.defaultUrl): void {
|
|
155
|
+
const cookieUrl = this.normalizeUrl(url);
|
|
156
|
+
if (!cookieUrl) return;
|
|
149
157
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (name) this.cookies[name] = value;
|
|
158
|
+
for (const cookieString of cookieStrings) {
|
|
159
|
+
this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
|
|
153
160
|
}
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
get(name: string): string | undefined {
|
|
157
|
-
return this.
|
|
163
|
+
get(name: string, url?: string): string | undefined {
|
|
164
|
+
return this.getAll(url)[name];
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
getAll(): Record<string, string> {
|
|
161
|
-
return
|
|
167
|
+
getAll(url?: string): Record<string, string> {
|
|
168
|
+
return Object.fromEntries(
|
|
169
|
+
this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => [cookie.key, cookie.value]),
|
|
170
|
+
);
|
|
162
171
|
}
|
|
163
172
|
|
|
164
|
-
has(name: string): boolean {
|
|
165
|
-
return Object.hasOwn(this.
|
|
173
|
+
has(name: string, url?: string): boolean {
|
|
174
|
+
return Object.hasOwn(this.getAll(url), name);
|
|
166
175
|
}
|
|
167
176
|
|
|
168
|
-
toString(): string {
|
|
169
|
-
return
|
|
170
|
-
.map((
|
|
177
|
+
toString(url?: string): string {
|
|
178
|
+
return this.getUniqueCookies(url ?? this.defaultUrl)
|
|
179
|
+
.map((cookie) => cookie.cookieString())
|
|
171
180
|
.join("; ");
|
|
172
181
|
}
|
|
173
182
|
|
|
174
|
-
toHeader(): string {
|
|
175
|
-
return this.toString();
|
|
183
|
+
toHeader(url?: string): string {
|
|
184
|
+
return this.toString(url);
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
snapshot(): Record<string, string> {
|
|
179
|
-
|
|
188
|
+
// This compatibility view deliberately enumerates the serialized store,
|
|
189
|
+
// not getAll(defaultUrl): persistence must include sibling hosts and paths.
|
|
190
|
+
// Duplicate names still collapse because a flat map cannot represent them.
|
|
191
|
+
const entries: [string, string][] = [];
|
|
192
|
+
for (const cookie of this.serialize().jar.cookies) {
|
|
193
|
+
if (typeof cookie.key === "string" && typeof cookie.value === "string" && cookie.key) {
|
|
194
|
+
entries.push([cookie.key, cookie.value]);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return Object.fromEntries(entries);
|
|
180
198
|
}
|
|
181
199
|
|
|
182
200
|
restore(cookies: Record<string, string>): void {
|
|
183
201
|
this.clear();
|
|
184
202
|
for (const [name, value] of Object.entries(cookies)) {
|
|
185
|
-
if (name)
|
|
203
|
+
if (!name) continue;
|
|
204
|
+
this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
|
|
205
|
+
ignoreError: true,
|
|
206
|
+
});
|
|
186
207
|
}
|
|
187
208
|
}
|
|
188
209
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
210
|
+
serialize(): StealthCookieStoreV1 {
|
|
211
|
+
const jar = this.cookies.serializeSync();
|
|
212
|
+
if (!jar) {
|
|
213
|
+
throw new SDKError("Stealth cookie store could not be serialized", {
|
|
214
|
+
code: "stealth_cookie_store_serialize_failed",
|
|
215
|
+
});
|
|
192
216
|
}
|
|
217
|
+
return { version: 1, jar };
|
|
193
218
|
}
|
|
194
219
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
220
|
+
deserialize(state: StealthCookieStore): void {
|
|
221
|
+
const version = isRecord(state) ? state.version : undefined;
|
|
222
|
+
if (version !== 1) {
|
|
223
|
+
throw new StealthCookieStoreVersionError(version);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Deserialize into a new jar first so invalid state cannot partially clear
|
|
227
|
+
// or replace a live session. tough-cookie restores the cookie attributes and
|
|
228
|
+
// matching semantics represented in its own serialized format.
|
|
229
|
+
const restored = ToughCookieJar.deserializeSync(state.jar);
|
|
230
|
+
// tough-cookie 6 does not include this option in serializeSync(). Preserve
|
|
231
|
+
// the SDK's stricter setting across restoration.
|
|
232
|
+
Reflect.set(restored, "allowSecureOnLocal", false);
|
|
233
|
+
this.cookies = restored;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
clear(): void {
|
|
237
|
+
this.cookies.removeAllCookiesSync();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
find(predicate: (cookie: string) => boolean, url?: string): string | undefined {
|
|
241
|
+
for (const cookie of this.getUniqueCookies(url ?? this.defaultUrl)) {
|
|
242
|
+
const cookieString = cookie.cookieString();
|
|
243
|
+
if (predicate(cookieString)) {
|
|
244
|
+
return cookieString;
|
|
200
245
|
}
|
|
201
246
|
}
|
|
202
247
|
|
|
203
248
|
return undefined;
|
|
204
249
|
}
|
|
250
|
+
|
|
251
|
+
private normalizeUrl(url: string): string | undefined {
|
|
252
|
+
try {
|
|
253
|
+
return new URL(url).toString();
|
|
254
|
+
} catch {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private getUniqueCookies(url: string): Cookie[] {
|
|
260
|
+
const cookieUrl = this.normalizeUrl(url);
|
|
261
|
+
if (!cookieUrl) return [];
|
|
262
|
+
|
|
263
|
+
// tough-cookie returns longer (more-specific) paths first. Keeping the
|
|
264
|
+
// first cookie for each name prevents ambiguous duplicate-name headers.
|
|
265
|
+
const names = new Set<string>();
|
|
266
|
+
return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
|
|
267
|
+
if (names.has(cookie.key)) return false;
|
|
268
|
+
names.add(cookie.key);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
205
272
|
}
|
|
206
273
|
|
|
207
274
|
function closestImpitBrowser(
|
|
@@ -282,12 +349,12 @@ function hasOwn(object: object, key: string): boolean {
|
|
|
282
349
|
}
|
|
283
350
|
function toImpitCookieJar(cookieJar: CookieJarImpl): NonNullable<ImpitOptions["cookieJar"]> {
|
|
284
351
|
return {
|
|
285
|
-
setCookie(cookie: string,
|
|
286
|
-
cookieJar.setFromCookieStrings([cookie]);
|
|
352
|
+
setCookie(cookie: string, url: string, cb?: (error?: unknown) => void) {
|
|
353
|
+
cookieJar.setFromCookieStrings([cookie], url);
|
|
287
354
|
if (typeof cb === "function") cb();
|
|
288
355
|
},
|
|
289
|
-
getCookieString(
|
|
290
|
-
return cookieJar.
|
|
356
|
+
getCookieString(url: string) {
|
|
357
|
+
return cookieJar.toHeader(url);
|
|
291
358
|
},
|
|
292
359
|
};
|
|
293
360
|
}
|
|
@@ -341,7 +408,10 @@ export async function normalizeResponse(
|
|
|
341
408
|
requestUrl?: string,
|
|
342
409
|
): Promise<StealthResponse> {
|
|
343
410
|
const headers = Object.fromEntries(response.headers.entries());
|
|
344
|
-
const cookies = new CookieJarImpl(
|
|
411
|
+
const cookies = new CookieJarImpl(
|
|
412
|
+
setCookieHeadersFromResponse(response.headers),
|
|
413
|
+
response.url ?? requestUrl,
|
|
414
|
+
);
|
|
345
415
|
const bodyBytes = await response.arrayBuffer();
|
|
346
416
|
const body = new TextDecoder().decode(bodyBytes);
|
|
347
417
|
|
|
@@ -588,7 +658,7 @@ function createSessionFetcher(
|
|
|
588
658
|
let closed = false;
|
|
589
659
|
let hasWarnedMissingProxy = false;
|
|
590
660
|
const warn = clientOptions.warn ?? console.warn;
|
|
591
|
-
const cookieJar = new CookieJarImpl([]);
|
|
661
|
+
const cookieJar = new CookieJarImpl([], baseUrl);
|
|
592
662
|
const impitCookieJar = toImpitCookieJar(cookieJar);
|
|
593
663
|
|
|
594
664
|
function getClient(
|
|
@@ -757,7 +827,7 @@ function createSessionFetcher(
|
|
|
757
827
|
const requestUrl = appendQueryParams(resolveUrl(baseUrl, url), options.params);
|
|
758
828
|
const headers = { ...(options.headers ?? {}) };
|
|
759
829
|
if (!hasHeader(headers, "Cookie")) {
|
|
760
|
-
const cookieHeader = cookieJar.
|
|
830
|
+
const cookieHeader = cookieJar.toHeader(requestUrl);
|
|
761
831
|
if (cookieHeader) headers.Cookie = cookieHeader;
|
|
762
832
|
}
|
|
763
833
|
const requestInit: StealthRequestInit = {
|
|
@@ -774,7 +844,10 @@ function createSessionFetcher(
|
|
|
774
844
|
requestInit,
|
|
775
845
|
);
|
|
776
846
|
const normalized = await normalizeResponse(response, requestUrl);
|
|
777
|
-
cookieJar.setFromCookieStrings(
|
|
847
|
+
cookieJar.setFromCookieStrings(
|
|
848
|
+
setCookieHeadersFromResponse(response.headers),
|
|
849
|
+
response.url ?? requestUrl,
|
|
850
|
+
);
|
|
778
851
|
|
|
779
852
|
if (proxy && isProxyConnectFailureResponse(response, normalized.body)) {
|
|
780
853
|
throw createProxyConnectFailureError(normalized.body);
|
|
@@ -954,6 +1027,7 @@ function createSessionFetcher(
|
|
|
954
1027
|
hops,
|
|
955
1028
|
reason: "completed",
|
|
956
1029
|
cookies: cookieJar.snapshot(),
|
|
1030
|
+
cookieStore: cookieJar.serialize(),
|
|
957
1031
|
};
|
|
958
1032
|
}
|
|
959
1033
|
|
|
@@ -976,6 +1050,7 @@ function createSessionFetcher(
|
|
|
976
1050
|
hops,
|
|
977
1051
|
reason: "stopped",
|
|
978
1052
|
cookies: cookieJar.snapshot(),
|
|
1053
|
+
cookieStore: cookieJar.serialize(),
|
|
979
1054
|
};
|
|
980
1055
|
}
|
|
981
1056
|
|
|
@@ -985,6 +1060,7 @@ function createSessionFetcher(
|
|
|
985
1060
|
hops,
|
|
986
1061
|
reason: "missing_location",
|
|
987
1062
|
cookies: cookieJar.snapshot(),
|
|
1063
|
+
cookieStore: cookieJar.serialize(),
|
|
988
1064
|
};
|
|
989
1065
|
}
|
|
990
1066
|
|
|
@@ -994,6 +1070,7 @@ function createSessionFetcher(
|
|
|
994
1070
|
hops,
|
|
995
1071
|
reason: "max_hops",
|
|
996
1072
|
cookies: cookieJar.snapshot(),
|
|
1073
|
+
cookieStore: cookieJar.serialize(),
|
|
997
1074
|
};
|
|
998
1075
|
}
|
|
999
1076
|
|
|
@@ -1007,6 +1084,7 @@ function createSessionFetcher(
|
|
|
1007
1084
|
hops,
|
|
1008
1085
|
reason: "loop",
|
|
1009
1086
|
cookies: cookieJar.snapshot(),
|
|
1087
|
+
cookieStore: cookieJar.serialize(),
|
|
1010
1088
|
};
|
|
1011
1089
|
}
|
|
1012
1090
|
method = nextMethod;
|
|
@@ -1028,6 +1106,7 @@ function createSessionFetcher(
|
|
|
1028
1106
|
hops,
|
|
1029
1107
|
reason: "max_hops",
|
|
1030
1108
|
cookies: cookieJar.snapshot(),
|
|
1109
|
+
cookieStore: cookieJar.serialize(),
|
|
1031
1110
|
};
|
|
1032
1111
|
},
|
|
1033
1112
|
},
|
package/src/server/index.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
createServerApp,
|
|
3
|
+
type ProviderServerCloseOptions,
|
|
4
|
+
type ProviderServerHandle,
|
|
5
|
+
type ProviderServerLogEvent,
|
|
6
|
+
type ProviderServerLogger,
|
|
7
|
+
type ProviderServerOperationExecutor,
|
|
8
|
+
type ProviderServerOperationExecutorInput,
|
|
9
|
+
type ProviderServerOptions,
|
|
10
|
+
type ProviderServerStatefulForwardEnvelope,
|
|
11
|
+
type ServeOptions,
|
|
12
|
+
serve,
|
|
13
|
+
} from "./serve.js";
|
|
2
14
|
export {
|
|
3
15
|
computeSelfTestPlanDigest,
|
|
4
16
|
createSelfTestApp,
|