@bluefields/fetcher 0.2.0
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/LICENSE +661 -0
- package/dist/asset-block-measure.d.ts +14 -0
- package/dist/asset-block-measure.js +82 -0
- package/dist/asset-block-measure.js.map +1 -0
- package/dist/browser-pool.d.ts +65 -0
- package/dist/browser-pool.js +161 -0
- package/dist/browser-pool.js.map +1 -0
- package/dist/cost.d.ts +51 -0
- package/dist/cost.js +54 -0
- package/dist/cost.js.map +1 -0
- package/dist/fetcher-brightdata.d.ts +52 -0
- package/dist/fetcher-brightdata.js +135 -0
- package/dist/fetcher-brightdata.js.map +1 -0
- package/dist/fetcher-escalating.d.ts +100 -0
- package/dist/fetcher-escalating.js +489 -0
- package/dist/fetcher-escalating.js.map +1 -0
- package/dist/fetcher-http.d.ts +81 -0
- package/dist/fetcher-http.js +395 -0
- package/dist/fetcher-http.js.map +1 -0
- package/dist/fetcher-patchright.d.ts +212 -0
- package/dist/fetcher-patchright.js +505 -0
- package/dist/fetcher-patchright.js.map +1 -0
- package/dist/fetcher-robots-gate.d.ts +60 -0
- package/dist/fetcher-robots-gate.js +87 -0
- package/dist/fetcher-robots-gate.js.map +1 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-options.d.ts +59 -0
- package/dist/internal-options.js +16 -0
- package/dist/internal-options.js.map +1 -0
- package/dist/perf-harness.d.ts +105 -0
- package/dist/perf-harness.js +176 -0
- package/dist/perf-harness.js.map +1 -0
- package/dist/storage-memory.d.ts +18 -0
- package/dist/storage-memory.js +45 -0
- package/dist/storage-memory.js.map +1 -0
- package/dist/storage-r2.d.ts +44 -0
- package/dist/storage-r2.js +209 -0
- package/dist/storage-r2.js.map +1 -0
- package/dist/types.d.ts +231 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patchright fetcher adapter (chunks 8 + Phase A real wiring).
|
|
3
|
+
*
|
|
4
|
+
* Patchright is a Chromium fork with CDP-leak patches that defeat many
|
|
5
|
+
* naive bot-detection signals (navigator.webdriver, console.debug
|
|
6
|
+
* fingerprinting, etc.). API is Playwright-compatible — drop-in.
|
|
7
|
+
*
|
|
8
|
+
* Production wiring is gated on IPROYAL_* env vars: when they're set,
|
|
9
|
+
* the factory (`tryCreatePatchrightAdapter`) returns a real adapter
|
|
10
|
+
* that launches Chromium with the IPRoyal residential proxy. Without
|
|
11
|
+
* them, callers fall back to the HTTP adapter (`fetcher-http.ts`).
|
|
12
|
+
*
|
|
13
|
+
* Per-fetch lifecycle: launch fresh browser → newContext → newPage →
|
|
14
|
+
* goto → wait → content + (optional) screenshot → close. One browser
|
|
15
|
+
* per fetch keeps fingerprint state isolated; pooling can ship later
|
|
16
|
+
* if launch overhead becomes load-bearing (~500ms per fetch today).
|
|
17
|
+
*
|
|
18
|
+
* Fingerprint rotation: v1 uses a small pool of (UA, viewport, locale,
|
|
19
|
+
* timezone) tuples picked randomly per fetch. Full diversity (Review #4)
|
|
20
|
+
* comes later — we just need basic variation to defeat per-tuple
|
|
21
|
+
* fingerprint blocklists.
|
|
22
|
+
*
|
|
23
|
+
* Egress IP: deferred. IPRoyal residential proxies rotate per-session;
|
|
24
|
+
* resolving the actual egress IP requires an extra eval to api.ipify.org
|
|
25
|
+
* which we don't want to pay on every fetch. Logged as a placeholder for
|
|
26
|
+
* now; revisit in M14-17 when attestation needs it (Review #3 manifest).
|
|
27
|
+
*/
|
|
28
|
+
import type { ActionResults, PageAction, RequestCookie, WarmableFetcherAdapter } from './types.js';
|
|
29
|
+
export declare const PATCHRIGHT_REQUIRED_ENV: readonly ["IPROYAL_GATEWAY"];
|
|
30
|
+
/** Subset of a Playwright Route we use to abort/continue intercepted requests. */
|
|
31
|
+
export interface PatchrightRoute {
|
|
32
|
+
request(): {
|
|
33
|
+
resourceType(): string;
|
|
34
|
+
};
|
|
35
|
+
abort(): Promise<void>;
|
|
36
|
+
continue(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/** Subset of a Playwright Response we read for transferred-byte accounting. */
|
|
39
|
+
export interface PatchrightResponse {
|
|
40
|
+
headers(): Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Pure predicate: should the browser tier abort this request's download?
|
|
44
|
+
* Extracted so it's unit-tested without a live browser. The caller is
|
|
45
|
+
* responsible for skipping interception entirely when a screenshot is
|
|
46
|
+
* requested (a screenshot needs the full visual render).
|
|
47
|
+
*/
|
|
48
|
+
export declare function shouldBlockResource(resourceType: string, opts?: {
|
|
49
|
+
blockStylesheet?: boolean;
|
|
50
|
+
}): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Subset of Playwright's Page surface we actually call. Kept narrow so
|
|
53
|
+
* the test mock stays small.
|
|
54
|
+
*/
|
|
55
|
+
export interface PatchrightPage {
|
|
56
|
+
goto(url: string, opts: {
|
|
57
|
+
waitUntil: 'load' | 'networkidle' | 'domcontentloaded';
|
|
58
|
+
timeout: number;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
status(): number;
|
|
61
|
+
headers(): Record<string, string>;
|
|
62
|
+
url(): string;
|
|
63
|
+
} | null>;
|
|
64
|
+
waitForSelector(selector: string, opts: {
|
|
65
|
+
timeout: number;
|
|
66
|
+
}): Promise<unknown>;
|
|
67
|
+
waitForTimeout(ms: number): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Wait for a Playwright load state (we use 'networkidle'). Optional: the real
|
|
70
|
+
* Playwright Page always has it, but the test mock may omit it, and the
|
|
71
|
+
* full-render path calls it best-effort (skips when absent, swallows timeout).
|
|
72
|
+
*/
|
|
73
|
+
waitForLoadState?(state: 'load' | 'domcontentloaded' | 'networkidle', opts?: {
|
|
74
|
+
timeout?: number;
|
|
75
|
+
}): Promise<void>;
|
|
76
|
+
content(): Promise<string>;
|
|
77
|
+
screenshot(opts: {
|
|
78
|
+
fullPage: boolean;
|
|
79
|
+
}): Promise<Buffer>;
|
|
80
|
+
url(): string;
|
|
81
|
+
/** Click a CSS selector (Phase F actions). */
|
|
82
|
+
click(selector: string, opts?: {
|
|
83
|
+
timeout?: number;
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
/** Fill a selector with text (Phase F actions). */
|
|
86
|
+
fill(selector: string, text: string): Promise<void>;
|
|
87
|
+
/** Keyboard namespace — type free-form text or press a single key. */
|
|
88
|
+
keyboard: {
|
|
89
|
+
type(text: string): Promise<void>;
|
|
90
|
+
press(key: string): Promise<void>;
|
|
91
|
+
};
|
|
92
|
+
/** Evaluate JS in the page context — used for scroll. */
|
|
93
|
+
evaluate(fn: string | ((arg?: unknown) => unknown), arg?: unknown): Promise<unknown>;
|
|
94
|
+
/**
|
|
95
|
+
* Intercept network requests (optional — real Playwright always has it; the
|
|
96
|
+
* test mock may omit it, so callers guard with a typeof check). We use it to
|
|
97
|
+
* abort image/media/font downloads we discard, cutting metered proxy egress.
|
|
98
|
+
*/
|
|
99
|
+
route?(urlPattern: string, handler: (route: PatchrightRoute) => Promise<void> | void): Promise<void>;
|
|
100
|
+
/** Subscribe to page events (optional). We use `response` to sum bytes. */
|
|
101
|
+
on?(event: 'response', handler: (response: PatchrightResponse) => void): void;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Subset of a Playwright BrowserContext we use. `close()` is optional only so
|
|
105
|
+
* the narrow test mock can omit it; the adapter always calls it best-effort in
|
|
106
|
+
* `finally` so a pooled (reused) browser never accumulates one context per fetch.
|
|
107
|
+
*/
|
|
108
|
+
export interface PatchrightBrowserContext {
|
|
109
|
+
addCookies(cookies: RequestCookie[]): Promise<void>;
|
|
110
|
+
newPage(): Promise<PatchrightPage>;
|
|
111
|
+
/** Close the context and all its pages. */
|
|
112
|
+
close?(): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Minimal subset of patchright/playwright's chromium namespace that we
|
|
116
|
+
* actually use. Tests inject a mock that implements just these methods.
|
|
117
|
+
*/
|
|
118
|
+
export interface ChromiumLauncher {
|
|
119
|
+
launch(opts: {
|
|
120
|
+
proxy: {
|
|
121
|
+
server: string;
|
|
122
|
+
username: string;
|
|
123
|
+
password: string;
|
|
124
|
+
};
|
|
125
|
+
headless?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Chromium command-line args. We pass --no-sandbox in Docker because
|
|
128
|
+
* Chromium's setuid sandbox can't initialize when the process is
|
|
129
|
+
* root and seccomp isn't configured — the launch silently hangs
|
|
130
|
+
* otherwise.
|
|
131
|
+
*/
|
|
132
|
+
args?: string[];
|
|
133
|
+
/** Hard timeout for the launch step. Default 30s in Playwright. */
|
|
134
|
+
timeout?: number;
|
|
135
|
+
}): Promise<{
|
|
136
|
+
newContext(opts: {
|
|
137
|
+
userAgent: string;
|
|
138
|
+
viewport: {
|
|
139
|
+
width: number;
|
|
140
|
+
height: number;
|
|
141
|
+
};
|
|
142
|
+
locale: string;
|
|
143
|
+
timezoneId: string;
|
|
144
|
+
extraHTTPHeaders?: Record<string, string>;
|
|
145
|
+
}): Promise<PatchrightBrowserContext>;
|
|
146
|
+
close(): Promise<void>;
|
|
147
|
+
}>;
|
|
148
|
+
}
|
|
149
|
+
export interface PatchrightAdapterConfig {
|
|
150
|
+
proxy: {
|
|
151
|
+
server: string;
|
|
152
|
+
username: string;
|
|
153
|
+
password: string;
|
|
154
|
+
};
|
|
155
|
+
/** Test seam: inject a mock chromium launcher. */
|
|
156
|
+
chromiumImpl?: ChromiumLauncher;
|
|
157
|
+
/** Test seam: deterministic random for fingerprint picking. */
|
|
158
|
+
rand?: () => number;
|
|
159
|
+
/** Override the default user agent (rare — fingerprint pool usually wins). */
|
|
160
|
+
defaultUserAgent?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Number of pooled browsers to keep warm. When 0 (default), every fetch
|
|
163
|
+
* launches and closes a fresh browser (~500ms-1.5s overhead). When > 0,
|
|
164
|
+
* the first `pool` fetches each take the launch hit, subsequent fetches
|
|
165
|
+
* reuse a browser and only pay newContext (~30ms).
|
|
166
|
+
*
|
|
167
|
+
* Each browser still gets a fresh `newContext` per fetch, so reused
|
|
168
|
+
* browsers don't leak cookies / localStorage / fingerprint state across
|
|
169
|
+
* customers.
|
|
170
|
+
*/
|
|
171
|
+
pool?: number;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Returns the env-gated Patchright adapter when the required env vars
|
|
175
|
+
* are set; null otherwise. Callers (the factory in `index.ts`) fall
|
|
176
|
+
* back to the HTTP adapter when this returns null.
|
|
177
|
+
*/
|
|
178
|
+
export declare function tryCreatePatchrightAdapter(env?: NodeJS.ProcessEnv): WarmableFetcherAdapter | null;
|
|
179
|
+
/**
|
|
180
|
+
* Direct constructor — bypass the env gate. Useful in tests + when the
|
|
181
|
+
* caller has the proxy creds from somewhere other than env vars.
|
|
182
|
+
*/
|
|
183
|
+
export declare function createPatchrightAdapter(config: PatchrightAdapterConfig): WarmableFetcherAdapter;
|
|
184
|
+
export interface FullRenderOptions {
|
|
185
|
+
/** Absolute wall-clock deadline (ms, Date.now-based). Full render never runs past it. */
|
|
186
|
+
deadlineMs: number;
|
|
187
|
+
/** Test seam: clock. Default Date.now. */
|
|
188
|
+
nowMs?: () => number;
|
|
189
|
+
/** Override the hard iteration cap (test seam). Default FULL_RENDER_MAX_STEPS. */
|
|
190
|
+
maxSteps?: number;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Fully render a JS app before its content is read: a best-effort `networkidle`
|
|
194
|
+
* wait, then a bounded autoscroll + content-settle loop. Used on escalation
|
|
195
|
+
* re-fetches of a SPA shell (no customer waitFor) so we capture hydrated content
|
|
196
|
+
* instead of the empty mount point tier-0 returned.
|
|
197
|
+
*
|
|
198
|
+
* Hard-bounded so it can never hang: the loop stops
|
|
199
|
+
* at the FIRST of — the page looks rendered (>= FULL_RENDER_MIN_CHARS visible text
|
|
200
|
+
* AND the DOM stopped growing), `maxSteps` reached, or the `deadlineMs` passed.
|
|
201
|
+
* Every page call is best-effort (errors swallowed): a flaky settle must never
|
|
202
|
+
* fail an otherwise-good fetch. Fetcher-layer only — it issues no new outbound
|
|
203
|
+
* request of ours; sub-resource loads happen inside the already-proxied browser,
|
|
204
|
+
* so the SSRF posture is unchanged.
|
|
205
|
+
*/
|
|
206
|
+
export declare function fullyRenderPage(page: PatchrightPage, opts: FullRenderOptions): Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Execute Phase F actions sequentially against an open page. Throws on
|
|
209
|
+
* the first failure with the action index included for easier debugging
|
|
210
|
+
* — the route surfaces this in the 502 response detail.
|
|
211
|
+
*/
|
|
212
|
+
export declare function runActions(page: PatchrightPage, actions: PageAction[]): Promise<ActionResults>;
|