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
|
@@ -12,11 +12,96 @@ export declare class BrowserProcess implements Process {
|
|
|
12
12
|
private userDataDir;
|
|
13
13
|
private shouldCleanupUserDataDir;
|
|
14
14
|
private isKilling;
|
|
15
|
+
/**
|
|
16
|
+
* Temp fingerprint-config file(s) written for this launch (the
|
|
17
|
+
* --blink-platform-overrides-file JSON). Tracked here so both the normal
|
|
18
|
+
* exit path and the SIGINT/SIGTERM signal handler can remove them.
|
|
19
|
+
*/
|
|
20
|
+
private fingerprintConfigPaths;
|
|
21
|
+
/**
|
|
22
|
+
* Browser-level CDP WebSocket endpoint, parsed from the "DevTools listening
|
|
23
|
+
* on ws://…" line Chromium prints to stderr once its remote-debugging server
|
|
24
|
+
* is accepting connections. The launcher (browser.ts) drives this: it calls
|
|
25
|
+
* {@link resolveWsEndpoint} on the match and {@link rejectWsEndpoint} if the
|
|
26
|
+
* process dies first. Consumers await {@link wsEndpoint} instead of polling
|
|
27
|
+
* the TCP port - Puppeteer-style readiness. Settled exactly once.
|
|
28
|
+
*/
|
|
29
|
+
private wsEndpointResolve;
|
|
30
|
+
private wsEndpointReject;
|
|
31
|
+
private wsEndpointSettled;
|
|
32
|
+
private readonly wsEndpointPromise;
|
|
15
33
|
constructor(browserProcess: ChildProcess, userDataDir: string, shouldCleanupUserDataDir?: boolean);
|
|
34
|
+
/**
|
|
35
|
+
* The browser-level CDP WebSocket endpoint, resolved once the launcher parses
|
|
36
|
+
* the "DevTools listening on ws://…" line from Chromium's stderr, or rejected
|
|
37
|
+
* if the process exits/errors first. Awaited by the plugin's connect path in
|
|
38
|
+
* place of the former TCP port poll + fixed pre-connect delay.
|
|
39
|
+
*/
|
|
40
|
+
wsEndpoint(): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* Called by the launcher when it matches the DevTools ws line on stderr.
|
|
43
|
+
* Idempotent - the first settlement (resolve or reject) wins.
|
|
44
|
+
*/
|
|
45
|
+
resolveWsEndpoint(wsUrl: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Called by the launcher's process exit/error handlers when the process dies
|
|
48
|
+
* before the ws line appears. The error should carry the buffered stderr
|
|
49
|
+
* tail so a genuinely dead binary fails fast with an actionable message.
|
|
50
|
+
* Idempotent - a no-op once the endpoint has already settled.
|
|
51
|
+
*/
|
|
52
|
+
rejectWsEndpoint(error: Error): void;
|
|
53
|
+
/**
|
|
54
|
+
* Record a temp fingerprint-config file that must be removed when this
|
|
55
|
+
* browser exits (or on signal). Called by the launcher after it writes the
|
|
56
|
+
* --blink-platform-overrides-file JSON.
|
|
57
|
+
*/
|
|
58
|
+
trackFingerprintConfigPath(configPath: string): void;
|
|
59
|
+
/** Absolute path of the profile directory this browser launched with. */
|
|
60
|
+
getUserDataDir(): string;
|
|
61
|
+
/** Temp fingerprint-config files tracked for this launch (read-only copy). */
|
|
62
|
+
getFingerprintConfigPaths(): string[];
|
|
63
|
+
/**
|
|
64
|
+
* Signal the browser's whole process group, not just the parent PID.
|
|
65
|
+
*
|
|
66
|
+
* Chromium spawns helper processes (renderer / GPU / utility) that - unless
|
|
67
|
+
* the group is signalled - survive a SIGTERM/SIGKILL aimed only at the
|
|
68
|
+
* parent and reparent to launchd/init, leaking memory and (on macOS)
|
|
69
|
+
* exhausting IOSurface kernel clients until the GPU process aborts.
|
|
70
|
+
*
|
|
71
|
+
* Because the browser is spawned with `detached: true`, the parent is its
|
|
72
|
+
* own process-group leader (pgid === pid) and the helpers inherit that
|
|
73
|
+
* group, so `process.kill(-pid, signal)` reaps the entire tree in one shot.
|
|
74
|
+
* Falls back to signalling the parent alone if the group send throws
|
|
75
|
+
* (ESRCH = already gone, EPERM, or a platform where `detached` didn't take).
|
|
76
|
+
*
|
|
77
|
+
* NOTE the `pid > 1` guard: a failed spawn leaves `pid === -1`, and
|
|
78
|
+
* `process.kill(-(-1))` === `process.kill(1)` would signal init - never do
|
|
79
|
+
* that.
|
|
80
|
+
*/
|
|
81
|
+
private signalGroup;
|
|
16
82
|
close(): Promise<void>;
|
|
17
83
|
kill(): Promise<void>;
|
|
18
84
|
signal(signal?: NodeJS.Signals): void;
|
|
85
|
+
/**
|
|
86
|
+
* Synchronous teardown for the SIGINT/SIGTERM handler. A signal handler
|
|
87
|
+
* cannot await Promises - once it returns, Node re-raises the (now default)
|
|
88
|
+
* signal and exits, so any pending async fs work is dropped. This does the
|
|
89
|
+
* minimum synchronously: SIGKILL the browser (no graceful wait - we are
|
|
90
|
+
* being interrupted) and remove the temp profile dir + fingerprint config
|
|
91
|
+
* with the *Sync fs calls. Idempotent and never throws.
|
|
92
|
+
*/
|
|
93
|
+
killAndCleanupSync(): void;
|
|
19
94
|
private cleanup;
|
|
95
|
+
/** Remove every tracked temp fingerprint-config file (async path). */
|
|
96
|
+
private cleanupFingerprintConfigs;
|
|
97
|
+
/**
|
|
98
|
+
* Synchronous variant of cleanup() for the signal handler. Removes the temp
|
|
99
|
+
* fingerprint configs and the temp profile dir (honoring
|
|
100
|
+
* shouldCleanupUserDataDir) using blocking fs calls. Never throws.
|
|
101
|
+
*/
|
|
102
|
+
private cleanupSync;
|
|
103
|
+
/** Blocking recursive delete used by the signal handler. Never throws. */
|
|
104
|
+
private forceDeleteDirectorySync;
|
|
20
105
|
/**
|
|
21
106
|
* Aggressively force delete the browser profile directory
|
|
22
107
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/browser-process/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/browser-process/process.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AASlD,MAAM,WAAW,OAAO;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;CACzC;AAED,qBAAa,cAAe,YAAW,OAAO;IAC1C,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,OAAO,CAAC,cAAc,CAAe;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,wBAAwB,CAAU;IAC1C,OAAO,CAAC,SAAS,CAAS;IAC1B;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAgB;IAE9C;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAG/B;gBAES,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,wBAAwB,UAAO;IAgC9F;;;;;OAKG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAInC;;;OAGG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAMtC;;;;;OAKG;IACH,gBAAgB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAMpC;;;;OAIG;IACH,0BAA0B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAMpD,yEAAyE;IACzE,cAAc,IAAI,MAAM;IAIxB,8EAA8E;IAC9E,yBAAyB,IAAI,MAAM,EAAE;IAIrC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,WAAW;IAgBb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B,MAAM,CAAC,MAAM,GAAE,MAAM,CAAC,OAAmB,GAAG,IAAI;IAKhD;;;;;;;OAOG;IACH,kBAAkB,IAAI,IAAI;YAOZ,OAAO;IAgBrB,sEAAsE;YACxD,yBAAyB;IAUvC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IA2BnB,0EAA0E;IAC1E,OAAO,CAAC,wBAAwB;IAShC;;OAEG;YACW,oBAAoB;IAmClC;;OAEG;YACW,uBAAuB;CAsBxC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import fsSync from 'fs';
|
|
1
2
|
import fs from 'fs/promises';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { log } from '@crawlee/core';
|
|
5
|
+
import { registerLiveBrowserProcess, unregisterLiveBrowserProcess } from './signal-cleanup.js';
|
|
4
6
|
export class BrowserProcess {
|
|
5
7
|
constructor(browserProcess, userDataDir, shouldCleanupUserDataDir = true) {
|
|
6
8
|
Object.defineProperty(this, "pid", {
|
|
@@ -39,62 +41,223 @@ export class BrowserProcess {
|
|
|
39
41
|
writable: true,
|
|
40
42
|
value: false
|
|
41
43
|
});
|
|
44
|
+
/**
|
|
45
|
+
* Temp fingerprint-config file(s) written for this launch (the
|
|
46
|
+
* --blink-platform-overrides-file JSON). Tracked here so both the normal
|
|
47
|
+
* exit path and the SIGINT/SIGTERM signal handler can remove them.
|
|
48
|
+
*/
|
|
49
|
+
Object.defineProperty(this, "fingerprintConfigPaths", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: []
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Browser-level CDP WebSocket endpoint, parsed from the "DevTools listening
|
|
57
|
+
* on ws://…" line Chromium prints to stderr once its remote-debugging server
|
|
58
|
+
* is accepting connections. The launcher (browser.ts) drives this: it calls
|
|
59
|
+
* {@link resolveWsEndpoint} on the match and {@link rejectWsEndpoint} if the
|
|
60
|
+
* process dies first. Consumers await {@link wsEndpoint} instead of polling
|
|
61
|
+
* the TCP port - Puppeteer-style readiness. Settled exactly once.
|
|
62
|
+
*/
|
|
63
|
+
Object.defineProperty(this, "wsEndpointResolve", {
|
|
64
|
+
enumerable: true,
|
|
65
|
+
configurable: true,
|
|
66
|
+
writable: true,
|
|
67
|
+
value: void 0
|
|
68
|
+
});
|
|
69
|
+
Object.defineProperty(this, "wsEndpointReject", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
75
|
+
Object.defineProperty(this, "wsEndpointSettled", {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
writable: true,
|
|
79
|
+
value: false
|
|
80
|
+
});
|
|
81
|
+
Object.defineProperty(this, "wsEndpointPromise", {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
configurable: true,
|
|
84
|
+
writable: true,
|
|
85
|
+
value: new Promise((resolve, reject) => {
|
|
86
|
+
this.wsEndpointResolve = resolve;
|
|
87
|
+
this.wsEndpointReject = reject;
|
|
88
|
+
})
|
|
89
|
+
});
|
|
42
90
|
this.browserProcess = browserProcess;
|
|
43
91
|
this.pid = browserProcess.pid || -1;
|
|
44
92
|
this.userDataDir = userDataDir;
|
|
45
93
|
this.shouldCleanupUserDataDir = shouldCleanupUserDataDir;
|
|
94
|
+
// Swallow the rejection on the stored promise so a launch that fails
|
|
95
|
+
// before anyone awaits wsEndpoint() (or one that resolves, making a
|
|
96
|
+
// later rejection a no-op) never surfaces as an unhandledRejection. The
|
|
97
|
+
// real awaiter still observes the settlement through its own await.
|
|
98
|
+
this.wsEndpointPromise.catch(() => { });
|
|
99
|
+
// Register in the process-wide signal-cleanup registry so a top-level
|
|
100
|
+
// SIGINT/SIGTERM can kill this browser and remove its temp files even
|
|
101
|
+
// when Crawlee's async teardown never gets a chance to run.
|
|
102
|
+
registerLiveBrowserProcess(this);
|
|
46
103
|
this.onExit = new Promise((resolve) => {
|
|
47
104
|
browserProcess.once('exit', () => {
|
|
105
|
+
unregisterLiveBrowserProcess(this);
|
|
48
106
|
void this.cleanup().catch(log.debug);
|
|
49
107
|
resolve();
|
|
50
108
|
});
|
|
51
109
|
browserProcess.once('error', (err) => {
|
|
110
|
+
unregisterLiveBrowserProcess(this);
|
|
52
111
|
void this.cleanup().catch(log.debug);
|
|
53
112
|
log.error(`Browser crashed: ${err.message}`);
|
|
54
113
|
resolve();
|
|
55
114
|
});
|
|
56
115
|
});
|
|
57
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* The browser-level CDP WebSocket endpoint, resolved once the launcher parses
|
|
119
|
+
* the "DevTools listening on ws://…" line from Chromium's stderr, or rejected
|
|
120
|
+
* if the process exits/errors first. Awaited by the plugin's connect path in
|
|
121
|
+
* place of the former TCP port poll + fixed pre-connect delay.
|
|
122
|
+
*/
|
|
123
|
+
async wsEndpoint() {
|
|
124
|
+
return this.wsEndpointPromise;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Called by the launcher when it matches the DevTools ws line on stderr.
|
|
128
|
+
* Idempotent - the first settlement (resolve or reject) wins.
|
|
129
|
+
*/
|
|
130
|
+
resolveWsEndpoint(wsUrl) {
|
|
131
|
+
if (this.wsEndpointSettled)
|
|
132
|
+
return;
|
|
133
|
+
this.wsEndpointSettled = true;
|
|
134
|
+
this.wsEndpointResolve(wsUrl);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Called by the launcher's process exit/error handlers when the process dies
|
|
138
|
+
* before the ws line appears. The error should carry the buffered stderr
|
|
139
|
+
* tail so a genuinely dead binary fails fast with an actionable message.
|
|
140
|
+
* Idempotent - a no-op once the endpoint has already settled.
|
|
141
|
+
*/
|
|
142
|
+
rejectWsEndpoint(error) {
|
|
143
|
+
if (this.wsEndpointSettled)
|
|
144
|
+
return;
|
|
145
|
+
this.wsEndpointSettled = true;
|
|
146
|
+
this.wsEndpointReject(error);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Record a temp fingerprint-config file that must be removed when this
|
|
150
|
+
* browser exits (or on signal). Called by the launcher after it writes the
|
|
151
|
+
* --blink-platform-overrides-file JSON.
|
|
152
|
+
*/
|
|
153
|
+
trackFingerprintConfigPath(configPath) {
|
|
154
|
+
if (configPath && !this.fingerprintConfigPaths.includes(configPath)) {
|
|
155
|
+
this.fingerprintConfigPaths.push(configPath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Absolute path of the profile directory this browser launched with. */
|
|
159
|
+
getUserDataDir() {
|
|
160
|
+
return this.userDataDir;
|
|
161
|
+
}
|
|
162
|
+
/** Temp fingerprint-config files tracked for this launch (read-only copy). */
|
|
163
|
+
getFingerprintConfigPaths() {
|
|
164
|
+
return [...this.fingerprintConfigPaths];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Signal the browser's whole process group, not just the parent PID.
|
|
168
|
+
*
|
|
169
|
+
* Chromium spawns helper processes (renderer / GPU / utility) that - unless
|
|
170
|
+
* the group is signalled - survive a SIGTERM/SIGKILL aimed only at the
|
|
171
|
+
* parent and reparent to launchd/init, leaking memory and (on macOS)
|
|
172
|
+
* exhausting IOSurface kernel clients until the GPU process aborts.
|
|
173
|
+
*
|
|
174
|
+
* Because the browser is spawned with `detached: true`, the parent is its
|
|
175
|
+
* own process-group leader (pgid === pid) and the helpers inherit that
|
|
176
|
+
* group, so `process.kill(-pid, signal)` reaps the entire tree in one shot.
|
|
177
|
+
* Falls back to signalling the parent alone if the group send throws
|
|
178
|
+
* (ESRCH = already gone, EPERM, or a platform where `detached` didn't take).
|
|
179
|
+
*
|
|
180
|
+
* NOTE the `pid > 1` guard: a failed spawn leaves `pid === -1`, and
|
|
181
|
+
* `process.kill(-(-1))` === `process.kill(1)` would signal init - never do
|
|
182
|
+
* that.
|
|
183
|
+
*/
|
|
184
|
+
signalGroup(signal) {
|
|
185
|
+
if (this.pid && this.pid > 1) {
|
|
186
|
+
try {
|
|
187
|
+
process.kill(-this.pid, signal);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// Group gone / not a leader - fall through to parent-only.
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
this.browserProcess.kill(signal);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// ESRCH - process already dead. Fine.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
58
201
|
async close() {
|
|
59
|
-
if (!this.
|
|
202
|
+
if (!this.isKilling) {
|
|
60
203
|
this.isKilling = true;
|
|
61
|
-
|
|
204
|
+
// Orderly shutdown: SIGTERM the whole group (parent + helpers).
|
|
205
|
+
this.signalGroup('SIGTERM');
|
|
62
206
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}, 5000)),
|
|
207
|
+
// Wait for graceful exit; if it never comes, SIGKILL the group. Unlike
|
|
208
|
+
// the old code, this escalation actually fires - it is no longer gated
|
|
209
|
+
// on `browserProcess.killed` (which SIGTERM had already set to true,
|
|
210
|
+
// making the force-kill a silent no-op and leaking the process tree).
|
|
211
|
+
const exited = await Promise.race([
|
|
212
|
+
this.onExit.then(() => true),
|
|
213
|
+
new Promise((resolve) => setTimeout(() => resolve(false), 2000)),
|
|
71
214
|
]);
|
|
215
|
+
if (!exited) {
|
|
216
|
+
log.warning('Browser process close timeout, force killing process group');
|
|
217
|
+
this.signalGroup('SIGKILL');
|
|
218
|
+
await Promise.race([
|
|
219
|
+
this.onExit,
|
|
220
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
72
223
|
// Ensure cleanup happens after process closes
|
|
73
224
|
await this.cleanup();
|
|
74
225
|
}
|
|
75
226
|
async kill() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
227
|
+
this.isKilling = true;
|
|
228
|
+
// Force path: SIGKILL the whole group immediately.
|
|
229
|
+
this.signalGroup('SIGKILL');
|
|
80
230
|
// Add timeout for kill operation
|
|
81
231
|
await Promise.race([
|
|
82
232
|
this.onExit,
|
|
83
233
|
new Promise((resolve) => setTimeout(() => {
|
|
84
234
|
log.warning('Browser process kill timeout');
|
|
85
235
|
resolve();
|
|
86
|
-
},
|
|
236
|
+
}, 1000)),
|
|
87
237
|
]);
|
|
88
238
|
// Ensure cleanup happens after process is killed
|
|
89
239
|
await this.cleanup();
|
|
90
240
|
}
|
|
91
241
|
signal(signal = 'SIGTERM') {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
242
|
+
this.signalGroup(signal);
|
|
243
|
+
void this.onExit;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Synchronous teardown for the SIGINT/SIGTERM handler. A signal handler
|
|
247
|
+
* cannot await Promises - once it returns, Node re-raises the (now default)
|
|
248
|
+
* signal and exits, so any pending async fs work is dropped. This does the
|
|
249
|
+
* minimum synchronously: SIGKILL the browser (no graceful wait - we are
|
|
250
|
+
* being interrupted) and remove the temp profile dir + fingerprint config
|
|
251
|
+
* with the *Sync fs calls. Idempotent and never throws.
|
|
252
|
+
*/
|
|
253
|
+
killAndCleanupSync() {
|
|
254
|
+
// SIGKILL the whole group (parent + Chromium helpers), not just the
|
|
255
|
+
// parent - otherwise Ctrl+C leaves orphaned renderer/GPU processes.
|
|
256
|
+
this.signalGroup('SIGKILL');
|
|
257
|
+
this.cleanupSync();
|
|
96
258
|
}
|
|
97
259
|
async cleanup() {
|
|
260
|
+
await this.cleanupFingerprintConfigs();
|
|
98
261
|
if (!this.shouldCleanupUserDataDir) {
|
|
99
262
|
log.debug(`Preserving browser profile directory: ${this.userDataDir}`);
|
|
100
263
|
return;
|
|
@@ -107,6 +270,59 @@ export class BrowserProcess {
|
|
|
107
270
|
log.warning(`Could not delete browser profile dir: ${e.message}`);
|
|
108
271
|
}
|
|
109
272
|
}
|
|
273
|
+
/** Remove every tracked temp fingerprint-config file (async path). */
|
|
274
|
+
async cleanupFingerprintConfigs() {
|
|
275
|
+
for (const configPath of this.fingerprintConfigPaths) {
|
|
276
|
+
try {
|
|
277
|
+
await fs.unlink(configPath);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
// Non-fatal - temp file will be cleaned by the OS eventually.
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Synchronous variant of cleanup() for the signal handler. Removes the temp
|
|
286
|
+
* fingerprint configs and the temp profile dir (honoring
|
|
287
|
+
* shouldCleanupUserDataDir) using blocking fs calls. Never throws.
|
|
288
|
+
*/
|
|
289
|
+
cleanupSync() {
|
|
290
|
+
for (const configPath of this.fingerprintConfigPaths) {
|
|
291
|
+
try {
|
|
292
|
+
fsSync.unlinkSync(configPath);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// ENOENT / already removed - ignore.
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (!this.shouldCleanupUserDataDir)
|
|
299
|
+
return;
|
|
300
|
+
// Remove the profile dir and any earlier .cleanup-* rename fallbacks the
|
|
301
|
+
// async path may have left behind in the same parent.
|
|
302
|
+
this.forceDeleteDirectorySync(this.userDataDir);
|
|
303
|
+
try {
|
|
304
|
+
const parentDir = path.dirname(this.userDataDir);
|
|
305
|
+
const base = path.basename(this.userDataDir);
|
|
306
|
+
for (const entry of fsSync.readdirSync(parentDir)) {
|
|
307
|
+
if (entry.startsWith('.cleanup-') && entry.endsWith(base)) {
|
|
308
|
+
this.forceDeleteDirectorySync(path.join(parentDir, entry));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// Parent unreadable - nothing more we can do synchronously.
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Blocking recursive delete used by the signal handler. Never throws. */
|
|
317
|
+
forceDeleteDirectorySync(dirPath) {
|
|
318
|
+
try {
|
|
319
|
+
fsSync.rmSync(dirPath, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// Best-effort - a stuck handle on Windows may block this, but we
|
|
323
|
+
// are exiting anyway so the OS reclaims the temp dir.
|
|
324
|
+
}
|
|
325
|
+
}
|
|
110
326
|
/**
|
|
111
327
|
* Aggressively force delete the browser profile directory
|
|
112
328
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/browser-process/process.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/browser-process/process.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,IAAI,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEpC,OAAO,EAAE,0BAA0B,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAS/F,MAAM,OAAO,cAAc;IA8BvB,YAAY,cAA4B,EAAE,WAAmB,EAAE,wBAAwB,GAAG,IAAI;QA7B9F;;;;;WAAY;QACZ;;;;;WAAsB;QACd;;;;;WAA6B;QAC7B;;;;;WAAoB;QACpB;;;;;WAAkC;QAClC;;;;mBAAY,KAAK;WAAC;QAC1B;;;;WAIG;QACK;;;;mBAAmC,EAAE;WAAC;QAE9C;;;;;;;WAOG;QACK;;;;;WAA4C;QAC5C;;;;;WAA0C;QAC1C;;;;mBAAoB,KAAK;WAAC;QACjB;;;;mBAAqC,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1F,IAAI,CAAC,iBAAiB,GAAG,OAAO,CAAC;gBACjC,IAAI,CAAC,gBAAgB,GAAG,MAAM,CAAC;YACnC,CAAC,CAAC;WAAC;QAGC,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;QACrC,IAAI,CAAC,GAAG,GAAG,cAAc,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,wBAAwB,GAAG,wBAAwB,CAAC;QAEzD,qEAAqE;QACrE,oEAAoE;QACpE,wEAAwE;QACxE,oEAAoE;QACpE,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,EAAE,GAAyC,CAAC,CAAC,CAAC;QAE9E,sEAAsE;QACtE,sEAAsE;QACtE,4DAA4D;QAC5D,0BAA0B,CAAC,IAAI,CAAC,CAAC;QAEjC,IAAI,CAAC,MAAM,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACxC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;gBAC7B,4BAA4B,CAAC,IAAI,CAAC,CAAC;gBACnC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACrC,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;YACH,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACjC,4BAA4B,CAAC,IAAI,CAAC,CAAC;gBACnC,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACrC,GAAG,CAAC,KAAK,CAAC,oBAAoB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC7C,OAAO,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU;QACZ,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,KAAa;QAC3B,IAAI,IAAI,CAAC,iBAAiB;YAAE,OAAO;QACnC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED;;;;;OAKG;IACH,gBAAgB,CAAC,KAAY;QACzB,IAAI,IAAI,CAAC,iBAAiB;YAAE,OAAO;QACnC,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,0BAA0B,CAAC,UAAkB;QACzC,IAAI,UAAU,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClE,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,cAAc;QACV,OAAO,IAAI,CAAC,WAAW,CAAC;IAC5B,CAAC;IAED,8EAA8E;IAC9E,yBAAyB;QACrB,OAAO,CAAC,GAAG,IAAI,CAAC,sBAAsB,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACK,WAAW,CAAC,MAAsB;QACtC,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAChC,OAAO;YACX,CAAC;YAAC,MAAM,CAAC;gBACL,2DAA2D;YAC/D,CAAC;QACL,CAAC;QACD,IAAI,CAAC;YACD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACL,sCAAsC;QAC1C,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACP,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,gEAAgE;YAChE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,CAAC;QAED,uEAAuE;QACvE,uEAAuE;QACvE,qEAAqE;QACrE,sEAAsE;QACtE,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;YAC5B,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;SAC5E,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,GAAG,CAAC,OAAO,CAAC,4DAA4D,CAAC,CAAC;YAC1E,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC5B,MAAM,OAAO,CAAC,IAAI,CAAC;gBACf,IAAI,CAAC,MAAM;gBACX,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;aAC5D,CAAC,CAAC;QACP,CAAC;QAED,8CAA8C;QAC9C,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAI;QACN,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,mDAAmD;QACnD,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAE5B,iCAAiC;QACjC,MAAM,OAAO,CAAC,IAAI,CAAC;YACf,IAAI,CAAC,MAAM;YACX,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAC1B,UAAU,CAAC,GAAG,EAAE;gBACZ,GAAG,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC;gBAC5C,OAAO,EAAE,CAAC;YACd,CAAC,EAAE,IAAI,CAAC,CACX;SACJ,CAAC,CAAC;QAEH,iDAAiD;QACjD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,CAAC,SAAyB,SAAS;QACrC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACzB,KAAK,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;;;OAOG;IACH,kBAAkB;QACd,oEAAoE;QACpE,oEAAoE;QACpE,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;IACvB,CAAC;IAEO,KAAK,CAAC,OAAO;QACjB,MAAM,IAAI,CAAC,yBAAyB,EAAE,CAAC;QAEvC,IAAI,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC;YACjC,GAAG,CAAC,KAAK,CAAC,yCAAyC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YACvE,OAAO;QACX,CAAC;QAED,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAClD,GAAG,CAAC,KAAK,CAAC,sDAAsD,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YACd,GAAG,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QACtE,CAAC;IACL,CAAC;IAED,sEAAsE;IAC9D,KAAK,CAAC,yBAAyB;QACnC,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACnD,IAAI,CAAC;gBACD,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACL,8DAA8D;YAClE,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,WAAW;QACf,KAAK,MAAM,UAAU,IAAI,IAAI,CAAC,sBAAsB,EAAE,CAAC;YACnD,IAAI,CAAC;gBACD,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACL,qCAAqC;YACzC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,wBAAwB;YAAE,OAAO;QAE3C,yEAAyE;QACzE,sDAAsD;QACtD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAChD,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC7C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChD,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;gBAC/D,CAAC;YACL,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,4DAA4D;QAChE,CAAC;IACL,CAAC;IAED,0EAA0E;IAClE,wBAAwB,CAAC,OAAe;QAC5C,IAAI,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,MAAM,CAAC;YACL,iEAAiE;YACjE,sDAAsD;QAC1D,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,oBAAoB,CAAC,OAAe;QAC9C,IAAI,CAAC;YACD,4BAA4B;YAC5B,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACL,+CAA+C;YAC/C,OAAO;QACX,CAAC;QAED,mCAAmC;QACnC,IAAI,CAAC;YACD,MAAM,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACvD,OAAO;QACX,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,GAAG,CAAC,KAAK,CAAC,4BAA4B,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;QACtF,CAAC;QAED,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,qEAAqE;YACrE,IAAI,CAAC;gBACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACxC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,SAAS,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC3F,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;gBACrC,GAAG,CAAC,KAAK,CAAC,oDAAoD,UAAU,EAAE,CAAC,CAAC;YAChF,CAAC;YAAC,OAAO,WAAgB,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CAAC,yCAAyC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9E,CAAC;QACL,CAAC;IACL,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,uBAAuB,CAAC,OAAe;QACjD,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAEnE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC1B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAEhD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC;gBAC7C,IAAI,CAAC;oBACD,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBAC7B,CAAC;gBAAC,OAAO,KAAU,EAAE,CAAC;oBAClB,GAAG,CAAC,KAAK,CAAC,8BAA8B,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAC1E,CAAC;YACL,CAAC;iBAAM,CAAC;gBACJ,IAAI,CAAC;oBACD,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC9B,CAAC;gBAAC,OAAO,KAAU,EAAE,CAAC;oBAClB,GAAG,CAAC,KAAK,CAAC,yBAAyB,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBACrE,CAAC;YACL,CAAC;QACL,CAAC;IACL,CAAC;CACJ"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signal-cleanup.ts - process-wide SIGINT/SIGTERM teardown for live browsers.
|
|
3
|
+
*
|
|
4
|
+
* Problem (issue #73): when a crawl is interrupted with Ctrl+C (SIGINT) or
|
|
5
|
+
* SIGTERM, Node runs no graceful teardown of its own. Crawlee's async
|
|
6
|
+
* BrowserController._close()/_kill() and BrowserProcess.cleanup() only fire on
|
|
7
|
+
* normal shutdown; on a signal the process exits before that async work
|
|
8
|
+
* completes, leaving an orphaned Chromium plus leftover temp `userDataDir`
|
|
9
|
+
* directories (and their `.cleanup-*` rename fallbacks) and temp
|
|
10
|
+
* fingerprint-config files in the working directory.
|
|
11
|
+
*
|
|
12
|
+
* Fix: keep a registry of live BrowserProcess instances and install ONE
|
|
13
|
+
* idempotent handler per signal. On the signal we synchronously SIGKILL each
|
|
14
|
+
* registered browser and remove its temp files (a signal handler cannot await
|
|
15
|
+
* Promises - see BrowserProcess.killAndCleanupSync), then re-raise the signal
|
|
16
|
+
* after detaching only our own listeners. Whoever else is listening decides what
|
|
17
|
+
* happens next: with no listener left Node applies the default disposition and
|
|
18
|
+
* the process exits 128+signo; with Crawlee's own SIGINT handler still attached
|
|
19
|
+
* that handler runs and owns the exit. Either way our browsers are already gone.
|
|
20
|
+
*
|
|
21
|
+
* Design constraints honored:
|
|
22
|
+
* - Idempotent: a second signal while we're already tearing down is ignored.
|
|
23
|
+
* - Registered at most once per process, regardless of how many browsers /
|
|
24
|
+
* crawlers spin up.
|
|
25
|
+
* - Does not swallow or block Crawlee's own shutdown: we only ADD listeners
|
|
26
|
+
* (never removeAllListeners), and re-raise rather than calling process.exit,
|
|
27
|
+
* so a remaining Crawlee/host handler still gets to run its own teardown.
|
|
28
|
+
*/
|
|
29
|
+
/** Minimal shape the registry needs - avoids an import cycle with process.ts. */
|
|
30
|
+
export interface SignalCleanupTarget {
|
|
31
|
+
pid: number;
|
|
32
|
+
killAndCleanupSync(): void;
|
|
33
|
+
}
|
|
34
|
+
/** Add a live browser to the signal-cleanup registry. */
|
|
35
|
+
export declare function registerLiveBrowserProcess(target: SignalCleanupTarget): void;
|
|
36
|
+
/** Remove a browser from the registry once it has exited (normal path). */
|
|
37
|
+
export declare function unregisterLiveBrowserProcess(target: SignalCleanupTarget): void;
|
|
38
|
+
/** Test seam: current number of live registered browsers. */
|
|
39
|
+
export declare function liveBrowserProcessCount(): number;
|
|
40
|
+
//# sourceMappingURL=signal-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-cleanup.d.ts","sourceRoot":"","sources":["../../src/browser-process/signal-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,IAAI,IAAI,CAAC;CAC9B;AAsED,yDAAyD;AACzD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAG5E;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAE9E;AAED,6DAA6D;AAC7D,wBAAgB,uBAAuB,IAAI,MAAM,CAEhD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signal-cleanup.ts - process-wide SIGINT/SIGTERM teardown for live browsers.
|
|
3
|
+
*
|
|
4
|
+
* Problem (issue #73): when a crawl is interrupted with Ctrl+C (SIGINT) or
|
|
5
|
+
* SIGTERM, Node runs no graceful teardown of its own. Crawlee's async
|
|
6
|
+
* BrowserController._close()/_kill() and BrowserProcess.cleanup() only fire on
|
|
7
|
+
* normal shutdown; on a signal the process exits before that async work
|
|
8
|
+
* completes, leaving an orphaned Chromium plus leftover temp `userDataDir`
|
|
9
|
+
* directories (and their `.cleanup-*` rename fallbacks) and temp
|
|
10
|
+
* fingerprint-config files in the working directory.
|
|
11
|
+
*
|
|
12
|
+
* Fix: keep a registry of live BrowserProcess instances and install ONE
|
|
13
|
+
* idempotent handler per signal. On the signal we synchronously SIGKILL each
|
|
14
|
+
* registered browser and remove its temp files (a signal handler cannot await
|
|
15
|
+
* Promises - see BrowserProcess.killAndCleanupSync), then re-raise the signal
|
|
16
|
+
* after detaching only our own listeners. Whoever else is listening decides what
|
|
17
|
+
* happens next: with no listener left Node applies the default disposition and
|
|
18
|
+
* the process exits 128+signo; with Crawlee's own SIGINT handler still attached
|
|
19
|
+
* that handler runs and owns the exit. Either way our browsers are already gone.
|
|
20
|
+
*
|
|
21
|
+
* Design constraints honored:
|
|
22
|
+
* - Idempotent: a second signal while we're already tearing down is ignored.
|
|
23
|
+
* - Registered at most once per process, regardless of how many browsers /
|
|
24
|
+
* crawlers spin up.
|
|
25
|
+
* - Does not swallow or block Crawlee's own shutdown: we only ADD listeners
|
|
26
|
+
* (never removeAllListeners), and re-raise rather than calling process.exit,
|
|
27
|
+
* so a remaining Crawlee/host handler still gets to run its own teardown.
|
|
28
|
+
*/
|
|
29
|
+
import { log } from '@crawlee/core';
|
|
30
|
+
const liveBrowserProcesses = new Set();
|
|
31
|
+
let handlersInstalled = false;
|
|
32
|
+
let tearingDown = false;
|
|
33
|
+
const HANDLED_SIGNALS = ['SIGINT', 'SIGTERM'];
|
|
34
|
+
/**
|
|
35
|
+
* Run the synchronous teardown for every live browser, then re-raise the
|
|
36
|
+
* signal with its default disposition so the process exits normally. Guarded so
|
|
37
|
+
* a repeated signal during teardown does not re-enter.
|
|
38
|
+
*/
|
|
39
|
+
function handleSignal(signal) {
|
|
40
|
+
if (tearingDown)
|
|
41
|
+
return;
|
|
42
|
+
tearingDown = true;
|
|
43
|
+
const targets = [...liveBrowserProcesses];
|
|
44
|
+
log.debug(`[signal-cleanup] ${signal} received - tearing down ${targets.length} browser(s)`);
|
|
45
|
+
for (const target of targets) {
|
|
46
|
+
try {
|
|
47
|
+
target.killAndCleanupSync();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// killAndCleanupSync is already best-effort; never let one browser
|
|
51
|
+
// block cleanup of the others.
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
liveBrowserProcesses.delete(target);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Re-raise the signal, having first removed only OUR listeners so this does
|
|
58
|
+
// not re-enter handleSignal.
|
|
59
|
+
//
|
|
60
|
+
// What this actually does depends on who else is listening. Node applies a
|
|
61
|
+
// signal's default disposition only when NO listener remains, so:
|
|
62
|
+
// - we are the last listener -> default action runs, process exits 128+signo
|
|
63
|
+
// - Crawlee (or the host app) still has a SIGINT/SIGTERM listener -> that
|
|
64
|
+
// handler runs instead, and IT owns the exit
|
|
65
|
+
// The second case is the common one and is intentional: our browsers are
|
|
66
|
+
// already dead by this point, so handing control back to Crawlee's own
|
|
67
|
+
// graceful shutdown is the correct outcome. What we must not do is exit the
|
|
68
|
+
// process ourselves and cut that shutdown short.
|
|
69
|
+
for (const sig of HANDLED_SIGNALS) {
|
|
70
|
+
process.removeListener(sig, signalListeners[sig]);
|
|
71
|
+
}
|
|
72
|
+
process.kill(process.pid, signal);
|
|
73
|
+
}
|
|
74
|
+
const signalListeners = {};
|
|
75
|
+
/**
|
|
76
|
+
* Install the SIGINT/SIGTERM handlers exactly once for this process. Called
|
|
77
|
+
* lazily the first time a BrowserProcess registers, so processes that never
|
|
78
|
+
* launch a browser pay nothing.
|
|
79
|
+
*/
|
|
80
|
+
function ensureHandlersInstalled() {
|
|
81
|
+
if (handlersInstalled)
|
|
82
|
+
return;
|
|
83
|
+
handlersInstalled = true;
|
|
84
|
+
for (const sig of HANDLED_SIGNALS) {
|
|
85
|
+
const listener = () => handleSignal(sig);
|
|
86
|
+
signalListeners[sig] = listener;
|
|
87
|
+
// Additive: do not clobber Crawlee's or the host app's own handlers.
|
|
88
|
+
process.on(sig, listener);
|
|
89
|
+
}
|
|
90
|
+
log.debug('[signal-cleanup] Installed SIGINT/SIGTERM handlers');
|
|
91
|
+
}
|
|
92
|
+
/** Add a live browser to the signal-cleanup registry. */
|
|
93
|
+
export function registerLiveBrowserProcess(target) {
|
|
94
|
+
ensureHandlersInstalled();
|
|
95
|
+
liveBrowserProcesses.add(target);
|
|
96
|
+
}
|
|
97
|
+
/** Remove a browser from the registry once it has exited (normal path). */
|
|
98
|
+
export function unregisterLiveBrowserProcess(target) {
|
|
99
|
+
liveBrowserProcesses.delete(target);
|
|
100
|
+
}
|
|
101
|
+
/** Test seam: current number of live registered browsers. */
|
|
102
|
+
export function liveBrowserProcessCount() {
|
|
103
|
+
return liveBrowserProcesses.size;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=signal-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-cleanup.js","sourceRoot":"","sources":["../../src/browser-process/signal-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAQpC,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAuB,CAAC;AAE5D,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAC9B,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB,MAAM,eAAe,GAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAEhE;;;;GAIG;AACH,SAAS,YAAY,CAAC,MAAsB;IACxC,IAAI,WAAW;QAAE,OAAO;IACxB,WAAW,GAAG,IAAI,CAAC;IAEnB,MAAM,OAAO,GAAG,CAAC,GAAG,oBAAoB,CAAC,CAAC;IAC1C,GAAG,CAAC,KAAK,CAAC,oBAAoB,MAAM,4BAA4B,OAAO,CAAC,MAAM,aAAa,CAAC,CAAC;IAE7F,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC;YACD,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACL,mEAAmE;YACnE,+BAA+B;QACnC,CAAC;gBAAS,CAAC;YACP,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,6BAA6B;IAC7B,EAAE;IACF,2EAA2E;IAC3E,kEAAkE;IAClE,gFAAgF;IAChF,4EAA4E;IAC5E,iDAAiD;IACjD,yEAAyE;IACzE,uEAAuE;IACvE,4EAA4E;IAC5E,iDAAiD;IACjD,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,eAAe,GAAqD,EAAE,CAAC;AAE7E;;;;GAIG;AACH,SAAS,uBAAuB;IAC5B,IAAI,iBAAiB;QAAE,OAAO;IAC9B,iBAAiB,GAAG,IAAI,CAAC;IAEzB,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACzC,eAAe,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;QAChC,qEAAqE;QACrE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC9B,CAAC;IACD,GAAG,CAAC,KAAK,CAAC,oDAAoD,CAAC,CAAC;AACpE,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,0BAA0B,CAAC,MAA2B;IAClE,uBAAuB,EAAE,CAAC;IAC1B,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,4BAA4B,CAAC,MAA2B;IACpE,oBAAoB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,uBAAuB;IACnC,OAAO,oBAAoB,CAAC,IAAI,CAAC;AACrC,CAAC"}
|