jumpy-lion 0.1.6-beta.7 → 1.0.0-beta.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/README.md +1 -607
- package/browser/fonts/macos-bundle/40-macos-aliases.conf +515 -0
- package/browser/fonts/macos-bundle/README.md +24 -0
- package/browser/fonts/macos-bundle/install.sh +83 -0
- package/dist/browser-controller.d.ts +47 -4
- package/dist/browser-controller.d.ts.map +1 -1
- package/dist/browser-controller.js +219 -278
- package/dist/browser-controller.js.map +1 -1
- package/dist/browser-plugin.d.ts +82 -110
- package/dist/browser-plugin.d.ts.map +1 -1
- package/dist/browser-plugin.js +376 -379
- package/dist/browser-plugin.js.map +1 -1
- package/dist/browser-process/align-fingerprint-config.d.ts +36 -0
- package/dist/browser-process/align-fingerprint-config.d.ts.map +1 -0
- package/dist/browser-process/align-fingerprint-config.js +74 -0
- package/dist/browser-process/align-fingerprint-config.js.map +1 -0
- package/dist/browser-process/anti-detect-browser.d.ts +2 -2
- package/dist/browser-process/anti-detect-browser.js +7 -7
- package/dist/browser-process/anti-detect-config.d.ts +131 -2
- package/dist/browser-process/anti-detect-config.d.ts.map +1 -1
- package/dist/browser-process/anti-detect-config.js +337 -127
- package/dist/browser-process/anti-detect-config.js.map +1 -1
- package/dist/browser-process/browser.d.ts +25 -92
- package/dist/browser-process/browser.d.ts.map +1 -1
- package/dist/browser-process/browser.js +429 -73
- package/dist/browser-process/browser.js.map +1 -1
- package/dist/browser-process/fingerprint-config.d.ts +146 -0
- package/dist/browser-process/fingerprint-config.d.ts.map +1 -0
- package/dist/browser-process/fingerprint-config.js +308 -0
- package/dist/browser-process/fingerprint-config.js.map +1 -0
- package/dist/browser-process/get-chrome-executable.d.ts +6 -0
- package/dist/browser-process/get-chrome-executable.d.ts.map +1 -1
- package/dist/browser-process/get-chrome-executable.js +79 -4
- package/dist/browser-process/get-chrome-executable.js.map +1 -1
- package/dist/browser-process/gpu-family-profiles.d.ts +58 -0
- package/dist/browser-process/gpu-family-profiles.d.ts.map +1 -0
- package/dist/browser-process/gpu-family-profiles.js +480 -0
- package/dist/browser-process/gpu-family-profiles.js.map +1 -0
- package/dist/browser-process/launch-options.d.ts +301 -0
- package/dist/browser-process/launch-options.d.ts.map +1 -0
- package/dist/browser-process/launch-options.js +2 -0
- package/dist/browser-process/launch-options.js.map +1 -0
- package/dist/browser-process/port-allocator.d.ts +64 -0
- package/dist/browser-process/port-allocator.d.ts.map +1 -0
- package/dist/browser-process/port-allocator.js +198 -0
- package/dist/browser-process/port-allocator.js.map +1 -0
- package/dist/browser-process/process.d.ts +85 -0
- package/dist/browser-process/process.d.ts.map +1 -1
- package/dist/browser-process/process.js +235 -19
- package/dist/browser-process/process.js.map +1 -1
- package/dist/browser-process/signal-cleanup.d.ts +40 -0
- package/dist/browser-process/signal-cleanup.d.ts.map +1 -0
- package/dist/browser-process/signal-cleanup.js +105 -0
- package/dist/browser-process/signal-cleanup.js.map +1 -0
- package/dist/browser-process/widevine-cdm.d.ts +144 -0
- package/dist/browser-process/widevine-cdm.d.ts.map +1 -0
- package/dist/browser-process/widevine-cdm.js +298 -0
- package/dist/browser-process/widevine-cdm.js.map +1 -0
- package/dist/browser-profiles/chrome/populate-profile.d.ts.map +1 -1
- package/dist/browser-profiles/chrome/populate-profile.js +21 -22
- package/dist/browser-profiles/chrome/populate-profile.js.map +1 -1
- package/dist/connection/cdp-reconnection.d.ts +50 -0
- package/dist/connection/cdp-reconnection.d.ts.map +1 -0
- package/dist/connection/cdp-reconnection.js +75 -0
- package/dist/connection/cdp-reconnection.js.map +1 -0
- package/dist/connection/page-session.d.ts +114 -0
- package/dist/connection/page-session.d.ts.map +1 -0
- package/dist/connection/page-session.js +237 -0
- package/dist/connection/page-session.js.map +1 -0
- package/dist/connection/session-client.d.ts +34 -0
- package/dist/connection/session-client.d.ts.map +1 -0
- package/dist/connection/session-client.js +129 -0
- package/dist/connection/session-client.js.map +1 -0
- package/dist/crawler.d.ts +9 -88
- package/dist/crawler.d.ts.map +1 -1
- package/dist/crawler.js +157 -8
- package/dist/crawler.js.map +1 -1
- package/dist/fingerprinting/fingerprint-injector.d.ts +24 -114
- package/dist/fingerprinting/fingerprint-injector.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-injector.js +305 -611
- package/dist/fingerprinting/fingerprint-injector.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.d.ts +1 -1
- package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.js +16 -14
- package/dist/fingerprinting/fingerprint-overrides/datadome-bypass.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/index.d.ts +6 -2
- package/dist/fingerprinting/fingerprint-overrides/index.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/index.js +6 -2
- package/dist/fingerprinting/fingerprint-overrides/index.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.d.ts +1 -1
- package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.js +4 -4
- package/dist/fingerprinting/fingerprint-overrides/keyboard-humanization.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/locale-spoofing.js +4 -4
- package/dist/fingerprinting/fingerprint-overrides/navigator-override.d.ts +18 -0
- package/dist/fingerprinting/fingerprint-overrides/navigator-override.d.ts.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/navigator-override.js +136 -0
- package/dist/fingerprinting/fingerprint-overrides/navigator-override.js.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/override.d.ts +151 -0
- package/dist/fingerprinting/fingerprint-overrides/override.d.ts.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/override.js +14 -0
- package/dist/fingerprinting/fingerprint-overrides/override.js.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/platform-consistency.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/platform-consistency.js +46 -15
- package/dist/fingerprinting/fingerprint-overrides/platform-consistency.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/registry.d.ts +43 -0
- package/dist/fingerprinting/fingerprint-overrides/registry.d.ts.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/registry.js +342 -0
- package/dist/fingerprinting/fingerprint-overrides/registry.js.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/screen-override.d.ts +16 -0
- package/dist/fingerprinting/fingerprint-overrides/screen-override.d.ts.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/screen-override.js +175 -0
- package/dist/fingerprinting/fingerprint-overrides/screen-override.js.map +1 -0
- package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.d.ts +1 -1
- package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.js +4 -4
- package/dist/fingerprinting/fingerprint-overrides/scroll-humanization.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/stealth-script.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/stealth-script.js +75 -9
- package/dist/fingerprinting/fingerprint-overrides/stealth-script.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/ua-ch.d.ts +1 -1
- package/dist/fingerprinting/fingerprint-overrides/ua-ch.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/ua-ch.js +34 -20
- package/dist/fingerprinting/fingerprint-overrides/ua-ch.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.d.ts.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.js +25 -6
- package/dist/fingerprinting/fingerprint-overrides/webgl-spoofing.js.map +1 -1
- package/dist/fingerprinting/fingerprint-overrides/webrtc-spoofing.js +1 -1
- package/dist/fingerprinting/hardware-realism.d.ts +83 -0
- package/dist/fingerprinting/hardware-realism.d.ts.map +1 -0
- package/dist/fingerprinting/hardware-realism.js +156 -0
- package/dist/fingerprinting/hardware-realism.js.map +1 -0
- package/dist/fingerprinting/injection-planner.d.ts +133 -0
- package/dist/fingerprinting/injection-planner.d.ts.map +1 -0
- package/dist/fingerprinting/injection-planner.js +497 -0
- package/dist/fingerprinting/injection-planner.js.map +1 -0
- package/dist/fingerprinting/locale-resolver.d.ts +8 -1
- package/dist/fingerprinting/locale-resolver.d.ts.map +1 -1
- package/dist/fingerprinting/locale-resolver.js +26 -3
- package/dist/fingerprinting/locale-resolver.js.map +1 -1
- package/dist/fingerprinting/non-apify-fingerprint-generator.d.ts +4 -4
- package/dist/fingerprinting/non-apify-fingerprint-generator.d.ts.map +1 -1
- package/dist/fingerprinting/non-apify-fingerprint-generator.js +24 -58
- package/dist/fingerprinting/non-apify-fingerprint-generator.js.map +1 -1
- package/dist/fingerprinting/non-apify-profiles.json +10515 -8494
- package/dist/fingerprinting/os-consistency.d.ts +28 -0
- package/dist/fingerprinting/os-consistency.d.ts.map +1 -0
- package/dist/fingerprinting/os-consistency.js +45 -0
- package/dist/fingerprinting/os-consistency.js.map +1 -0
- package/dist/fingerprinting/profile-quality.d.ts +25 -0
- package/dist/fingerprinting/profile-quality.d.ts.map +1 -0
- package/dist/fingerprinting/profile-quality.js +310 -0
- package/dist/fingerprinting/profile-quality.js.map +1 -0
- package/dist/fingerprinting/profile-selector.d.ts +102 -0
- package/dist/fingerprinting/profile-selector.d.ts.map +1 -0
- package/dist/fingerprinting/profile-selector.js +168 -0
- package/dist/fingerprinting/profile-selector.js.map +1 -0
- package/dist/fingerprinting/ua-alignment.d.ts +83 -0
- package/dist/fingerprinting/ua-alignment.d.ts.map +1 -0
- package/dist/fingerprinting/ua-alignment.js +197 -0
- package/dist/fingerprinting/ua-alignment.js.map +1 -0
- package/dist/fingerprinting/ua-ch-brands.d.ts +88 -0
- package/dist/fingerprinting/ua-ch-brands.d.ts.map +1 -0
- package/dist/fingerprinting/ua-ch-brands.js +174 -0
- package/dist/fingerprinting/ua-ch-brands.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/input/dropdown-selector.d.ts +90 -0
- package/dist/input/dropdown-selector.d.ts.map +1 -0
- package/dist/input/dropdown-selector.js +394 -0
- package/dist/input/dropdown-selector.js.map +1 -0
- package/dist/input/element-target.d.ts +152 -0
- package/dist/input/element-target.d.ts.map +1 -0
- package/dist/input/element-target.js +461 -0
- package/dist/input/element-target.js.map +1 -0
- package/dist/input/input-emulator.d.ts +85 -0
- package/dist/input/input-emulator.d.ts.map +1 -0
- package/dist/input/input-emulator.js +325 -0
- package/dist/input/input-emulator.js.map +1 -0
- package/dist/input/input-transport.d.ts +60 -0
- package/dist/input/input-transport.d.ts.map +1 -0
- package/dist/input/input-transport.js +28 -0
- package/dist/input/input-transport.js.map +1 -0
- package/dist/input/recording-transport.d.ts +32 -0
- package/dist/input/recording-transport.d.ts.map +1 -0
- package/dist/input/recording-transport.js +43 -0
- package/dist/input/recording-transport.js.map +1 -0
- package/dist/keyboard-layout.d.ts +2 -2
- package/dist/keyboard-layout.js +2 -2
- package/dist/navigation/page-navigation.d.ts +67 -0
- package/dist/navigation/page-navigation.d.ts.map +1 -0
- package/dist/navigation/page-navigation.js +122 -0
- package/dist/navigation/page-navigation.js.map +1 -0
- package/dist/network/network-watch.d.ts +110 -0
- package/dist/network/network-watch.d.ts.map +1 -0
- package/dist/network/network-watch.js +223 -0
- package/dist/network/network-watch.js.map +1 -0
- package/dist/page.d.ts +118 -104
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +311 -1130
- package/dist/page.js.map +1 -1
- package/dist/session/inject-hook.d.ts +24 -0
- package/dist/session/inject-hook.d.ts.map +1 -0
- package/dist/session/inject-hook.js +80 -0
- package/dist/session/inject-hook.js.map +1 -0
- package/dist/session/save.d.ts +21 -0
- package/dist/session/save.d.ts.map +1 -0
- package/dist/session/save.js +163 -0
- package/dist/session/save.js.map +1 -0
- package/dist/session/tar-data-dir.d.ts +18 -0
- package/dist/session/tar-data-dir.d.ts.map +1 -0
- package/dist/session/tar-data-dir.js +105 -0
- package/dist/session/tar-data-dir.js.map +1 -0
- package/dist/session/types.d.ts +80 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +29 -0
- package/dist/session/types.js.map +1 -0
- package/dist/session-profile.d.ts +102 -0
- package/dist/session-profile.d.ts.map +1 -0
- package/dist/session-profile.js +163 -0
- package/dist/session-profile.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +17 -6
- package/scripts/postinstall.cjs +58 -0
- package/dist/fingerprinting/custom-fingerprint-injector.d.ts +0 -87
- package/dist/fingerprinting/custom-fingerprint-injector.d.ts.map +0 -1
- package/dist/fingerprinting/custom-fingerprint-injector.js +0 -342
- package/dist/fingerprinting/custom-fingerprint-injector.js.map +0 -1
- package/dist/launcher-wrap.d.ts +0 -10
- package/dist/launcher-wrap.d.ts.map +0 -1
- package/dist/launcher-wrap.js +0 -11
- package/dist/launcher-wrap.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CdpReconnection - the single home for CDP connection retry machinery.
|
|
3
|
+
*
|
|
4
|
+
* Three call sites used to hand-roll their own loops (CdpPage.reconnectSession,
|
|
5
|
+
* CDPBrowserController.initialize, CDPBrowserController._attemptReconnect),
|
|
6
|
+
* each with its own backoff policy and its own fd hygiene - and the fd-leak
|
|
7
|
+
* fix (old client closed before swap) had landed in only one of them.
|
|
8
|
+
*
|
|
9
|
+
* The retry loop, backoff policies, and old-client disposal live here once. The
|
|
10
|
+
* BINDING strategies stay distinct at the call sites - they differ for
|
|
11
|
+
* documented anti-detect reasons (the browser socket reconnects over its ws
|
|
12
|
+
* endpoint; a page re-attaches a flat session and re-enables only previously-
|
|
13
|
+
* enabled domains).
|
|
14
|
+
*/
|
|
15
|
+
/** Sentinel an attempt returns to stop retrying without a result (e.g. "no page target"). */
|
|
16
|
+
export declare const STOP: unique symbol;
|
|
17
|
+
export interface RetryPolicy {
|
|
18
|
+
maxAttempts: number;
|
|
19
|
+
/** Delay before attempt `n` (1-based). Return 0 for no wait. */
|
|
20
|
+
delayBeforeMs: (attempt: number) => number;
|
|
21
|
+
/** 'throw' rethrows the last error on exhaustion; 'give-up' resolves undefined. */
|
|
22
|
+
onExhaustion: 'throw' | 'give-up';
|
|
23
|
+
}
|
|
24
|
+
/** Page re-bind policy: no wait before the first try, then 2^n s capped at 10 s. */
|
|
25
|
+
export declare const exponentialBackoff: (maxAttempts: number) => RetryPolicy;
|
|
26
|
+
/** Root-reconnect policy: fixed pause between attempts, give up silently. */
|
|
27
|
+
export declare const fixedBackoff: (maxAttempts: number, pauseMs: number) => RetryPolicy;
|
|
28
|
+
export interface RunRetriesOptions<T> {
|
|
29
|
+
/** One bind attempt (1-based attempt number). Return STOP to give up early. */
|
|
30
|
+
attempt: (attempt: number) => Promise<T | typeof STOP>;
|
|
31
|
+
policy: RetryPolicy;
|
|
32
|
+
/** Logged context label, e.g. 'page session' / 'browser root'. */
|
|
33
|
+
label: string;
|
|
34
|
+
onAttemptError?: (error: unknown, attempt: number) => void;
|
|
35
|
+
/** Injectable for tests. */
|
|
36
|
+
sleep?: (ms: number) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The one retry loop. Resolves with the bind result, or undefined when the
|
|
40
|
+
* attempt asked to STOP or a 'give-up' policy is exhausted.
|
|
41
|
+
*/
|
|
42
|
+
export declare function runWithRetries<T>(options: RunRetriesOptions<T>): Promise<T | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Old-client disposal: close the replaced client tolerantly. Without this the
|
|
45
|
+
* old websocket lingers until GC and leaks an fd per reconnect.
|
|
46
|
+
*/
|
|
47
|
+
export declare function disposeClient(client: {
|
|
48
|
+
close?: () => Promise<unknown>;
|
|
49
|
+
} | undefined): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=cdp-reconnection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp-reconnection.d.ts","sourceRoot":"","sources":["../../src/connection/cdp-reconnection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,6FAA6F;AAC7F,eAAO,MAAM,IAAI,eAAkC,CAAC;AAEpD,MAAM,WAAW,WAAW;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,mFAAmF;IACnF,YAAY,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED,oFAAoF;AACpF,eAAO,MAAM,kBAAkB,gBAAiB,MAAM,KAAG,WAIvD,CAAC;AAEH,6EAA6E;AAC7E,eAAO,MAAM,YAAY,gBAAiB,MAAM,WAAW,MAAM,KAAG,WAIlE,CAAC;AAEH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAChC,+EAA+E;IAC/E,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvD,MAAM,EAAE,WAAW,CAAC;IACpB,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,4BAA4B;IAC5B,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAMD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAyB7F;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAMzG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CdpReconnection - the single home for CDP connection retry machinery.
|
|
3
|
+
*
|
|
4
|
+
* Three call sites used to hand-roll their own loops (CdpPage.reconnectSession,
|
|
5
|
+
* CDPBrowserController.initialize, CDPBrowserController._attemptReconnect),
|
|
6
|
+
* each with its own backoff policy and its own fd hygiene - and the fd-leak
|
|
7
|
+
* fix (old client closed before swap) had landed in only one of them.
|
|
8
|
+
*
|
|
9
|
+
* The retry loop, backoff policies, and old-client disposal live here once. The
|
|
10
|
+
* BINDING strategies stay distinct at the call sites - they differ for
|
|
11
|
+
* documented anti-detect reasons (the browser socket reconnects over its ws
|
|
12
|
+
* endpoint; a page re-attaches a flat session and re-enables only previously-
|
|
13
|
+
* enabled domains).
|
|
14
|
+
*/
|
|
15
|
+
import { log } from 'crawlee';
|
|
16
|
+
/** Sentinel an attempt returns to stop retrying without a result (e.g. "no page target"). */
|
|
17
|
+
export const STOP = Symbol('cdp-reconnection-stop');
|
|
18
|
+
/** Page re-bind policy: no wait before the first try, then 2^n s capped at 10 s. */
|
|
19
|
+
export const exponentialBackoff = (maxAttempts) => ({
|
|
20
|
+
maxAttempts,
|
|
21
|
+
delayBeforeMs: (attempt) => (attempt === 1 ? 0 : Math.min(1000 * 2 ** (attempt - 1), 10000)),
|
|
22
|
+
onExhaustion: 'give-up',
|
|
23
|
+
});
|
|
24
|
+
/** Root-reconnect policy: fixed pause between attempts, give up silently. */
|
|
25
|
+
export const fixedBackoff = (maxAttempts, pauseMs) => ({
|
|
26
|
+
maxAttempts,
|
|
27
|
+
delayBeforeMs: (attempt) => (attempt === 1 ? 0 : pauseMs),
|
|
28
|
+
onExhaustion: 'give-up',
|
|
29
|
+
});
|
|
30
|
+
const defaultSleep = async (ms) => new Promise((resolve) => {
|
|
31
|
+
setTimeout(resolve, ms);
|
|
32
|
+
});
|
|
33
|
+
/**
|
|
34
|
+
* The one retry loop. Resolves with the bind result, or undefined when the
|
|
35
|
+
* attempt asked to STOP or a 'give-up' policy is exhausted.
|
|
36
|
+
*/
|
|
37
|
+
export async function runWithRetries(options) {
|
|
38
|
+
const { attempt, policy, label } = options;
|
|
39
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
40
|
+
let lastError;
|
|
41
|
+
for (let n = 1; n <= policy.maxAttempts; n++) {
|
|
42
|
+
const delay = policy.delayBeforeMs(n);
|
|
43
|
+
if (delay > 0) {
|
|
44
|
+
log.debug(`Waiting ${delay}ms before ${label} attempt ${n}/${policy.maxAttempts}`);
|
|
45
|
+
await sleep(delay);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = await attempt(n);
|
|
49
|
+
if (result === STOP)
|
|
50
|
+
return undefined;
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
(options.onAttemptError ?? ((err, at) => log.error(`Failed ${label} attempt ${at}/${policy.maxAttempts}`, { error: err })))(error, n);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (policy.onExhaustion === 'throw')
|
|
59
|
+
throw lastError;
|
|
60
|
+
log.debug(`${label}: max retries reached, giving up`);
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Old-client disposal: close the replaced client tolerantly. Without this the
|
|
65
|
+
* old websocket lingers until GC and leaks an fd per reconnect.
|
|
66
|
+
*/
|
|
67
|
+
export async function disposeClient(client) {
|
|
68
|
+
try {
|
|
69
|
+
await client?.close?.();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// tolerate already-closed
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=cdp-reconnection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdp-reconnection.js","sourceRoot":"","sources":["../../src/connection/cdp-reconnection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PageSession - the live CDP session for one page, flatten-mode transport.
|
|
3
|
+
*
|
|
4
|
+
* A page is a CDP *session* (a `sessionId`) multiplexed over the single browser
|
|
5
|
+
* websocket (ADR 0003), not its own socket. `CdpPage` holds the session-scoped
|
|
6
|
+
* {@link createSessionClient} proxy and routes every CDP-touching method through
|
|
7
|
+
* `run(op)`; consumers read the current client through `client()`.
|
|
8
|
+
*
|
|
9
|
+
* PageSession owns the page-level connection-liveness *policy*: the reconnect +
|
|
10
|
+
* `isReconnecting` latch, and re-enabling the previously-enabled domains after a
|
|
11
|
+
* re-attach. It sits on top of the already-tested CdpReconnection *mechanism*
|
|
12
|
+
* (`runWithRetries`); the backoff loop is not re-implemented here. The keep-alive
|
|
13
|
+
* heartbeat is NOT here - with one shared browser socket a single controller-owned
|
|
14
|
+
* ping loop covers every page, instead of N loops + N ws 'error' listeners.
|
|
15
|
+
*
|
|
16
|
+
* Reconnect is a **re-attach**, not a new socket: the injected `reattach()` opens
|
|
17
|
+
* a fresh session on the (possibly freshly reconnected) browser client and points
|
|
18
|
+
* the proxy's sessionId at it. The proxy object is stable - there is no client
|
|
19
|
+
* swap and therefore no fd to dispose.
|
|
20
|
+
*
|
|
21
|
+
* Preserved observable behaviour:
|
|
22
|
+
* - reconnect is skipped while one is already in progress or the page is closed,
|
|
23
|
+
* - a gone page target stops retrying (the STOP sentinel),
|
|
24
|
+
* - only the domains that were previously enabled are re-enabled after re-attach,
|
|
25
|
+
* - there is NO eager Network.enable/Page.enable on any path
|
|
26
|
+
* (an anti-detect requirement - domains are enabled lazily by the caller).
|
|
27
|
+
*/
|
|
28
|
+
import type CDP from 'chrome-remote-interface';
|
|
29
|
+
export interface PageSessionOptions {
|
|
30
|
+
/** The page's session-scoped CDP client (the {@link createSessionClient} proxy). */
|
|
31
|
+
initialClient: CDP.Client;
|
|
32
|
+
/**
|
|
33
|
+
* Re-establish this page's session on the current browser client: attach a
|
|
34
|
+
* fresh flat session to the page target and point the proxy's sessionId at
|
|
35
|
+
* it. Throws on a transport error (retried) or when the target is gone
|
|
36
|
+
* (classified as STOP via {@link isTargetGone}). Omitted in unit tests that
|
|
37
|
+
* don't exercise reconnect.
|
|
38
|
+
*/
|
|
39
|
+
reattach?: () => Promise<void>;
|
|
40
|
+
/** Injectable delay between reconnect attempts (tests pass a no-op). */
|
|
41
|
+
sleep?: (ms: number) => Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** Recognises the transport-level errors that a reconnect can recover from. */
|
|
44
|
+
export declare function isConnectionError(error: unknown): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Recognises "this sessionId is no longer valid" - the shared browser socket was
|
|
47
|
+
* reconnected under us and every page's old session died with it, but the target
|
|
48
|
+
* itself still exists. The socket now reads OPEN, so this is NOT a connection
|
|
49
|
+
* error; it is nonetheless recoverable by re-attaching a fresh flat session.
|
|
50
|
+
*
|
|
51
|
+
* Matches Chrome's actual "session is gone" messages ONLY. A bare `sessionId`
|
|
52
|
+
* alternative used to be included, which also matched ordinary parameter
|
|
53
|
+
* validation errors ("Invalid parameters: sessionId: string value expected") -
|
|
54
|
+
* those are permanent caller bugs, so `run()` burned a pointless re-attach and
|
|
55
|
+
* retried an operation guaranteed to fail again.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isSessionStale(error: unknown): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Recognises "the page target no longer exists" - a re-attach can never succeed,
|
|
60
|
+
* so stop. This is strictly the *target* being gone (tab closed / crashed); a
|
|
61
|
+
* merely-stale session (see {@link isSessionStale}) is recoverable and must NOT
|
|
62
|
+
* match here, or a browser-socket reconnect would wrongly abandon live pages.
|
|
63
|
+
*/
|
|
64
|
+
export declare function isTargetGone(error: unknown): boolean;
|
|
65
|
+
export declare class PageSession {
|
|
66
|
+
private readonly rawClient;
|
|
67
|
+
private readonly reattachFn?;
|
|
68
|
+
private readonly sleep?;
|
|
69
|
+
/** True while a reconnect is in flight - the latch the call sites coordinate on. */
|
|
70
|
+
isReconnecting: boolean;
|
|
71
|
+
private closed;
|
|
72
|
+
/**
|
|
73
|
+
* CDP domains the caller has lazily enabled on this session. PageSession
|
|
74
|
+
* tracks them ONLY so a re-attach re-enables exactly this set on the fresh
|
|
75
|
+
* session - it never enables a domain on its own.
|
|
76
|
+
*/
|
|
77
|
+
private readonly enabledDomains;
|
|
78
|
+
constructor(options: PageSessionOptions);
|
|
79
|
+
/** The current CDP client - the stable session proxy (auto-follows re-attach). */
|
|
80
|
+
client(): CDP.Client;
|
|
81
|
+
/** Record that the page is closed so healing stops. */
|
|
82
|
+
markClosed(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Enable a CDP domain at most once, tracking it for re-enable on re-attach.
|
|
85
|
+
* No-op if already enabled or the client has no such domain.
|
|
86
|
+
*/
|
|
87
|
+
enableDomain(domain: string): Promise<void>;
|
|
88
|
+
/** True when the underlying (shared) CDP WebSocket is open and usable. */
|
|
89
|
+
private wsOpen;
|
|
90
|
+
/**
|
|
91
|
+
* Run a CDP-touching operation with transparent reconnect: pre-heal a
|
|
92
|
+
* dropped socket, and if the call still fails with a transport error,
|
|
93
|
+
* reconnect once and retry. Operations on an intentionally-closed page
|
|
94
|
+
* resolve to `undefined` (callers treat a closed page as a silent no-op).
|
|
95
|
+
*/
|
|
96
|
+
run<T>(op: () => Promise<T>): Promise<T | undefined>;
|
|
97
|
+
/**
|
|
98
|
+
* Heal the CDP session before an operation if the shared socket has dropped.
|
|
99
|
+
* No-op once the page is intentionally closed or while another reconnect is
|
|
100
|
+
* already running.
|
|
101
|
+
*/
|
|
102
|
+
private ensureConnected;
|
|
103
|
+
/**
|
|
104
|
+
* Re-bind the page session by re-attaching a fresh flat session to the page
|
|
105
|
+
* target (via the injected `reattach`) and re-enabling only the
|
|
106
|
+
* previously-enabled domains. Skipped while another reconnect is in progress
|
|
107
|
+
* or the page is closed. A gone target stops retrying.
|
|
108
|
+
*
|
|
109
|
+
* The retry/backoff machinery lives in CdpReconnection - not re-implemented
|
|
110
|
+
* here. There is no client swap and no probe socket in flatten mode.
|
|
111
|
+
*/
|
|
112
|
+
reconnect(retries?: number): Promise<void>;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=page-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-session.d.ts","sourceRoot":"","sources":["../../src/connection/page-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAK/C,MAAM,WAAW,kBAAkB;IAC/B,oFAAoF;IACpF,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,wEAAwE;IACxE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGpD;AAED,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAgC;IAEvD,oFAAoF;IACpF,cAAc,UAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IAEvB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;gBAExC,OAAO,EAAE,kBAAkB;IAMvC,kFAAkF;IAClF,MAAM,IAAI,GAAG,CAAC,MAAM;IAIpB,uDAAuD;IACvD,UAAU,IAAI,IAAI;IAIlB;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjD,0EAA0E;IAC1E,OAAO,CAAC,MAAM;IAKd;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IA0B1D;;;;OAIG;YACW,eAAe;IAQ7B;;;;;;;;OAQG;IACG,SAAS,CAAC,OAAO,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAgD9C"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PageSession - the live CDP session for one page, flatten-mode transport.
|
|
3
|
+
*
|
|
4
|
+
* A page is a CDP *session* (a `sessionId`) multiplexed over the single browser
|
|
5
|
+
* websocket (ADR 0003), not its own socket. `CdpPage` holds the session-scoped
|
|
6
|
+
* {@link createSessionClient} proxy and routes every CDP-touching method through
|
|
7
|
+
* `run(op)`; consumers read the current client through `client()`.
|
|
8
|
+
*
|
|
9
|
+
* PageSession owns the page-level connection-liveness *policy*: the reconnect +
|
|
10
|
+
* `isReconnecting` latch, and re-enabling the previously-enabled domains after a
|
|
11
|
+
* re-attach. It sits on top of the already-tested CdpReconnection *mechanism*
|
|
12
|
+
* (`runWithRetries`); the backoff loop is not re-implemented here. The keep-alive
|
|
13
|
+
* heartbeat is NOT here - with one shared browser socket a single controller-owned
|
|
14
|
+
* ping loop covers every page, instead of N loops + N ws 'error' listeners.
|
|
15
|
+
*
|
|
16
|
+
* Reconnect is a **re-attach**, not a new socket: the injected `reattach()` opens
|
|
17
|
+
* a fresh session on the (possibly freshly reconnected) browser client and points
|
|
18
|
+
* the proxy's sessionId at it. The proxy object is stable - there is no client
|
|
19
|
+
* swap and therefore no fd to dispose.
|
|
20
|
+
*
|
|
21
|
+
* Preserved observable behaviour:
|
|
22
|
+
* - reconnect is skipped while one is already in progress or the page is closed,
|
|
23
|
+
* - a gone page target stops retrying (the STOP sentinel),
|
|
24
|
+
* - only the domains that were previously enabled are re-enabled after re-attach,
|
|
25
|
+
* - there is NO eager Network.enable/Page.enable on any path
|
|
26
|
+
* (an anti-detect requirement - domains are enabled lazily by the caller).
|
|
27
|
+
*/
|
|
28
|
+
import { log } from 'crawlee';
|
|
29
|
+
import { runWithRetries, exponentialBackoff, STOP } from './cdp-reconnection.js';
|
|
30
|
+
/** Recognises the transport-level errors that a reconnect can recover from. */
|
|
31
|
+
export function isConnectionError(error) {
|
|
32
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
33
|
+
return /WebSocket is not open|WebSocket connection closed|connection closed|readyState/i.test(msg);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Recognises "this sessionId is no longer valid" - the shared browser socket was
|
|
37
|
+
* reconnected under us and every page's old session died with it, but the target
|
|
38
|
+
* itself still exists. The socket now reads OPEN, so this is NOT a connection
|
|
39
|
+
* error; it is nonetheless recoverable by re-attaching a fresh flat session.
|
|
40
|
+
*
|
|
41
|
+
* Matches Chrome's actual "session is gone" messages ONLY. A bare `sessionId`
|
|
42
|
+
* alternative used to be included, which also matched ordinary parameter
|
|
43
|
+
* validation errors ("Invalid parameters: sessionId: string value expected") -
|
|
44
|
+
* those are permanent caller bugs, so `run()` burned a pointless re-attach and
|
|
45
|
+
* retried an operation guaranteed to fail again.
|
|
46
|
+
*/
|
|
47
|
+
export function isSessionStale(error) {
|
|
48
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
49
|
+
return /No session with given id|Session with given id not found|Session not found|sessionId (?:is )?(?:no longer valid|not found)/i.test(msg);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Recognises "the page target no longer exists" - a re-attach can never succeed,
|
|
53
|
+
* so stop. This is strictly the *target* being gone (tab closed / crashed); a
|
|
54
|
+
* merely-stale session (see {@link isSessionStale}) is recoverable and must NOT
|
|
55
|
+
* match here, or a browser-socket reconnect would wrongly abandon live pages.
|
|
56
|
+
*/
|
|
57
|
+
export function isTargetGone(error) {
|
|
58
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return /No target with given id|Target closed|target closed/i.test(msg);
|
|
60
|
+
}
|
|
61
|
+
export class PageSession {
|
|
62
|
+
constructor(options) {
|
|
63
|
+
Object.defineProperty(this, "rawClient", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
value: void 0
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(this, "reattachFn", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
75
|
+
Object.defineProperty(this, "sleep", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
value: void 0
|
|
80
|
+
});
|
|
81
|
+
/** True while a reconnect is in flight - the latch the call sites coordinate on. */
|
|
82
|
+
Object.defineProperty(this, "isReconnecting", {
|
|
83
|
+
enumerable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
writable: true,
|
|
86
|
+
value: false
|
|
87
|
+
});
|
|
88
|
+
Object.defineProperty(this, "closed", {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
writable: true,
|
|
92
|
+
value: false
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* CDP domains the caller has lazily enabled on this session. PageSession
|
|
96
|
+
* tracks them ONLY so a re-attach re-enables exactly this set on the fresh
|
|
97
|
+
* session - it never enables a domain on its own.
|
|
98
|
+
*/
|
|
99
|
+
Object.defineProperty(this, "enabledDomains", {
|
|
100
|
+
enumerable: true,
|
|
101
|
+
configurable: true,
|
|
102
|
+
writable: true,
|
|
103
|
+
value: new Set()
|
|
104
|
+
});
|
|
105
|
+
this.rawClient = options.initialClient;
|
|
106
|
+
this.reattachFn = options.reattach;
|
|
107
|
+
this.sleep = options.sleep;
|
|
108
|
+
}
|
|
109
|
+
/** The current CDP client - the stable session proxy (auto-follows re-attach). */
|
|
110
|
+
client() {
|
|
111
|
+
return this.rawClient;
|
|
112
|
+
}
|
|
113
|
+
/** Record that the page is closed so healing stops. */
|
|
114
|
+
markClosed() {
|
|
115
|
+
this.closed = true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Enable a CDP domain at most once, tracking it for re-enable on re-attach.
|
|
119
|
+
* No-op if already enabled or the client has no such domain.
|
|
120
|
+
*/
|
|
121
|
+
async enableDomain(domain) {
|
|
122
|
+
if (this.enabledDomains.has(domain))
|
|
123
|
+
return;
|
|
124
|
+
const api = this.rawClient[domain];
|
|
125
|
+
if (api?.enable) {
|
|
126
|
+
await api.enable();
|
|
127
|
+
this.enabledDomains.add(domain);
|
|
128
|
+
log.debug(`Enabled ${domain} domain`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** True when the underlying (shared) CDP WebSocket is open and usable. */
|
|
132
|
+
wsOpen() {
|
|
133
|
+
const ws = this.rawClient?._ws;
|
|
134
|
+
return !!ws && ws.readyState === ws.OPEN;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run a CDP-touching operation with transparent reconnect: pre-heal a
|
|
138
|
+
* dropped socket, and if the call still fails with a transport error,
|
|
139
|
+
* reconnect once and retry. Operations on an intentionally-closed page
|
|
140
|
+
* resolve to `undefined` (callers treat a closed page as a silent no-op).
|
|
141
|
+
*/
|
|
142
|
+
async run(op) {
|
|
143
|
+
if (this.closed) {
|
|
144
|
+
log.debug('Operation skipped - page is closed');
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
await this.ensureConnected();
|
|
148
|
+
try {
|
|
149
|
+
return await op();
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
// A dropped socket (isConnectionError) OR a stale session left behind
|
|
153
|
+
// by a browser-socket reconnect (isSessionStale) is both healed by a
|
|
154
|
+
// re-attach + one retry. A stale session does NOT trip isConnectionError
|
|
155
|
+
// (the new socket reads OPEN), so it must be classified explicitly or
|
|
156
|
+
// the page would be permanently broken after a browser reconnect.
|
|
157
|
+
if ((isConnectionError(error) || isSessionStale(error)) && !this.closed) {
|
|
158
|
+
log.info('Recoverable CDP error during operation, re-attaching and retrying once', {
|
|
159
|
+
error: error.message,
|
|
160
|
+
});
|
|
161
|
+
await this.reconnect();
|
|
162
|
+
if (this.closed)
|
|
163
|
+
return undefined;
|
|
164
|
+
return await op();
|
|
165
|
+
}
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Heal the CDP session before an operation if the shared socket has dropped.
|
|
171
|
+
* No-op once the page is intentionally closed or while another reconnect is
|
|
172
|
+
* already running.
|
|
173
|
+
*/
|
|
174
|
+
async ensureConnected() {
|
|
175
|
+
if (this.closed || this.isReconnecting)
|
|
176
|
+
return;
|
|
177
|
+
if (!this.wsOpen()) {
|
|
178
|
+
log.debug('CDP socket not open before operation, re-attaching…');
|
|
179
|
+
await this.reconnect();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Re-bind the page session by re-attaching a fresh flat session to the page
|
|
184
|
+
* target (via the injected `reattach`) and re-enabling only the
|
|
185
|
+
* previously-enabled domains. Skipped while another reconnect is in progress
|
|
186
|
+
* or the page is closed. A gone target stops retrying.
|
|
187
|
+
*
|
|
188
|
+
* The retry/backoff machinery lives in CdpReconnection - not re-implemented
|
|
189
|
+
* here. There is no client swap and no probe socket in flatten mode.
|
|
190
|
+
*/
|
|
191
|
+
async reconnect(retries = 3) {
|
|
192
|
+
if (this.isReconnecting) {
|
|
193
|
+
log.debug('Reconnection skipped - already in progress');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (this.closed) {
|
|
197
|
+
log.debug('Reconnection skipped - page is closed');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!this.reattachFn) {
|
|
201
|
+
log.debug('Reconnection skipped - no reattach configured');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
this.isReconnecting = true;
|
|
205
|
+
try {
|
|
206
|
+
await runWithRetries({
|
|
207
|
+
label: 'page session re-attach',
|
|
208
|
+
policy: exponentialBackoff(retries),
|
|
209
|
+
sleep: this.sleep,
|
|
210
|
+
attempt: async (attempt) => {
|
|
211
|
+
try {
|
|
212
|
+
await this.reattachFn();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (isTargetGone(error)) {
|
|
216
|
+
log.debug('Page target gone, stopping re-attach');
|
|
217
|
+
return STOP;
|
|
218
|
+
}
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
// Re-enable only the domains that were previously enabled, on
|
|
222
|
+
// the fresh session (the proxy now points at the new sessionId).
|
|
223
|
+
await Promise.all(Array.from(this.enabledDomains).map(async (domain) => this.rawClient[domain].enable()));
|
|
224
|
+
log.info(`Page session re-attached successfully on attempt ${attempt}`);
|
|
225
|
+
return true;
|
|
226
|
+
},
|
|
227
|
+
onAttemptError: (error) => {
|
|
228
|
+
log.error('Failed to re-attach page session', { error });
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
this.isReconnecting = false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=page-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-session.js","sourceRoot":"","sources":["../../src/connection/page-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionClient - a session-scoped view of the single browser CDP client.
|
|
3
|
+
*
|
|
4
|
+
* In flatten-mode transport (ADR 0003) there is ONE websocket to the browser
|
|
5
|
+
* target; every page is a CDP *session* multiplexed over it, addressed by a
|
|
6
|
+
* `sessionId`. This proxy presents the familiar `CDP.Client` shape to `CdpPage`
|
|
7
|
+
* and its sub-modules while injecting the page's `sessionId` into every command
|
|
8
|
+
* and event subscription. That keeps the ~10 `this.client.Domain.method()` call
|
|
9
|
+
* sites in `page.ts` (and the input transport / injector / inject-hook) exactly
|
|
10
|
+
* as written - the proxy is the single seam where sessionId enters.
|
|
11
|
+
*
|
|
12
|
+
* How `chrome-remote-interface` (0.33.2) exposes things, which this mirrors:
|
|
13
|
+
* - a command is `Domain.method(params, sessionId, callback)` (decorated
|
|
14
|
+
* `.category === 'command'`); we bind arg 2 to the current sessionId.
|
|
15
|
+
* - an event is `Domain.event(sessionId, handler)` (`.category === 'event'`);
|
|
16
|
+
* we bind arg 1 to the current sessionId. It returns an unsubscribe fn.
|
|
17
|
+
* - the connection also emits every session event under `${Domain.event}.${sessionId}`
|
|
18
|
+
* (chrome.js), so raw `client.on('Domain.event', h)` must be rewritten to the
|
|
19
|
+
* session-scoped name or it would fire for *every* page's events.
|
|
20
|
+
*
|
|
21
|
+
* `browser()` and `sessionId()` are read at call time, so a browser-socket
|
|
22
|
+
* reconnect (new underlying client) or a session re-attach (new sessionId) is
|
|
23
|
+
* picked up transparently - the proxy object itself is stable and never needs
|
|
24
|
+
* to be recreated.
|
|
25
|
+
*/
|
|
26
|
+
import type CDP from 'chrome-remote-interface';
|
|
27
|
+
export interface SessionClientDeps {
|
|
28
|
+
/** The current shared browser-level CDP client (re-read each call for reconnect safety). */
|
|
29
|
+
browser: () => CDP.Client;
|
|
30
|
+
/** The current sessionId for this page (re-read each call; changes on re-attach). */
|
|
31
|
+
sessionId: () => string;
|
|
32
|
+
}
|
|
33
|
+
export declare function createSessionClient(deps: SessionClientDeps): CDP.Client;
|
|
34
|
+
//# sourceMappingURL=session-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-client.d.ts","sourceRoot":"","sources":["../../src/connection/session-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAC9B,4FAA4F;IAC5F,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,CAAC;IAC1B,qFAAqF;IACrF,SAAS,EAAE,MAAM,MAAM,CAAC;CAC3B;AAuBD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,GAAG,CAAC,MAAM,CAmGvE"}
|