@ricsam/r5d-browser 0.0.53 → 0.0.55
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.
|
@@ -35,6 +35,7 @@ __export(chrome_launcher_exports, {
|
|
|
35
35
|
defaultChromeProfilePath: () => defaultChromeProfilePath,
|
|
36
36
|
launchStableChrome: () => launchStableChrome,
|
|
37
37
|
resolveChromeExecutable: () => resolveChromeExecutable,
|
|
38
|
+
terminateOwnedProcess: () => terminateOwnedProcess,
|
|
38
39
|
validateChromeProfilePath: () => validateChromeProfilePath,
|
|
39
40
|
validateDebuggerWebSocketUrl: () => validateDebuggerWebSocketUrl,
|
|
40
41
|
validateEndpointOwnership: () => validateEndpointOwnership
|
|
@@ -529,21 +530,29 @@ async function requestOwnedBrowserClose(webSocketDebuggerUrl, chromePid) {
|
|
|
529
530
|
cdp.close();
|
|
530
531
|
}
|
|
531
532
|
}
|
|
532
|
-
async function terminateOwnedProcess(params
|
|
533
|
+
async function terminateOwnedProcess(params, timeouts = {
|
|
534
|
+
gracefulCloseMs: GRACEFUL_CLOSE_TIMEOUT_MS,
|
|
535
|
+
terminateMs: TERMINATE_TIMEOUT_MS
|
|
536
|
+
}) {
|
|
533
537
|
if (params.child.exitCode !== null || params.child.signalCode !== null) return;
|
|
534
538
|
if (params.browser?.isConnected()) {
|
|
535
539
|
void params.browser.newBrowserCDPSession().then(async (cdp) => {
|
|
536
540
|
await cdp.send("Browser.close");
|
|
537
541
|
}).catch(() => void 0);
|
|
538
|
-
if (await waitForExit(params.exited,
|
|
542
|
+
if (await waitForExit(params.exited, timeouts.gracefulCloseMs)) return;
|
|
539
543
|
} else if (params.ownedWebSocketDebuggerUrl) {
|
|
540
544
|
const closeRequested = await requestOwnedBrowserClose(params.ownedWebSocketDebuggerUrl, params.chromePid).catch(() => false);
|
|
541
|
-
if (closeRequested && await waitForExit(params.exited,
|
|
545
|
+
if (closeRequested && await waitForExit(params.exited, timeouts.gracefulCloseMs)) return;
|
|
542
546
|
}
|
|
543
547
|
if (params.child.exitCode === null && params.child.signalCode === null) params.child.kill("SIGTERM");
|
|
544
|
-
if (await waitForExit(params.exited,
|
|
548
|
+
if (await waitForExit(params.exited, timeouts.terminateMs)) return;
|
|
545
549
|
if (params.child.exitCode === null && params.child.signalCode === null) params.child.kill("SIGKILL");
|
|
546
|
-
await waitForExit(params.exited,
|
|
550
|
+
if (await waitForExit(params.exited, timeouts.terminateMs)) return;
|
|
551
|
+
const stderr = params.stderr?.().trim();
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Google Chrome PID ${params.chromePid} did not exit within ${timeouts.terminateMs}ms after SIGKILL; the run lock was retained to protect its profile.${stderr ? ` Chrome stderr:
|
|
554
|
+
${stderr}` : ""}`
|
|
555
|
+
);
|
|
547
556
|
}
|
|
548
557
|
function occupiedProfileError(profilePath) {
|
|
549
558
|
const owner = singletonLockOwner(profilePath);
|
|
@@ -562,6 +571,7 @@ async function launchStableChrome(options) {
|
|
|
562
571
|
const profilePath = validateChromeProfilePath(options.profilePath);
|
|
563
572
|
const playwrightDownloadsPath = import_node_path.default.resolve(options.playwrightDownloadsPath);
|
|
564
573
|
const lock = acquireChromeRunLock(options.lockPath ?? defaultChromeLockPath(), profilePath);
|
|
574
|
+
let releaseLockOnError = true;
|
|
565
575
|
try {
|
|
566
576
|
import_node_fs.default.mkdirSync(profilePath, { recursive: true, mode: 448 });
|
|
567
577
|
import_node_fs.default.mkdirSync(playwrightDownloadsPath, { recursive: true, mode: 448 });
|
|
@@ -618,17 +628,20 @@ async function launchStableChrome(options) {
|
|
|
618
628
|
let closePromise;
|
|
619
629
|
const close = () => {
|
|
620
630
|
closePromise ??= (async () => {
|
|
631
|
+
let processExited = false;
|
|
621
632
|
try {
|
|
622
633
|
await terminateOwnedProcess({
|
|
623
634
|
child,
|
|
624
635
|
exited: observed.exited,
|
|
625
636
|
chromePid,
|
|
626
637
|
ownedWebSocketDebuggerUrl,
|
|
627
|
-
browser
|
|
638
|
+
browser,
|
|
639
|
+
stderr: observed.stderr
|
|
628
640
|
});
|
|
641
|
+
processExited = true;
|
|
629
642
|
await browser.close().catch(() => void 0);
|
|
630
643
|
} finally {
|
|
631
|
-
lock.release();
|
|
644
|
+
if (processExited) lock.release();
|
|
632
645
|
}
|
|
633
646
|
})();
|
|
634
647
|
return closePromise;
|
|
@@ -650,7 +663,11 @@ async function launchStableChrome(options) {
|
|
|
650
663
|
child,
|
|
651
664
|
exited: observed.exited,
|
|
652
665
|
chromePid,
|
|
653
|
-
ownedWebSocketDebuggerUrl
|
|
666
|
+
ownedWebSocketDebuggerUrl,
|
|
667
|
+
stderr: observed.stderr
|
|
668
|
+
}).catch((terminationError) => {
|
|
669
|
+
releaseLockOnError = false;
|
|
670
|
+
throw terminationError;
|
|
654
671
|
});
|
|
655
672
|
const occupied = occupiedProfileError(profilePath);
|
|
656
673
|
if (occupied) throw occupied;
|
|
@@ -661,7 +678,7 @@ async function launchStableChrome(options) {
|
|
|
661
678
|
`Could not launch Google Chrome after ${MAX_LAUNCH_ATTEMPTS} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
662
679
|
);
|
|
663
680
|
} catch (error) {
|
|
664
|
-
lock.release();
|
|
681
|
+
if (releaseLockOnError) lock.release();
|
|
665
682
|
throw error;
|
|
666
683
|
}
|
|
667
684
|
}
|
|
@@ -674,6 +691,7 @@ async function launchStableChrome(options) {
|
|
|
674
691
|
defaultChromeProfilePath,
|
|
675
692
|
launchStableChrome,
|
|
676
693
|
resolveChromeExecutable,
|
|
694
|
+
terminateOwnedProcess,
|
|
677
695
|
validateChromeProfilePath,
|
|
678
696
|
validateDebuggerWebSocketUrl,
|
|
679
697
|
validateEndpointOwnership
|
package/dist/cjs/package.json
CHANGED
|
@@ -487,21 +487,29 @@ async function requestOwnedBrowserClose(webSocketDebuggerUrl, chromePid) {
|
|
|
487
487
|
cdp.close();
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
|
-
async function terminateOwnedProcess(params
|
|
490
|
+
async function terminateOwnedProcess(params, timeouts = {
|
|
491
|
+
gracefulCloseMs: GRACEFUL_CLOSE_TIMEOUT_MS,
|
|
492
|
+
terminateMs: TERMINATE_TIMEOUT_MS
|
|
493
|
+
}) {
|
|
491
494
|
if (params.child.exitCode !== null || params.child.signalCode !== null) return;
|
|
492
495
|
if (params.browser?.isConnected()) {
|
|
493
496
|
void params.browser.newBrowserCDPSession().then(async (cdp) => {
|
|
494
497
|
await cdp.send("Browser.close");
|
|
495
498
|
}).catch(() => void 0);
|
|
496
|
-
if (await waitForExit(params.exited,
|
|
499
|
+
if (await waitForExit(params.exited, timeouts.gracefulCloseMs)) return;
|
|
497
500
|
} else if (params.ownedWebSocketDebuggerUrl) {
|
|
498
501
|
const closeRequested = await requestOwnedBrowserClose(params.ownedWebSocketDebuggerUrl, params.chromePid).catch(() => false);
|
|
499
|
-
if (closeRequested && await waitForExit(params.exited,
|
|
502
|
+
if (closeRequested && await waitForExit(params.exited, timeouts.gracefulCloseMs)) return;
|
|
500
503
|
}
|
|
501
504
|
if (params.child.exitCode === null && params.child.signalCode === null) params.child.kill("SIGTERM");
|
|
502
|
-
if (await waitForExit(params.exited,
|
|
505
|
+
if (await waitForExit(params.exited, timeouts.terminateMs)) return;
|
|
503
506
|
if (params.child.exitCode === null && params.child.signalCode === null) params.child.kill("SIGKILL");
|
|
504
|
-
await waitForExit(params.exited,
|
|
507
|
+
if (await waitForExit(params.exited, timeouts.terminateMs)) return;
|
|
508
|
+
const stderr = params.stderr?.().trim();
|
|
509
|
+
throw new Error(
|
|
510
|
+
`Google Chrome PID ${params.chromePid} did not exit within ${timeouts.terminateMs}ms after SIGKILL; the run lock was retained to protect its profile.${stderr ? ` Chrome stderr:
|
|
511
|
+
${stderr}` : ""}`
|
|
512
|
+
);
|
|
505
513
|
}
|
|
506
514
|
function occupiedProfileError(profilePath) {
|
|
507
515
|
const owner = singletonLockOwner(profilePath);
|
|
@@ -520,6 +528,7 @@ async function launchStableChrome(options) {
|
|
|
520
528
|
const profilePath = validateChromeProfilePath(options.profilePath);
|
|
521
529
|
const playwrightDownloadsPath = path.resolve(options.playwrightDownloadsPath);
|
|
522
530
|
const lock = acquireChromeRunLock(options.lockPath ?? defaultChromeLockPath(), profilePath);
|
|
531
|
+
let releaseLockOnError = true;
|
|
523
532
|
try {
|
|
524
533
|
fs.mkdirSync(profilePath, { recursive: true, mode: 448 });
|
|
525
534
|
fs.mkdirSync(playwrightDownloadsPath, { recursive: true, mode: 448 });
|
|
@@ -576,17 +585,20 @@ async function launchStableChrome(options) {
|
|
|
576
585
|
let closePromise;
|
|
577
586
|
const close = () => {
|
|
578
587
|
closePromise ??= (async () => {
|
|
588
|
+
let processExited = false;
|
|
579
589
|
try {
|
|
580
590
|
await terminateOwnedProcess({
|
|
581
591
|
child,
|
|
582
592
|
exited: observed.exited,
|
|
583
593
|
chromePid,
|
|
584
594
|
ownedWebSocketDebuggerUrl,
|
|
585
|
-
browser
|
|
595
|
+
browser,
|
|
596
|
+
stderr: observed.stderr
|
|
586
597
|
});
|
|
598
|
+
processExited = true;
|
|
587
599
|
await browser.close().catch(() => void 0);
|
|
588
600
|
} finally {
|
|
589
|
-
lock.release();
|
|
601
|
+
if (processExited) lock.release();
|
|
590
602
|
}
|
|
591
603
|
})();
|
|
592
604
|
return closePromise;
|
|
@@ -608,7 +620,11 @@ async function launchStableChrome(options) {
|
|
|
608
620
|
child,
|
|
609
621
|
exited: observed.exited,
|
|
610
622
|
chromePid,
|
|
611
|
-
ownedWebSocketDebuggerUrl
|
|
623
|
+
ownedWebSocketDebuggerUrl,
|
|
624
|
+
stderr: observed.stderr
|
|
625
|
+
}).catch((terminationError) => {
|
|
626
|
+
releaseLockOnError = false;
|
|
627
|
+
throw terminationError;
|
|
612
628
|
});
|
|
613
629
|
const occupied = occupiedProfileError(profilePath);
|
|
614
630
|
if (occupied) throw occupied;
|
|
@@ -619,7 +635,7 @@ async function launchStableChrome(options) {
|
|
|
619
635
|
`Could not launch Google Chrome after ${MAX_LAUNCH_ATTEMPTS} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`
|
|
620
636
|
);
|
|
621
637
|
} catch (error) {
|
|
622
|
-
lock.release();
|
|
638
|
+
if (releaseLockOnError) lock.release();
|
|
623
639
|
throw error;
|
|
624
640
|
}
|
|
625
641
|
}
|
|
@@ -631,6 +647,7 @@ export {
|
|
|
631
647
|
defaultChromeProfilePath,
|
|
632
648
|
launchStableChrome,
|
|
633
649
|
resolveChromeExecutable,
|
|
650
|
+
terminateOwnedProcess,
|
|
634
651
|
validateChromeProfilePath,
|
|
635
652
|
validateDebuggerWebSocketUrl,
|
|
636
653
|
validateEndpointOwnership
|
package/dist/mjs/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
1
2
|
import { type Browser, type BrowserContext } from "playwright-core";
|
|
2
3
|
export type ChromeExit = {
|
|
3
4
|
code: number | null;
|
|
@@ -26,6 +27,10 @@ export type ChromeRunLock = {
|
|
|
26
27
|
token: string;
|
|
27
28
|
release(): void;
|
|
28
29
|
};
|
|
30
|
+
type ChromeTerminationTimeouts = {
|
|
31
|
+
gracefulCloseMs: number;
|
|
32
|
+
terminateMs: number;
|
|
33
|
+
};
|
|
29
34
|
export declare function defaultChromeProfilePath(homeDirectory?: string): string;
|
|
30
35
|
export declare function defaultChromeLockPath(homeDirectory?: string): string;
|
|
31
36
|
export declare function resolveChromeExecutable(options?: {
|
|
@@ -50,4 +55,14 @@ export declare function validateEndpointOwnership(params: {
|
|
|
50
55
|
product: unknown;
|
|
51
56
|
commandLine: unknown;
|
|
52
57
|
}): string;
|
|
58
|
+
/** @internal Exported only so the bounded shutdown failure can be unit-tested. */
|
|
59
|
+
export declare function terminateOwnedProcess(params: {
|
|
60
|
+
child: ChildProcess;
|
|
61
|
+
exited: Promise<ChromeExit>;
|
|
62
|
+
chromePid: number;
|
|
63
|
+
ownedWebSocketDebuggerUrl?: string;
|
|
64
|
+
browser?: Browser;
|
|
65
|
+
stderr?: () => string;
|
|
66
|
+
}, timeouts?: ChromeTerminationTimeouts): Promise<void>;
|
|
53
67
|
export declare function launchStableChrome(options: LaunchStableChromeOptions): Promise<StableChromeSession>;
|
|
68
|
+
export {};
|