@narumitw/pi-chrome-devtools 0.54.0 → 0.54.1
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/CHANGELOG.md +109 -0
- package/README.md +6 -5
- package/dist/chunks/{browser-settings-menu-GQAXLWZ7.ts → browser-settings-menu-7M3VM7QN.ts} +3 -3
- package/dist/chunks/{chunk-WOFI2A72.ts → chunk-JKPJYUVG.ts} +6 -23
- package/dist/chunks/chunk-JKPJYUVG.ts.map +7 -0
- package/dist/chunks/{chunk-5T2WEIYN.ts → chunk-JLEJ6WFO.ts} +2 -2
- package/dist/chunks/{chunk-TVVYQ5KM.ts → chunk-WTMZID3G.ts} +328 -240
- package/dist/chunks/chunk-WTMZID3G.ts.map +7 -0
- package/dist/chunks/{menu-Z26D6XSI.ts → menu-HGG26E3X.ts} +4 -4
- package/dist/chunks/{tools-DSYQPFF3.ts → tools-O5N4KIAK.ts} +3 -3
- package/dist/index.ts +8 -8
- package/docs/browser-setup.md +7 -1
- package/package.json +6 -5
- package/src/browser-candidates.ts +184 -0
- package/src/browser-manager.ts +93 -276
- package/src/browser-startup.ts +122 -0
- package/src/lazy-tools.ts +10 -24
- package/src/runtime.ts +2 -2
- package/dist/chunks/chunk-TVVYQ5KM.ts.map +0 -7
- package/dist/chunks/chunk-WOFI2A72.ts.map +0 -7
- /package/dist/chunks/{browser-settings-menu-GQAXLWZ7.ts.map → browser-settings-menu-7M3VM7QN.ts.map} +0 -0
- /package/dist/chunks/{chunk-5T2WEIYN.ts.map → chunk-JLEJ6WFO.ts.map} +0 -0
- /package/dist/chunks/{menu-Z26D6XSI.ts.map → menu-HGG26E3X.ts.map} +0 -0
- /package/dist/chunks/{tools-DSYQPFF3.ts.map → tools-O5N4KIAK.ts.map} +0 -0
|
@@ -484,7 +484,6 @@ var DEFAULT_HTTP_TIMEOUT_MS = 1e3;
|
|
|
484
484
|
var DEFAULT_ENDPOINT_WAIT_MS = 5e3;
|
|
485
485
|
var DEFAULT_ENDPOINT_RETRY_MS = 250;
|
|
486
486
|
var MANAGED_BROWSER_PROFILE_PREFIX = "pi-chrome-devtools-profile-";
|
|
487
|
-
var DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
|
|
488
487
|
var BROWSER_SHUTDOWN_WAIT_MS = 1500;
|
|
489
488
|
var state = {
|
|
490
489
|
host: DEFAULT_HOST,
|
|
@@ -589,15 +588,265 @@ function applyRuntimeBrowserSettings(browser, paths, projectTrusted) {
|
|
|
589
588
|
|
|
590
589
|
// src/browser-manager.ts
|
|
591
590
|
import { execFile, spawn } from "node:child_process";
|
|
592
|
-
import {
|
|
593
|
-
import { access as access2, mkdtemp, readFile as readFile2, rm as rm2 } from "node:fs/promises";
|
|
591
|
+
import { access as access2, mkdtemp, rm as rm2 } from "node:fs/promises";
|
|
594
592
|
import { createServer } from "node:net";
|
|
595
593
|
import { tmpdir } from "node:os";
|
|
594
|
+
import { join as join3 } from "node:path";
|
|
595
|
+
|
|
596
|
+
// src/browser-candidates.ts
|
|
597
|
+
import { constants as constants2 } from "node:fs";
|
|
596
598
|
import { delimiter, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
599
|
+
function normalizePathForComparison(value) {
|
|
600
|
+
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
601
|
+
}
|
|
602
|
+
function browserCandidateDefinitions(browser) {
|
|
603
|
+
const explicitCandidate = explicitBrowserCandidateDefinition(browser);
|
|
604
|
+
if (explicitCandidate.length > 0) return explicitCandidate;
|
|
605
|
+
return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
|
|
606
|
+
}
|
|
607
|
+
function explicitBrowserCandidateDefinition(browser) {
|
|
608
|
+
if (!browser.executablePath) return [];
|
|
609
|
+
return [
|
|
610
|
+
{
|
|
611
|
+
label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
|
|
612
|
+
executable: browser.executablePath,
|
|
613
|
+
source: "env"
|
|
614
|
+
}
|
|
615
|
+
];
|
|
616
|
+
}
|
|
617
|
+
function platformBrowserCandidateDefinitions() {
|
|
618
|
+
if (process.platform === "darwin") {
|
|
619
|
+
return [
|
|
620
|
+
{
|
|
621
|
+
label: "Google Chrome",
|
|
622
|
+
executable: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
623
|
+
source: "wellKnownPath"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
label: "Chromium",
|
|
627
|
+
executable: "/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
628
|
+
source: "wellKnownPath"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
label: "Brave Browser",
|
|
632
|
+
executable: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
633
|
+
source: "wellKnownPath"
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
label: "Microsoft Edge",
|
|
637
|
+
executable: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
638
|
+
source: "wellKnownPath"
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
}
|
|
642
|
+
if (process.platform === "win32") {
|
|
643
|
+
return windowsBrowserCandidateDefinitions();
|
|
644
|
+
}
|
|
645
|
+
return [
|
|
646
|
+
{ label: "Google Chrome", executable: "google-chrome", source: "path" },
|
|
647
|
+
{ label: "Google Chrome Stable", executable: "google-chrome-stable", source: "path" },
|
|
648
|
+
{ label: "Chromium", executable: "chromium", source: "path" },
|
|
649
|
+
{ label: "Chromium Browser", executable: "chromium-browser", source: "path" },
|
|
650
|
+
{ label: "Brave Browser", executable: "brave-browser", source: "path" },
|
|
651
|
+
{ label: "Brave", executable: "brave", source: "path" },
|
|
652
|
+
{ label: "Microsoft Edge", executable: "microsoft-edge", source: "path" },
|
|
653
|
+
{ label: "Microsoft Edge Stable", executable: "microsoft-edge-stable", source: "path" }
|
|
654
|
+
];
|
|
655
|
+
}
|
|
656
|
+
function windowsBrowserCandidateDefinitions() {
|
|
657
|
+
const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter(
|
|
658
|
+
(value) => typeof value === "string" && value.length > 0
|
|
659
|
+
);
|
|
660
|
+
const wellKnownPaths = programFiles.flatMap((root) => [
|
|
661
|
+
join2(root, "Google", "Chrome", "Application", "chrome.exe"),
|
|
662
|
+
join2(root, "Chromium", "Application", "chrome.exe"),
|
|
663
|
+
join2(root, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
664
|
+
join2(root, "Microsoft", "Edge", "Application", "msedge.exe")
|
|
665
|
+
]);
|
|
666
|
+
return [
|
|
667
|
+
...wellKnownPaths.map((executable) => ({
|
|
668
|
+
label: browserLabelFromExecutable(executable),
|
|
669
|
+
executable,
|
|
670
|
+
source: "wellKnownPath"
|
|
671
|
+
})),
|
|
672
|
+
{ label: "Google Chrome", executable: "chrome.exe", source: "path" },
|
|
673
|
+
{ label: "Chromium", executable: "chromium.exe", source: "path" },
|
|
674
|
+
{ label: "Brave Browser", executable: "brave.exe", source: "path" },
|
|
675
|
+
{ label: "Microsoft Edge", executable: "msedge.exe", source: "path" }
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
function browserLabelFromExecutable(executable) {
|
|
679
|
+
const normalizedExecutable = normalizePathForComparison(executable);
|
|
680
|
+
if (normalizedExecutable.includes("brave")) return "Brave Browser";
|
|
681
|
+
if (normalizedExecutable.includes("edge") || normalizedExecutable.includes("msedge")) {
|
|
682
|
+
return "Microsoft Edge";
|
|
683
|
+
}
|
|
684
|
+
if (normalizedExecutable.includes("chromium")) return "Chromium";
|
|
685
|
+
return "Google Chrome";
|
|
686
|
+
}
|
|
687
|
+
function uniqueBrowserCandidates(candidates) {
|
|
688
|
+
const seen = /* @__PURE__ */ new Set();
|
|
689
|
+
return candidates.filter((candidate) => {
|
|
690
|
+
const key = normalizePathForComparison(candidate.executable);
|
|
691
|
+
if (seen.has(key)) return false;
|
|
692
|
+
seen.add(key);
|
|
693
|
+
return true;
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
async function resolveBrowserCandidates(definitions, access3, throwIfCancelled) {
|
|
697
|
+
const candidates = [];
|
|
698
|
+
for (const definition of definitions) {
|
|
699
|
+
const resolvedExecutable = await resolveBrowserExecutable(definition.executable, access3, throwIfCancelled);
|
|
700
|
+
throwIfCancelled();
|
|
701
|
+
if (!resolvedExecutable) continue;
|
|
702
|
+
candidates.push({ ...definition, resolvedExecutable });
|
|
703
|
+
}
|
|
704
|
+
return uniqueBrowserCandidatesByResolvedPath(candidates);
|
|
705
|
+
}
|
|
706
|
+
function uniqueBrowserCandidatesByResolvedPath(candidates) {
|
|
707
|
+
const seen = /* @__PURE__ */ new Set();
|
|
708
|
+
return candidates.filter((candidate) => {
|
|
709
|
+
const key = normalizePathForComparison(resolve2(candidate.resolvedExecutable));
|
|
710
|
+
if (seen.has(key)) return false;
|
|
711
|
+
seen.add(key);
|
|
712
|
+
return true;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
async function resolveBrowserExecutable(executable, access3, throwIfCancelled) {
|
|
716
|
+
if (hasPathSeparator(executable) || isAbsolute2(executable)) {
|
|
717
|
+
const resolvedExecutable = isAbsolute2(executable) ? executable : resolve2(executable);
|
|
718
|
+
return await canAccessExecutable(resolvedExecutable, access3, throwIfCancelled) ? resolvedExecutable : void 0;
|
|
719
|
+
}
|
|
720
|
+
for (const directory of executableSearchPath()) {
|
|
721
|
+
for (const executableName of executableSearchNames(executable)) {
|
|
722
|
+
const candidate = join2(directory, executableName);
|
|
723
|
+
if (await canAccessExecutable(candidate, access3, throwIfCancelled)) return candidate;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return void 0;
|
|
727
|
+
}
|
|
728
|
+
function hasPathSeparator(path) {
|
|
729
|
+
return path.includes("/") || path.includes("\\");
|
|
730
|
+
}
|
|
731
|
+
function executableSearchPath() {
|
|
732
|
+
return (process.env.PATH ?? "").split(delimiter).filter((part) => part.length > 0);
|
|
733
|
+
}
|
|
734
|
+
function executableSearchNames(executable) {
|
|
735
|
+
if (process.platform !== "win32" || /\.[a-z0-9]+$/i.test(executable)) return [executable];
|
|
736
|
+
return [executable, `${executable}.exe`, `${executable}.cmd`, `${executable}.bat`];
|
|
737
|
+
}
|
|
738
|
+
async function canAccessExecutable(path, access3, throwIfCancelled) {
|
|
739
|
+
let accessible = false;
|
|
740
|
+
try {
|
|
741
|
+
await access3(path, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
|
|
742
|
+
accessible = true;
|
|
743
|
+
} catch {
|
|
744
|
+
}
|
|
745
|
+
throwIfCancelled();
|
|
746
|
+
return accessible;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/browser-startup.ts
|
|
750
|
+
var MAX_STARTUP_STDERR_BYTES = 64 * 1024;
|
|
751
|
+
function handleErrorsUntilClose(emitter) {
|
|
752
|
+
const ignoreError = () => void 0;
|
|
753
|
+
emitter.on("error", ignoreError);
|
|
754
|
+
emitter.once("close", () => emitter.off("error", ignoreError));
|
|
755
|
+
}
|
|
756
|
+
function browserWebSocketIdentity(value, port) {
|
|
757
|
+
if (typeof value !== "string") return void 0;
|
|
758
|
+
try {
|
|
759
|
+
const url = new URL(value);
|
|
760
|
+
if (url.protocol !== "ws:" || !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) || Number(url.port || "80") !== port || url.username || url.password || url.search || url.hash || !/^\/devtools\/browser\/[^/]+$/.test(url.pathname)) {
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
return url.pathname;
|
|
764
|
+
} catch {
|
|
765
|
+
return void 0;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function waitForBrowserStartup(child, port, waitMs, signal) {
|
|
769
|
+
handleErrorsUntilClose(child);
|
|
770
|
+
const stderr = child.stderr;
|
|
771
|
+
if (stderr && !stderr.closed) handleErrorsUntilClose(stderr);
|
|
772
|
+
signal.throwIfAborted();
|
|
773
|
+
if (!stderr) return Promise.reject(new Error("Auto-launched browser has no startup stderr stream."));
|
|
774
|
+
return new Promise((resolve3, reject) => {
|
|
775
|
+
let spawned = false;
|
|
776
|
+
let identity;
|
|
777
|
+
let receivedBytes = 0;
|
|
778
|
+
let pendingLine = "";
|
|
779
|
+
let settled = false;
|
|
780
|
+
let timeout;
|
|
781
|
+
const settle = (error) => {
|
|
782
|
+
if (settled) return;
|
|
783
|
+
if (!error && (!spawned || !identity)) return;
|
|
784
|
+
settled = true;
|
|
785
|
+
clearTimeout(timeout);
|
|
786
|
+
child.off("spawn", onSpawn);
|
|
787
|
+
child.off("error", onError);
|
|
788
|
+
child.off("exit", onExit);
|
|
789
|
+
child.off("close", onExit);
|
|
790
|
+
stderr.off("data", onData);
|
|
791
|
+
stderr.off("end", onEnd);
|
|
792
|
+
stderr.off("error", onError);
|
|
793
|
+
signal.removeEventListener("abort", onAbort);
|
|
794
|
+
stderr.resume();
|
|
795
|
+
if (error) reject(error);
|
|
796
|
+
else resolve3(identity);
|
|
797
|
+
};
|
|
798
|
+
const onError = (error) => settle(error);
|
|
799
|
+
const onAbort = () => settle(new Error("Chrome DevTools browser startup cancelled.", { cause: signal.reason }));
|
|
800
|
+
const onExit = () => settle(new Error("Auto-launched browser exited before DevTools became available."));
|
|
801
|
+
const onSpawn = () => {
|
|
802
|
+
spawned = true;
|
|
803
|
+
timeout = setTimeout(
|
|
804
|
+
() => settle(new Error("Timed out waiting for auto-launched browser DevTools startup announcement.")),
|
|
805
|
+
waitMs
|
|
806
|
+
);
|
|
807
|
+
settle();
|
|
808
|
+
};
|
|
809
|
+
const inspectLine = (line) => {
|
|
810
|
+
const match = /^DevTools listening on (\S+)$/.exec(line.trim());
|
|
811
|
+
if (!match) return;
|
|
812
|
+
identity = browserWebSocketIdentity(match[1], port);
|
|
813
|
+
if (!identity) settle(new Error("Auto-launched browser announced an unexpected DevTools endpoint."));
|
|
814
|
+
else settle();
|
|
815
|
+
};
|
|
816
|
+
const onData = (data) => {
|
|
817
|
+
receivedBytes += Buffer.byteLength(data);
|
|
818
|
+
if (receivedBytes > MAX_STARTUP_STDERR_BYTES) {
|
|
819
|
+
settle(new Error("Auto-launched browser startup stderr exceeded 64 KB before readiness."));
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
pendingLine += data.toString();
|
|
823
|
+
const lines = pendingLine.split("\n");
|
|
824
|
+
pendingLine = lines.pop() ?? "";
|
|
825
|
+
for (const line of lines) {
|
|
826
|
+
inspectLine(line);
|
|
827
|
+
if (settled) break;
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
const onEnd = () => {
|
|
831
|
+
inspectLine(pendingLine);
|
|
832
|
+
if (!identity) settle(new Error("Auto-launched browser closed stderr before its DevTools startup announcement."));
|
|
833
|
+
};
|
|
834
|
+
child.once("spawn", onSpawn);
|
|
835
|
+
child.once("error", onError);
|
|
836
|
+
child.once("exit", onExit);
|
|
837
|
+
child.once("close", onExit);
|
|
838
|
+
stderr.on("data", onData);
|
|
839
|
+
stderr.once("end", onEnd);
|
|
840
|
+
stderr.once("error", onError);
|
|
841
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/browser-manager.ts
|
|
597
846
|
var DEFAULT_BROWSER_MANAGER_OPERATIONS = {
|
|
598
847
|
access: access2,
|
|
599
848
|
mkdtemp,
|
|
600
|
-
|
|
849
|
+
allocatePort: allocateBrowserPort,
|
|
601
850
|
rm: (path, options) => rm2(path, options),
|
|
602
851
|
fetch: (input, init) => fetch(input, init),
|
|
603
852
|
spawn,
|
|
@@ -670,9 +919,13 @@ function setManagedBrowser(owner, browser) {
|
|
|
670
919
|
function launchPromiseForOwner(owner) {
|
|
671
920
|
return owner ? managedBrowserScope(owner).launchPromise : state.launchPromise;
|
|
672
921
|
}
|
|
922
|
+
function latestLaunchPromiseForOwner(owner) {
|
|
923
|
+
return owner ? managedBrowserScope(owner).latestLaunchPromise : state.latestLaunchPromise;
|
|
924
|
+
}
|
|
673
925
|
function setLaunchPromise(owner, launchPromise) {
|
|
674
|
-
|
|
675
|
-
|
|
926
|
+
const scope = owner ? managedBrowserScope(owner) : state;
|
|
927
|
+
scope.launchPromise = launchPromise;
|
|
928
|
+
if (launchPromise) scope.latestLaunchPromise = launchPromise;
|
|
676
929
|
}
|
|
677
930
|
function managedBrowserPort(owner) {
|
|
678
931
|
return owner ? managedBrowserScope(owner).port : state.port;
|
|
@@ -697,12 +950,6 @@ function setLastLaunchAttempt(owner, attempt) {
|
|
|
697
950
|
function formatError2(error) {
|
|
698
951
|
return error instanceof Error ? error.message : String(error);
|
|
699
952
|
}
|
|
700
|
-
function isNodeError2(error) {
|
|
701
|
-
return error instanceof Error && "code" in error;
|
|
702
|
-
}
|
|
703
|
-
function normalizePathForComparison(value) {
|
|
704
|
-
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
705
|
-
}
|
|
706
953
|
async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerSignal, owner) {
|
|
707
954
|
callerSignal?.throwIfAborted();
|
|
708
955
|
if (extensionsConfigured(owner)) {
|
|
@@ -710,14 +957,19 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
|
|
|
710
957
|
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
711
958
|
return;
|
|
712
959
|
}
|
|
960
|
+
const pendingLaunch = launchPromiseForOwner(owner);
|
|
961
|
+
if (pendingLaunch) {
|
|
962
|
+
await waitForCaller(pendingLaunch, callerSignal);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
713
965
|
if (canAutoLaunchBrowser(owner)) {
|
|
966
|
+
const launchBeforeProbe = latestLaunchPromiseForOwner(owner);
|
|
714
967
|
try {
|
|
715
968
|
await withEndpointRetry(
|
|
716
969
|
() => fetchDevToolsJson("/json/version", { signal: callerSignal }, owner),
|
|
717
970
|
waitMs,
|
|
718
971
|
callerSignal
|
|
719
972
|
);
|
|
720
|
-
return;
|
|
721
973
|
} catch (error) {
|
|
722
974
|
if (shouldAutoLaunchAfterEndpointError(error, owner)) {
|
|
723
975
|
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
@@ -725,6 +977,14 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
|
|
|
725
977
|
}
|
|
726
978
|
throw error;
|
|
727
979
|
}
|
|
980
|
+
let observedLaunch = launchBeforeProbe;
|
|
981
|
+
while (true) {
|
|
982
|
+
const concurrentLaunch = latestLaunchPromiseForOwner(owner);
|
|
983
|
+
if (!concurrentLaunch || concurrentLaunch === observedLaunch) break;
|
|
984
|
+
observedLaunch = concurrentLaunch;
|
|
985
|
+
await waitForCaller(concurrentLaunch, callerSignal);
|
|
986
|
+
}
|
|
987
|
+
return;
|
|
728
988
|
}
|
|
729
989
|
try {
|
|
730
990
|
await fetchDevToolsJson("/json/version", { signal: callerSignal }, owner);
|
|
@@ -802,7 +1062,7 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
|
|
|
802
1062
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
803
1063
|
if (!available) {
|
|
804
1064
|
throw new DevToolsEndpointError(
|
|
805
|
-
`Cannot launch the extension-owned browser because explicit port ${managedBrowserPort(owner)} is already in use. Choose a free browser.endpoint port or reset the endpoint to use
|
|
1065
|
+
`Cannot launch the extension-owned browser because explicit port ${managedBrowserPort(owner)} is already in use. Choose a free browser.endpoint port or reset the endpoint to use an automatically selected managed port.`
|
|
806
1066
|
);
|
|
807
1067
|
}
|
|
808
1068
|
}
|
|
@@ -810,12 +1070,16 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
|
|
|
810
1070
|
}
|
|
811
1071
|
async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
812
1072
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
813
|
-
const candidateDefinitions = browserCandidateDefinitions(owner);
|
|
814
|
-
const candidates = await resolveBrowserCandidates(
|
|
1073
|
+
const candidateDefinitions = browserCandidateDefinitions(browserSettingsForOwner(owner));
|
|
1074
|
+
const candidates = await resolveBrowserCandidates(
|
|
1075
|
+
candidateDefinitions,
|
|
1076
|
+
browserManagerOperations.access,
|
|
1077
|
+
() => throwIfBrowserLaunchCancelled(generation, signal, owner)
|
|
1078
|
+
);
|
|
815
1079
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
816
1080
|
setLastLaunchAttempt(owner, {
|
|
817
1081
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
818
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1082
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
819
1083
|
});
|
|
820
1084
|
if (candidates.length === 0) {
|
|
821
1085
|
throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions, owner));
|
|
@@ -830,7 +1094,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
830
1094
|
setLastLaunchAttempt(owner, {
|
|
831
1095
|
...lastLaunchAttempt(owner),
|
|
832
1096
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
833
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1097
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port",
|
|
834
1098
|
selectedCandidate: formatBrowserCandidate(candidate),
|
|
835
1099
|
userDataDir: managedBrowserForOwner(owner)?.userDataDir
|
|
836
1100
|
});
|
|
@@ -842,7 +1106,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
842
1106
|
setLastLaunchAttempt(owner, {
|
|
843
1107
|
...lastLaunchAttempt(owner) ?? {
|
|
844
1108
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
845
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1109
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
846
1110
|
},
|
|
847
1111
|
lastError: formatError2(cancellation)
|
|
848
1112
|
});
|
|
@@ -852,7 +1116,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
852
1116
|
setLastLaunchAttempt(owner, {
|
|
853
1117
|
...lastLaunchAttempt(owner) ?? {
|
|
854
1118
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
855
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1119
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
856
1120
|
},
|
|
857
1121
|
lastError: formatError2(error)
|
|
858
1122
|
});
|
|
@@ -897,19 +1161,24 @@ function classifyExtensionBrowserVersion(version) {
|
|
|
897
1161
|
}
|
|
898
1162
|
async function launchBrowserCandidate(candidate, waitMs, generation, signal, owner) {
|
|
899
1163
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
900
|
-
const userDataDir = await browserManagerOperations.mkdtemp(
|
|
1164
|
+
const userDataDir = await browserManagerOperations.mkdtemp(join3(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX));
|
|
901
1165
|
let managedBrowser;
|
|
902
1166
|
try {
|
|
903
1167
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
904
|
-
const
|
|
905
|
-
|
|
1168
|
+
const port = managedBrowserPortConfigured(owner) ? managedBrowserPort(owner) : await browserManagerOperations.allocatePort(browserSettingsForOwner(owner).host);
|
|
1169
|
+
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1170
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1171
|
+
throw new DevToolsEndpointError("Cannot launch a browser without a valid nonzero DevTools port.");
|
|
1172
|
+
}
|
|
1173
|
+
const args = buildManagedBrowserLaunchArguments(userDataDir, String(port), managedBrowserExtensionPaths(owner));
|
|
906
1174
|
const child = browserManagerOperations.spawn(candidate.resolvedExecutable, args, {
|
|
907
1175
|
shell: false,
|
|
908
|
-
stdio: "ignore"
|
|
1176
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
909
1177
|
});
|
|
910
1178
|
const launchedBrowser = {
|
|
911
1179
|
process: child,
|
|
912
1180
|
userDataDir,
|
|
1181
|
+
port,
|
|
913
1182
|
exited: false,
|
|
914
1183
|
ready: false,
|
|
915
1184
|
ownerGeneration: generation,
|
|
@@ -927,24 +1196,12 @@ async function launchBrowserCandidate(candidate, waitMs, generation, signal, own
|
|
|
927
1196
|
setManagedBrowserPort(owner, managedBrowserConfiguredPort(owner));
|
|
928
1197
|
}
|
|
929
1198
|
});
|
|
930
|
-
await
|
|
1199
|
+
const browserIdentity = await waitForBrowserStartup(child, port, waitMs, signal);
|
|
931
1200
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
} else {
|
|
935
|
-
launchedBrowser.port = await readManagedBrowserPort(
|
|
936
|
-
userDataDir,
|
|
937
|
-
launchedBrowser,
|
|
938
|
-
waitMs,
|
|
939
|
-
generation,
|
|
940
|
-
signal,
|
|
941
|
-
owner
|
|
942
|
-
);
|
|
943
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
944
|
-
setManagedBrowserPort(owner, launchedBrowser.port);
|
|
945
|
-
}
|
|
946
|
-
await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal, owner);
|
|
1201
|
+
setManagedBrowserPort(owner, port);
|
|
1202
|
+
await waitForDevToolsEndpoint(waitMs, launchedBrowser, browserIdentity, generation, signal, owner);
|
|
947
1203
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1204
|
+
throwIfManagedBrowserExited(launchedBrowser);
|
|
948
1205
|
launchedBrowser.ready = true;
|
|
949
1206
|
} catch (error) {
|
|
950
1207
|
if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false, owner });
|
|
@@ -969,55 +1226,21 @@ function buildManagedBrowserLaunchArguments(userDataDir, portArgument, extension
|
|
|
969
1226
|
"about:blank"
|
|
970
1227
|
];
|
|
971
1228
|
}
|
|
972
|
-
function
|
|
973
|
-
return new Promise((resolve3, reject) => {
|
|
974
|
-
let settled = false;
|
|
975
|
-
const settle = (callback) => {
|
|
976
|
-
if (settled) return;
|
|
977
|
-
settled = true;
|
|
978
|
-
child.off("error", onError);
|
|
979
|
-
child.off("spawn", onSpawn);
|
|
980
|
-
callback();
|
|
981
|
-
};
|
|
982
|
-
const onError = (error) => settle(() => reject(error));
|
|
983
|
-
const onSpawn = () => settle(resolve3);
|
|
984
|
-
child.once("error", onError);
|
|
985
|
-
child.once("spawn", onSpawn);
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal, owner) {
|
|
989
|
-
const activePortFile = join2(userDataDir, DEVTOOLS_ACTIVE_PORT_FILE);
|
|
990
|
-
const deadline = Date.now() + waitMs;
|
|
991
|
-
while (true) {
|
|
992
|
-
throwIfManagedBrowserExited(managedBrowser);
|
|
993
|
-
const text = await browserManagerOperations.readFile(activePortFile, "utf8").catch((error) => {
|
|
994
|
-
if (isNodeError2(error) && error.code === "ENOENT") return void 0;
|
|
995
|
-
throw error;
|
|
996
|
-
});
|
|
997
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
998
|
-
const portText = text?.split(/\r?\n/, 1)[0]?.trim();
|
|
999
|
-
const port = Number(portText);
|
|
1000
|
-
if (Number.isInteger(port) && port > 0) return port;
|
|
1001
|
-
const remainingMs = deadline - Date.now();
|
|
1002
|
-
if (remainingMs <= 0) {
|
|
1003
|
-
throw new DevToolsEndpointError(
|
|
1004
|
-
[
|
|
1005
|
-
"Timed out waiting for auto-launched browser DevToolsActivePort.",
|
|
1006
|
-
`Expected file: ${activePortFile}`,
|
|
1007
|
-
launchHint(owner)
|
|
1008
|
-
].join("\n")
|
|
1009
|
-
);
|
|
1010
|
-
}
|
|
1011
|
-
await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs), signal);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal, owner) {
|
|
1229
|
+
async function waitForDevToolsEndpoint(waitMs, managedBrowser, browserIdentity, generation, signal, owner) {
|
|
1015
1230
|
const deadline = Date.now() + waitMs;
|
|
1016
1231
|
while (true) {
|
|
1017
1232
|
throwIfManagedBrowserExited(managedBrowser);
|
|
1018
1233
|
try {
|
|
1019
|
-
|
|
1234
|
+
const version = await fetchDevToolsJson(
|
|
1235
|
+
"/json/version",
|
|
1236
|
+
{ signal },
|
|
1237
|
+
owner
|
|
1238
|
+
);
|
|
1020
1239
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1240
|
+
throwIfManagedBrowserExited(managedBrowser);
|
|
1241
|
+
if (browserWebSocketIdentity(version?.webSocketDebuggerUrl, managedBrowser.port ?? 0) !== browserIdentity) {
|
|
1242
|
+
throw new DevToolsEndpointError("DevTools endpoint does not match the auto-launched browser.");
|
|
1243
|
+
}
|
|
1021
1244
|
return;
|
|
1022
1245
|
} catch (error) {
|
|
1023
1246
|
if (!isRetryableEndpointError(error)) throw error;
|
|
@@ -1216,16 +1439,16 @@ function launchModeLabel(owner) {
|
|
|
1216
1439
|
if (!isLocalDevToolsHost(browser.host)) return "invalid; extensions require a local endpoint";
|
|
1217
1440
|
if (!browser.autoLaunchEnabled) return "invalid; extensions require auto-launch";
|
|
1218
1441
|
if (managedBrowser && !managedBrowser.exited) {
|
|
1219
|
-
return managedBrowserPortConfigured(owner) ? "extension-owned browser on explicit port" : "extension-owned browser on
|
|
1442
|
+
return managedBrowserPortConfigured(owner) ? "extension-owned browser on explicit port" : "extension-owned browser on automatically selected port";
|
|
1220
1443
|
}
|
|
1221
|
-
return managedBrowserPortConfigured(owner) ? "force extension-owned launch on explicit port" : "force extension-owned launch on
|
|
1444
|
+
return managedBrowserPortConfigured(owner) ? "force extension-owned launch on explicit port" : "force extension-owned launch on automatically selected port";
|
|
1222
1445
|
}
|
|
1223
1446
|
if (!isLocalDevToolsHost(browser.host)) return "manual remote endpoint";
|
|
1224
1447
|
if (!browser.autoLaunchEnabled) return "manual; auto-launch disabled";
|
|
1225
1448
|
if (managedBrowser && !managedBrowser.exited) {
|
|
1226
|
-
return managedBrowserPortConfigured(owner) ? "auto-launched on explicit port" : "auto-launched on
|
|
1449
|
+
return managedBrowserPortConfigured(owner) ? "auto-launched on explicit port" : "auto-launched on automatically selected port";
|
|
1227
1450
|
}
|
|
1228
|
-
return managedBrowserPortConfigured(owner) ? "attach first; auto-launch explicit port" : "attach first; auto-launch
|
|
1451
|
+
return managedBrowserPortConfigured(owner) ? "attach first; auto-launch explicit port" : "attach first; auto-launch on an automatically selected port";
|
|
1229
1452
|
}
|
|
1230
1453
|
function launchAttemptLines(owner) {
|
|
1231
1454
|
const attempt = lastLaunchAttempt(owner);
|
|
@@ -1251,14 +1474,14 @@ function launchHint(owner) {
|
|
|
1251
1474
|
if (!browser.autoLaunchEnabled) {
|
|
1252
1475
|
return `Auto-launch is disabled. Start a browser manually: ${chromeLaunchCommand(owner)}`;
|
|
1253
1476
|
}
|
|
1254
|
-
const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "
|
|
1477
|
+
const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "an automatically selected nonzero DevTools port";
|
|
1255
1478
|
return `If no endpoint is available, Pi will auto-launch a Chromium-family browser with ${managedMode} and an isolated temp profile. Manual command: ${chromeLaunchCommand(owner)}`;
|
|
1256
1479
|
}
|
|
1257
1480
|
function browserCandidateHint(owner) {
|
|
1258
1481
|
if (extensionsConfigured(owner)) {
|
|
1259
1482
|
return "Extension browser requirement: configure Chrome for Testing or Chromium with browser.executablePath; branded Chrome is rejected because it may ignore unpacked-extension flags.";
|
|
1260
1483
|
}
|
|
1261
|
-
return `Browser candidates: ${browserCandidateDefinitions(owner).map((candidate) => candidate.label).join(", ")}`;
|
|
1484
|
+
return `Browser candidates: ${browserCandidateDefinitions(browserSettingsForOwner(owner)).map((candidate) => candidate.label).join(", ")}`;
|
|
1262
1485
|
}
|
|
1263
1486
|
function chromeLaunchCommand(owner) {
|
|
1264
1487
|
const executable = browserSettingsForOwner(owner).executablePath ?? defaultManualBrowserExecutable();
|
|
@@ -1278,151 +1501,6 @@ function isLocalDevToolsHost(host) {
|
|
|
1278
1501
|
const normalizedHost = host.toLowerCase().replace(/^\[(.*)]$/, "$1");
|
|
1279
1502
|
return ["localhost", "127.0.0.1", "::1", "0:0:0:0:0:0:0:1"].includes(normalizedHost);
|
|
1280
1503
|
}
|
|
1281
|
-
function browserCandidateDefinitions(owner) {
|
|
1282
|
-
const explicitCandidate = explicitBrowserCandidateDefinition(owner);
|
|
1283
|
-
if (explicitCandidate.length > 0) return explicitCandidate;
|
|
1284
|
-
return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
|
|
1285
|
-
}
|
|
1286
|
-
function explicitBrowserCandidateDefinition(owner) {
|
|
1287
|
-
const browser = browserSettingsForOwner(owner);
|
|
1288
|
-
if (!browser.executablePath) return [];
|
|
1289
|
-
return [
|
|
1290
|
-
{
|
|
1291
|
-
label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
|
|
1292
|
-
executable: browser.executablePath,
|
|
1293
|
-
source: "env"
|
|
1294
|
-
}
|
|
1295
|
-
];
|
|
1296
|
-
}
|
|
1297
|
-
function platformBrowserCandidateDefinitions() {
|
|
1298
|
-
if (process.platform === "darwin") {
|
|
1299
|
-
return [
|
|
1300
|
-
{
|
|
1301
|
-
label: "Google Chrome",
|
|
1302
|
-
executable: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1303
|
-
source: "wellKnownPath"
|
|
1304
|
-
},
|
|
1305
|
-
{
|
|
1306
|
-
label: "Chromium",
|
|
1307
|
-
executable: "/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
1308
|
-
source: "wellKnownPath"
|
|
1309
|
-
},
|
|
1310
|
-
{
|
|
1311
|
-
label: "Brave Browser",
|
|
1312
|
-
executable: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
1313
|
-
source: "wellKnownPath"
|
|
1314
|
-
},
|
|
1315
|
-
{
|
|
1316
|
-
label: "Microsoft Edge",
|
|
1317
|
-
executable: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
1318
|
-
source: "wellKnownPath"
|
|
1319
|
-
}
|
|
1320
|
-
];
|
|
1321
|
-
}
|
|
1322
|
-
if (process.platform === "win32") {
|
|
1323
|
-
return windowsBrowserCandidateDefinitions();
|
|
1324
|
-
}
|
|
1325
|
-
return [
|
|
1326
|
-
{ label: "Google Chrome", executable: "google-chrome", source: "path" },
|
|
1327
|
-
{ label: "Google Chrome Stable", executable: "google-chrome-stable", source: "path" },
|
|
1328
|
-
{ label: "Chromium", executable: "chromium", source: "path" },
|
|
1329
|
-
{ label: "Chromium Browser", executable: "chromium-browser", source: "path" },
|
|
1330
|
-
{ label: "Brave Browser", executable: "brave-browser", source: "path" },
|
|
1331
|
-
{ label: "Brave", executable: "brave", source: "path" },
|
|
1332
|
-
{ label: "Microsoft Edge", executable: "microsoft-edge", source: "path" },
|
|
1333
|
-
{ label: "Microsoft Edge Stable", executable: "microsoft-edge-stable", source: "path" }
|
|
1334
|
-
];
|
|
1335
|
-
}
|
|
1336
|
-
function windowsBrowserCandidateDefinitions() {
|
|
1337
|
-
const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter(
|
|
1338
|
-
(value) => typeof value === "string" && value.length > 0
|
|
1339
|
-
);
|
|
1340
|
-
const wellKnownPaths = programFiles.flatMap((root) => [
|
|
1341
|
-
join2(root, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1342
|
-
join2(root, "Chromium", "Application", "chrome.exe"),
|
|
1343
|
-
join2(root, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
1344
|
-
join2(root, "Microsoft", "Edge", "Application", "msedge.exe")
|
|
1345
|
-
]);
|
|
1346
|
-
return [
|
|
1347
|
-
...wellKnownPaths.map((executable) => ({
|
|
1348
|
-
label: browserLabelFromExecutable(executable),
|
|
1349
|
-
executable,
|
|
1350
|
-
source: "wellKnownPath"
|
|
1351
|
-
})),
|
|
1352
|
-
{ label: "Google Chrome", executable: "chrome.exe", source: "path" },
|
|
1353
|
-
{ label: "Chromium", executable: "chromium.exe", source: "path" },
|
|
1354
|
-
{ label: "Brave Browser", executable: "brave.exe", source: "path" },
|
|
1355
|
-
{ label: "Microsoft Edge", executable: "msedge.exe", source: "path" }
|
|
1356
|
-
];
|
|
1357
|
-
}
|
|
1358
|
-
function browserLabelFromExecutable(executable) {
|
|
1359
|
-
const normalizedExecutable = normalizePathForComparison(executable);
|
|
1360
|
-
if (normalizedExecutable.includes("brave")) return "Brave Browser";
|
|
1361
|
-
if (normalizedExecutable.includes("edge") || normalizedExecutable.includes("msedge")) {
|
|
1362
|
-
return "Microsoft Edge";
|
|
1363
|
-
}
|
|
1364
|
-
if (normalizedExecutable.includes("chromium")) return "Chromium";
|
|
1365
|
-
return "Google Chrome";
|
|
1366
|
-
}
|
|
1367
|
-
function uniqueBrowserCandidates(candidates) {
|
|
1368
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1369
|
-
return candidates.filter((candidate) => {
|
|
1370
|
-
const key = normalizePathForComparison(candidate.executable);
|
|
1371
|
-
if (seen.has(key)) return false;
|
|
1372
|
-
seen.add(key);
|
|
1373
|
-
return true;
|
|
1374
|
-
});
|
|
1375
|
-
}
|
|
1376
|
-
async function resolveBrowserCandidates(definitions, generation, signal, owner) {
|
|
1377
|
-
const candidates = [];
|
|
1378
|
-
for (const definition of definitions) {
|
|
1379
|
-
const resolvedExecutable = await resolveBrowserExecutable(definition.executable);
|
|
1380
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1381
|
-
if (!resolvedExecutable) continue;
|
|
1382
|
-
candidates.push({ ...definition, resolvedExecutable });
|
|
1383
|
-
}
|
|
1384
|
-
return uniqueBrowserCandidatesByResolvedPath(candidates);
|
|
1385
|
-
}
|
|
1386
|
-
function uniqueBrowserCandidatesByResolvedPath(candidates) {
|
|
1387
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1388
|
-
return candidates.filter((candidate) => {
|
|
1389
|
-
const key = normalizePathForComparison(resolve2(candidate.resolvedExecutable));
|
|
1390
|
-
if (seen.has(key)) return false;
|
|
1391
|
-
seen.add(key);
|
|
1392
|
-
return true;
|
|
1393
|
-
});
|
|
1394
|
-
}
|
|
1395
|
-
async function resolveBrowserExecutable(executable) {
|
|
1396
|
-
if (hasPathSeparator(executable) || isAbsolute2(executable)) {
|
|
1397
|
-
const resolvedExecutable = isAbsolute2(executable) ? executable : resolve2(executable);
|
|
1398
|
-
return await canAccessExecutable(resolvedExecutable) ? resolvedExecutable : void 0;
|
|
1399
|
-
}
|
|
1400
|
-
for (const directory of executableSearchPath()) {
|
|
1401
|
-
for (const executableName of executableSearchNames(executable)) {
|
|
1402
|
-
const candidate = join2(directory, executableName);
|
|
1403
|
-
if (await canAccessExecutable(candidate)) return candidate;
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
return void 0;
|
|
1407
|
-
}
|
|
1408
|
-
function hasPathSeparator(path) {
|
|
1409
|
-
return path.includes("/") || path.includes("\\");
|
|
1410
|
-
}
|
|
1411
|
-
function executableSearchPath() {
|
|
1412
|
-
return (process.env.PATH ?? "").split(delimiter).filter((part) => part.length > 0);
|
|
1413
|
-
}
|
|
1414
|
-
function executableSearchNames(executable) {
|
|
1415
|
-
if (process.platform !== "win32" || /\.[a-z0-9]+$/i.test(executable)) return [executable];
|
|
1416
|
-
return [executable, `${executable}.exe`, `${executable}.cmd`, `${executable}.bat`];
|
|
1417
|
-
}
|
|
1418
|
-
async function canAccessExecutable(path) {
|
|
1419
|
-
try {
|
|
1420
|
-
await browserManagerOperations.access(path, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
|
|
1421
|
-
return true;
|
|
1422
|
-
} catch {
|
|
1423
|
-
return false;
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
1504
|
function formatBrowserCandidate(candidate) {
|
|
1427
1505
|
return `${candidate.label} (${candidate.resolvedExecutable})`;
|
|
1428
1506
|
}
|
|
@@ -1480,19 +1558,29 @@ function inspectBrowserVersion(executable, signal) {
|
|
|
1480
1558
|
});
|
|
1481
1559
|
});
|
|
1482
1560
|
}
|
|
1483
|
-
function
|
|
1484
|
-
|
|
1561
|
+
async function allocateBrowserPort(host) {
|
|
1562
|
+
const port = await probeBrowserPort(host, 0);
|
|
1563
|
+
if (port === void 0) throw new Error("Cannot allocate an available DevTools port.");
|
|
1564
|
+
return port;
|
|
1565
|
+
}
|
|
1566
|
+
async function isPortAvailable(host, port) {
|
|
1567
|
+
return await probeBrowserPort(host, port) !== void 0;
|
|
1568
|
+
}
|
|
1569
|
+
function probeBrowserPort(host, port) {
|
|
1570
|
+
return new Promise((resolvePort, reject) => {
|
|
1485
1571
|
const server = createServer();
|
|
1486
1572
|
const normalizedHost = host.replace(/^\[(.*)]$/, "$1");
|
|
1487
1573
|
server.unref();
|
|
1488
1574
|
server.once("error", (error) => {
|
|
1489
|
-
if (error.code === "EADDRINUSE")
|
|
1575
|
+
if (error.code === "EADDRINUSE") resolvePort(void 0);
|
|
1490
1576
|
else reject(error);
|
|
1491
1577
|
});
|
|
1492
1578
|
server.listen(port, normalizedHost, () => {
|
|
1579
|
+
const address = server.address();
|
|
1493
1580
|
server.close((error) => {
|
|
1494
1581
|
if (error) reject(error);
|
|
1495
|
-
else
|
|
1582
|
+
else if (!address || typeof address === "string") reject(new Error("DevTools port probe has no address."));
|
|
1583
|
+
else resolvePort(address.port);
|
|
1496
1584
|
});
|
|
1497
1585
|
});
|
|
1498
1586
|
});
|
|
@@ -1553,4 +1641,4 @@ export {
|
|
|
1553
1641
|
endpointConfigHint,
|
|
1554
1642
|
formatPageListItem
|
|
1555
1643
|
};
|
|
1556
|
-
//# sourceMappingURL=chunk-
|
|
1644
|
+
//# sourceMappingURL=chunk-WTMZID3G.ts.map
|