@narumitw/pi-chrome-devtools 0.53.4 → 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 +34 -16
- package/dist/chunks/{browser-settings-menu-FVMCCUPM.ts → browser-settings-menu-7M3VM7QN.ts} +25 -3
- package/dist/chunks/browser-settings-menu-7M3VM7QN.ts.map +7 -0
- package/dist/chunks/{chunk-V7N3WI5B.ts → chunk-JKPJYUVG.ts} +340 -108
- package/dist/chunks/chunk-JKPJYUVG.ts.map +7 -0
- package/dist/chunks/{chunk-4B3EQHOH.ts → chunk-JLEJ6WFO.ts} +2 -2
- package/dist/chunks/{chunk-LSJSDY2M.ts → chunk-WTMZID3G.ts} +382 -246
- package/dist/chunks/chunk-WTMZID3G.ts.map +7 -0
- package/dist/chunks/{menu-MTPSLDHZ.ts → menu-HGG26E3X.ts} +7 -7
- package/dist/chunks/{menu-MTPSLDHZ.ts.map → menu-HGG26E3X.ts.map} +1 -1
- package/dist/chunks/{tools-OVNH45IJ.ts → tools-O5N4KIAK.ts} +3 -3
- package/dist/index.ts +45 -21
- package/dist/index.ts.map +2 -2
- 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-settings-menu.ts +23 -0
- package/src/browser-startup.ts +122 -0
- package/src/chrome-devtools.ts +48 -15
- package/src/lazy-tools.ts +358 -48
- package/src/menu.ts +3 -3
- package/src/runtime.ts +2 -2
- package/src/settings.ts +65 -1
- package/src/tool-selector.ts +95 -40
- package/dist/chunks/browser-settings-menu-FVMCCUPM.ts.map +0 -7
- package/dist/chunks/chunk-LSJSDY2M.ts.map +0 -7
- package/dist/chunks/chunk-V7N3WI5B.ts.map +0 -7
- /package/dist/chunks/{chunk-4B3EQHOH.ts.map → chunk-JLEJ6WFO.ts.map} +0 -0
- /package/dist/chunks/{tools-OVNH45IJ.ts.map → tools-O5N4KIAK.ts.map} +0 -0
|
@@ -23,6 +23,9 @@ import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
|
23
23
|
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
24
24
|
var NEW_SETTINGS_FILE_NAME = "pi-chrome-devtools.json";
|
|
25
25
|
var LEGACY_SETTINGS_FILE_NAME = "pi-chrome-devtools-settings.json";
|
|
26
|
+
function isChromeDevtoolsToolMode(value) {
|
|
27
|
+
return value === "codemode" || value === "lazy" || value === "direct";
|
|
28
|
+
}
|
|
26
29
|
var DEFAULT_BROWSER_HOST = "127.0.0.1";
|
|
27
30
|
var DEFAULT_BROWSER_PORT = 9222;
|
|
28
31
|
var DEFAULT_BROWSER_ENDPOINT = `http://${DEFAULT_BROWSER_HOST}:${DEFAULT_BROWSER_PORT}`;
|
|
@@ -77,7 +80,7 @@ async function loadSettings(options = {}) {
|
|
|
77
80
|
const userBrowser = user.normalized?.browser ?? {};
|
|
78
81
|
const effectiveBrowser = resolveEffectiveBrowser(userBrowser, project.normalized?.browser);
|
|
79
82
|
const settings = resolveSettings(user.normalized, project.normalized, effectiveBrowser);
|
|
80
|
-
const recognized = settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
|
|
83
|
+
const recognized = user.normalized?.toolMode !== void 0 || settings.tools !== void 0 || user.normalized?.browser !== void 0 || project.normalized?.browser !== void 0 || user.normalized?.webmcp !== void 0;
|
|
81
84
|
const invalidReasons = [user, project].filter((result) => result.kind === "invalid").map((result) => result.reason).filter((reason) => Boolean(reason));
|
|
82
85
|
const userFile = user.kind === "invalid" ? {
|
|
83
86
|
kind: "invalid",
|
|
@@ -99,6 +102,7 @@ async function loadSettings(options = {}) {
|
|
|
99
102
|
}
|
|
100
103
|
function resolveSettings(user, _project, browser) {
|
|
101
104
|
return {
|
|
105
|
+
toolMode: user?.toolMode ?? "codemode",
|
|
102
106
|
...user?.tools ? { tools: user.tools, updatedAt: user.updatedAt } : {},
|
|
103
107
|
browser,
|
|
104
108
|
webmcp: { enabled: user?.webmcp?.enabled ?? false }
|
|
@@ -190,6 +194,11 @@ function projectOwnedSettingsWarnings(document, scope, filePath) {
|
|
|
190
194
|
}
|
|
191
195
|
async function normalizeSettingsDocument(document, scope, filePath, cwd) {
|
|
192
196
|
const normalized = {};
|
|
197
|
+
if (scope === "user" && document.toolMode !== void 0) {
|
|
198
|
+
if (!isChromeDevtoolsToolMode(document.toolMode))
|
|
199
|
+
throw new Error("expected toolMode to be codemode, lazy, or direct");
|
|
200
|
+
normalized.toolMode = document.toolMode;
|
|
201
|
+
}
|
|
193
202
|
if (scope === "user" && (document.tools !== void 0 || document.updatedAt !== void 0)) {
|
|
194
203
|
const toolSettings = normalizeChromeDevtoolsSettings(document);
|
|
195
204
|
if (!toolSettings) {
|
|
@@ -332,16 +341,31 @@ function orderedUniqueChromeDevtoolsTools(tools) {
|
|
|
332
341
|
function orderedUnique(values) {
|
|
333
342
|
return [...new Set(values)];
|
|
334
343
|
}
|
|
335
|
-
|
|
344
|
+
var ToolCatalogApplicationError = class extends Error {
|
|
345
|
+
constructor(error, rollbackError) {
|
|
346
|
+
super(
|
|
347
|
+
rollbackError ? `${formatError(error)}; saved catalog rollback failed: ${formatError(rollbackError)}` : `${formatError(error)}; previous catalog restored`,
|
|
348
|
+
{ cause: error }
|
|
349
|
+
);
|
|
350
|
+
this.rollbackError = rollbackError;
|
|
351
|
+
}
|
|
352
|
+
rollbackError;
|
|
353
|
+
};
|
|
354
|
+
function saveSettings(settings, operations = {}, applyAfterSave) {
|
|
336
355
|
return queueSettingsMutation(
|
|
337
356
|
(current) => ({
|
|
338
357
|
...current,
|
|
339
358
|
tools: [...settings.tools],
|
|
340
359
|
updatedAt: settings.updatedAt
|
|
341
360
|
}),
|
|
342
|
-
operations
|
|
361
|
+
operations,
|
|
362
|
+
applyAfterSave
|
|
343
363
|
);
|
|
344
364
|
}
|
|
365
|
+
function saveToolMode(mode, operations = {}) {
|
|
366
|
+
if (!isChromeDevtoolsToolMode(mode)) return Promise.reject(new Error("Invalid tool mode"));
|
|
367
|
+
return queueSettingsMutation((current) => ({ ...current, toolMode: mode }), operations);
|
|
368
|
+
}
|
|
345
369
|
function saveWebMcpSettings(enabled, operations = {}) {
|
|
346
370
|
return queueSettingsMutation(
|
|
347
371
|
(current) => ({
|
|
@@ -367,12 +391,12 @@ function saveBrowserSettings(patch, operations = {}) {
|
|
|
367
391
|
return { ...current, browser };
|
|
368
392
|
}, operations);
|
|
369
393
|
}
|
|
370
|
-
function queueSettingsMutation(mutate, operations) {
|
|
371
|
-
const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations));
|
|
394
|
+
function queueSettingsMutation(mutate, operations, applyToolCatalog) {
|
|
395
|
+
const operation = settingsSaveQueue.then(() => saveSettingsMutationNow(mutate, operations, applyToolCatalog));
|
|
372
396
|
settingsSaveQueue = operation.catch(() => void 0);
|
|
373
397
|
return operation;
|
|
374
398
|
}
|
|
375
|
-
async function saveSettingsMutationNow(mutate, operations) {
|
|
399
|
+
async function saveSettingsMutationNow(mutate, operations, applyToolCatalog) {
|
|
376
400
|
const filePath = settingsFilePath();
|
|
377
401
|
let current = await readSettingsDocument(filePath, "user");
|
|
378
402
|
const replaceCanonical = current.kind !== "missing";
|
|
@@ -394,6 +418,27 @@ async function saveSettingsMutationNow(mutate, operations) {
|
|
|
394
418
|
await rm(tempFile, { force: true }).catch(() => void 0);
|
|
395
419
|
throw error;
|
|
396
420
|
}
|
|
421
|
+
if (!applyToolCatalog) return;
|
|
422
|
+
try {
|
|
423
|
+
applyToolCatalog();
|
|
424
|
+
} catch (error) {
|
|
425
|
+
let rollbackError;
|
|
426
|
+
try {
|
|
427
|
+
await saveSettingsMutationNow((latest) => {
|
|
428
|
+
const restored = { ...latest };
|
|
429
|
+
for (const field of ["tools", "updatedAt"]) {
|
|
430
|
+
if (JSON.stringify(latest[field]) !== JSON.stringify(nextDocument[field]))
|
|
431
|
+
throw new Error("Tool catalog changed after save; refusing to overwrite newer settings");
|
|
432
|
+
delete restored[field];
|
|
433
|
+
if (current.document?.[field] !== void 0) restored[field] = current.document[field];
|
|
434
|
+
}
|
|
435
|
+
return restored;
|
|
436
|
+
}, operations);
|
|
437
|
+
} catch (caught) {
|
|
438
|
+
rollbackError = caught;
|
|
439
|
+
}
|
|
440
|
+
throw new ToolCatalogApplicationError(error, rollbackError);
|
|
441
|
+
}
|
|
397
442
|
}
|
|
398
443
|
function settingsFilePath() {
|
|
399
444
|
return join(getAgentDir(), NEW_SETTINGS_FILE_NAME);
|
|
@@ -439,7 +484,6 @@ var DEFAULT_HTTP_TIMEOUT_MS = 1e3;
|
|
|
439
484
|
var DEFAULT_ENDPOINT_WAIT_MS = 5e3;
|
|
440
485
|
var DEFAULT_ENDPOINT_RETRY_MS = 250;
|
|
441
486
|
var MANAGED_BROWSER_PROFILE_PREFIX = "pi-chrome-devtools-profile-";
|
|
442
|
-
var DEVTOOLS_ACTIVE_PORT_FILE = "DevToolsActivePort";
|
|
443
487
|
var BROWSER_SHUTDOWN_WAIT_MS = 1500;
|
|
444
488
|
var state = {
|
|
445
489
|
host: DEFAULT_HOST,
|
|
@@ -544,15 +588,265 @@ function applyRuntimeBrowserSettings(browser, paths, projectTrusted) {
|
|
|
544
588
|
|
|
545
589
|
// src/browser-manager.ts
|
|
546
590
|
import { execFile, spawn } from "node:child_process";
|
|
547
|
-
import {
|
|
548
|
-
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";
|
|
549
592
|
import { createServer } from "node:net";
|
|
550
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";
|
|
551
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
|
|
552
846
|
var DEFAULT_BROWSER_MANAGER_OPERATIONS = {
|
|
553
847
|
access: access2,
|
|
554
848
|
mkdtemp,
|
|
555
|
-
|
|
849
|
+
allocatePort: allocateBrowserPort,
|
|
556
850
|
rm: (path, options) => rm2(path, options),
|
|
557
851
|
fetch: (input, init) => fetch(input, init),
|
|
558
852
|
spawn,
|
|
@@ -625,9 +919,13 @@ function setManagedBrowser(owner, browser) {
|
|
|
625
919
|
function launchPromiseForOwner(owner) {
|
|
626
920
|
return owner ? managedBrowserScope(owner).launchPromise : state.launchPromise;
|
|
627
921
|
}
|
|
922
|
+
function latestLaunchPromiseForOwner(owner) {
|
|
923
|
+
return owner ? managedBrowserScope(owner).latestLaunchPromise : state.latestLaunchPromise;
|
|
924
|
+
}
|
|
628
925
|
function setLaunchPromise(owner, launchPromise) {
|
|
629
|
-
|
|
630
|
-
|
|
926
|
+
const scope = owner ? managedBrowserScope(owner) : state;
|
|
927
|
+
scope.launchPromise = launchPromise;
|
|
928
|
+
if (launchPromise) scope.latestLaunchPromise = launchPromise;
|
|
631
929
|
}
|
|
632
930
|
function managedBrowserPort(owner) {
|
|
633
931
|
return owner ? managedBrowserScope(owner).port : state.port;
|
|
@@ -652,12 +950,6 @@ function setLastLaunchAttempt(owner, attempt) {
|
|
|
652
950
|
function formatError2(error) {
|
|
653
951
|
return error instanceof Error ? error.message : String(error);
|
|
654
952
|
}
|
|
655
|
-
function isNodeError2(error) {
|
|
656
|
-
return error instanceof Error && "code" in error;
|
|
657
|
-
}
|
|
658
|
-
function normalizePathForComparison(value) {
|
|
659
|
-
return process.platform === "win32" ? value.toLowerCase() : value;
|
|
660
|
-
}
|
|
661
953
|
async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerSignal, owner) {
|
|
662
954
|
callerSignal?.throwIfAborted();
|
|
663
955
|
if (extensionsConfigured(owner)) {
|
|
@@ -665,14 +957,19 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
|
|
|
665
957
|
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
666
958
|
return;
|
|
667
959
|
}
|
|
960
|
+
const pendingLaunch = launchPromiseForOwner(owner);
|
|
961
|
+
if (pendingLaunch) {
|
|
962
|
+
await waitForCaller(pendingLaunch, callerSignal);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
668
965
|
if (canAutoLaunchBrowser(owner)) {
|
|
966
|
+
const launchBeforeProbe = latestLaunchPromiseForOwner(owner);
|
|
669
967
|
try {
|
|
670
968
|
await withEndpointRetry(
|
|
671
969
|
() => fetchDevToolsJson("/json/version", { signal: callerSignal }, owner),
|
|
672
970
|
waitMs,
|
|
673
971
|
callerSignal
|
|
674
972
|
);
|
|
675
|
-
return;
|
|
676
973
|
} catch (error) {
|
|
677
974
|
if (shouldAutoLaunchAfterEndpointError(error, owner)) {
|
|
678
975
|
await ensureManagedBrowserLaunched(waitMs, callerSignal, owner);
|
|
@@ -680,6 +977,14 @@ async function ensureDevToolsEndpoint(waitMs = DEFAULT_ENDPOINT_WAIT_MS, callerS
|
|
|
680
977
|
}
|
|
681
978
|
throw error;
|
|
682
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;
|
|
683
988
|
}
|
|
684
989
|
try {
|
|
685
990
|
await fetchDevToolsJson("/json/version", { signal: callerSignal }, owner);
|
|
@@ -757,7 +1062,7 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
|
|
|
757
1062
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
758
1063
|
if (!available) {
|
|
759
1064
|
throw new DevToolsEndpointError(
|
|
760
|
-
`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.`
|
|
761
1066
|
);
|
|
762
1067
|
}
|
|
763
1068
|
}
|
|
@@ -765,12 +1070,16 @@ async function prepareManagedBrowserLaunch(waitMs, generation, signal, owner) {
|
|
|
765
1070
|
}
|
|
766
1071
|
async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
767
1072
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
768
|
-
const candidateDefinitions = browserCandidateDefinitions(owner);
|
|
769
|
-
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
|
+
);
|
|
770
1079
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
771
1080
|
setLastLaunchAttempt(owner, {
|
|
772
1081
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
773
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1082
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
774
1083
|
});
|
|
775
1084
|
if (candidates.length === 0) {
|
|
776
1085
|
throw new DevToolsEndpointError(noBrowserCandidateMessage(candidateDefinitions, owner));
|
|
@@ -785,7 +1094,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
785
1094
|
setLastLaunchAttempt(owner, {
|
|
786
1095
|
...lastLaunchAttempt(owner),
|
|
787
1096
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
788
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1097
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port",
|
|
789
1098
|
selectedCandidate: formatBrowserCandidate(candidate),
|
|
790
1099
|
userDataDir: managedBrowserForOwner(owner)?.userDataDir
|
|
791
1100
|
});
|
|
@@ -797,7 +1106,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
797
1106
|
setLastLaunchAttempt(owner, {
|
|
798
1107
|
...lastLaunchAttempt(owner) ?? {
|
|
799
1108
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
800
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1109
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
801
1110
|
},
|
|
802
1111
|
lastError: formatError2(cancellation)
|
|
803
1112
|
});
|
|
@@ -807,7 +1116,7 @@ async function launchManagedBrowser(waitMs, generation, signal, owner) {
|
|
|
807
1116
|
setLastLaunchAttempt(owner, {
|
|
808
1117
|
...lastLaunchAttempt(owner) ?? {
|
|
809
1118
|
candidateLabels: candidateDefinitions.map(formatBrowserCandidateDefinition),
|
|
810
|
-
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "
|
|
1119
|
+
mode: managedBrowserPortConfigured(owner) ? "explicit-port" : "automatic-port"
|
|
811
1120
|
},
|
|
812
1121
|
lastError: formatError2(error)
|
|
813
1122
|
});
|
|
@@ -852,19 +1161,24 @@ function classifyExtensionBrowserVersion(version) {
|
|
|
852
1161
|
}
|
|
853
1162
|
async function launchBrowserCandidate(candidate, waitMs, generation, signal, owner) {
|
|
854
1163
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
855
|
-
const userDataDir = await browserManagerOperations.mkdtemp(
|
|
1164
|
+
const userDataDir = await browserManagerOperations.mkdtemp(join3(tmpdir(), MANAGED_BROWSER_PROFILE_PREFIX));
|
|
856
1165
|
let managedBrowser;
|
|
857
1166
|
try {
|
|
858
1167
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
859
|
-
const
|
|
860
|
-
|
|
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));
|
|
861
1174
|
const child = browserManagerOperations.spawn(candidate.resolvedExecutable, args, {
|
|
862
1175
|
shell: false,
|
|
863
|
-
stdio: "ignore"
|
|
1176
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
864
1177
|
});
|
|
865
1178
|
const launchedBrowser = {
|
|
866
1179
|
process: child,
|
|
867
1180
|
userDataDir,
|
|
1181
|
+
port,
|
|
868
1182
|
exited: false,
|
|
869
1183
|
ready: false,
|
|
870
1184
|
ownerGeneration: generation,
|
|
@@ -882,24 +1196,12 @@ async function launchBrowserCandidate(candidate, waitMs, generation, signal, own
|
|
|
882
1196
|
setManagedBrowserPort(owner, managedBrowserConfiguredPort(owner));
|
|
883
1197
|
}
|
|
884
1198
|
});
|
|
885
|
-
await
|
|
1199
|
+
const browserIdentity = await waitForBrowserStartup(child, port, waitMs, signal);
|
|
886
1200
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
} else {
|
|
890
|
-
launchedBrowser.port = await readManagedBrowserPort(
|
|
891
|
-
userDataDir,
|
|
892
|
-
launchedBrowser,
|
|
893
|
-
waitMs,
|
|
894
|
-
generation,
|
|
895
|
-
signal,
|
|
896
|
-
owner
|
|
897
|
-
);
|
|
898
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
899
|
-
setManagedBrowserPort(owner, launchedBrowser.port);
|
|
900
|
-
}
|
|
901
|
-
await waitForDevToolsEndpoint(waitMs, launchedBrowser, generation, signal, owner);
|
|
1201
|
+
setManagedBrowserPort(owner, port);
|
|
1202
|
+
await waitForDevToolsEndpoint(waitMs, launchedBrowser, browserIdentity, generation, signal, owner);
|
|
902
1203
|
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1204
|
+
throwIfManagedBrowserExited(launchedBrowser);
|
|
903
1205
|
launchedBrowser.ready = true;
|
|
904
1206
|
} catch (error) {
|
|
905
1207
|
if (managedBrowser) await shutdownManagedBrowser(managedBrowser, { awaitLaunch: false, owner });
|
|
@@ -924,55 +1226,21 @@ function buildManagedBrowserLaunchArguments(userDataDir, portArgument, extension
|
|
|
924
1226
|
"about:blank"
|
|
925
1227
|
];
|
|
926
1228
|
}
|
|
927
|
-
function
|
|
928
|
-
return new Promise((resolve3, reject) => {
|
|
929
|
-
let settled = false;
|
|
930
|
-
const settle = (callback) => {
|
|
931
|
-
if (settled) return;
|
|
932
|
-
settled = true;
|
|
933
|
-
child.off("error", onError);
|
|
934
|
-
child.off("spawn", onSpawn);
|
|
935
|
-
callback();
|
|
936
|
-
};
|
|
937
|
-
const onError = (error) => settle(() => reject(error));
|
|
938
|
-
const onSpawn = () => settle(resolve3);
|
|
939
|
-
child.once("error", onError);
|
|
940
|
-
child.once("spawn", onSpawn);
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
async function readManagedBrowserPort(userDataDir, managedBrowser, waitMs, generation, signal, owner) {
|
|
944
|
-
const activePortFile = join2(userDataDir, DEVTOOLS_ACTIVE_PORT_FILE);
|
|
945
|
-
const deadline = Date.now() + waitMs;
|
|
946
|
-
while (true) {
|
|
947
|
-
throwIfManagedBrowserExited(managedBrowser);
|
|
948
|
-
const text = await browserManagerOperations.readFile(activePortFile, "utf8").catch((error) => {
|
|
949
|
-
if (isNodeError2(error) && error.code === "ENOENT") return void 0;
|
|
950
|
-
throw error;
|
|
951
|
-
});
|
|
952
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
953
|
-
const portText = text?.split(/\r?\n/, 1)[0]?.trim();
|
|
954
|
-
const port = Number(portText);
|
|
955
|
-
if (Number.isInteger(port) && port > 0) return port;
|
|
956
|
-
const remainingMs = deadline - Date.now();
|
|
957
|
-
if (remainingMs <= 0) {
|
|
958
|
-
throw new DevToolsEndpointError(
|
|
959
|
-
[
|
|
960
|
-
"Timed out waiting for auto-launched browser DevToolsActivePort.",
|
|
961
|
-
`Expected file: ${activePortFile}`,
|
|
962
|
-
launchHint(owner)
|
|
963
|
-
].join("\n")
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
await browserManagerOperations.sleep(Math.min(DEFAULT_ENDPOINT_RETRY_MS, remainingMs), signal);
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
async function waitForDevToolsEndpoint(waitMs, managedBrowser, generation, signal, owner) {
|
|
1229
|
+
async function waitForDevToolsEndpoint(waitMs, managedBrowser, browserIdentity, generation, signal, owner) {
|
|
970
1230
|
const deadline = Date.now() + waitMs;
|
|
971
1231
|
while (true) {
|
|
972
1232
|
throwIfManagedBrowserExited(managedBrowser);
|
|
973
1233
|
try {
|
|
974
|
-
|
|
1234
|
+
const version = await fetchDevToolsJson(
|
|
1235
|
+
"/json/version",
|
|
1236
|
+
{ signal },
|
|
1237
|
+
owner
|
|
1238
|
+
);
|
|
975
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
|
+
}
|
|
976
1244
|
return;
|
|
977
1245
|
} catch (error) {
|
|
978
1246
|
if (!isRetryableEndpointError(error)) throw error;
|
|
@@ -1171,16 +1439,16 @@ function launchModeLabel(owner) {
|
|
|
1171
1439
|
if (!isLocalDevToolsHost(browser.host)) return "invalid; extensions require a local endpoint";
|
|
1172
1440
|
if (!browser.autoLaunchEnabled) return "invalid; extensions require auto-launch";
|
|
1173
1441
|
if (managedBrowser && !managedBrowser.exited) {
|
|
1174
|
-
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";
|
|
1175
1443
|
}
|
|
1176
|
-
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";
|
|
1177
1445
|
}
|
|
1178
1446
|
if (!isLocalDevToolsHost(browser.host)) return "manual remote endpoint";
|
|
1179
1447
|
if (!browser.autoLaunchEnabled) return "manual; auto-launch disabled";
|
|
1180
1448
|
if (managedBrowser && !managedBrowser.exited) {
|
|
1181
|
-
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";
|
|
1182
1450
|
}
|
|
1183
|
-
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";
|
|
1184
1452
|
}
|
|
1185
1453
|
function launchAttemptLines(owner) {
|
|
1186
1454
|
const attempt = lastLaunchAttempt(owner);
|
|
@@ -1206,14 +1474,14 @@ function launchHint(owner) {
|
|
|
1206
1474
|
if (!browser.autoLaunchEnabled) {
|
|
1207
1475
|
return `Auto-launch is disabled. Start a browser manually: ${chromeLaunchCommand(owner)}`;
|
|
1208
1476
|
}
|
|
1209
|
-
const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "
|
|
1477
|
+
const managedMode = managedBrowserPortConfigured(owner) ? `port ${managedBrowserPort(owner)}` : "an automatically selected nonzero DevTools port";
|
|
1210
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)}`;
|
|
1211
1479
|
}
|
|
1212
1480
|
function browserCandidateHint(owner) {
|
|
1213
1481
|
if (extensionsConfigured(owner)) {
|
|
1214
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.";
|
|
1215
1483
|
}
|
|
1216
|
-
return `Browser candidates: ${browserCandidateDefinitions(owner).map((candidate) => candidate.label).join(", ")}`;
|
|
1484
|
+
return `Browser candidates: ${browserCandidateDefinitions(browserSettingsForOwner(owner)).map((candidate) => candidate.label).join(", ")}`;
|
|
1217
1485
|
}
|
|
1218
1486
|
function chromeLaunchCommand(owner) {
|
|
1219
1487
|
const executable = browserSettingsForOwner(owner).executablePath ?? defaultManualBrowserExecutable();
|
|
@@ -1233,151 +1501,6 @@ function isLocalDevToolsHost(host) {
|
|
|
1233
1501
|
const normalizedHost = host.toLowerCase().replace(/^\[(.*)]$/, "$1");
|
|
1234
1502
|
return ["localhost", "127.0.0.1", "::1", "0:0:0:0:0:0:0:1"].includes(normalizedHost);
|
|
1235
1503
|
}
|
|
1236
|
-
function browserCandidateDefinitions(owner) {
|
|
1237
|
-
const explicitCandidate = explicitBrowserCandidateDefinition(owner);
|
|
1238
|
-
if (explicitCandidate.length > 0) return explicitCandidate;
|
|
1239
|
-
return uniqueBrowserCandidates(platformBrowserCandidateDefinitions());
|
|
1240
|
-
}
|
|
1241
|
-
function explicitBrowserCandidateDefinition(owner) {
|
|
1242
|
-
const browser = browserSettingsForOwner(owner);
|
|
1243
|
-
if (!browser.executablePath) return [];
|
|
1244
|
-
return [
|
|
1245
|
-
{
|
|
1246
|
-
label: browser.executablePathSource === "environment" ? "PI_CHROME_DEVTOOLS_BROWSER" : "browser.executablePath",
|
|
1247
|
-
executable: browser.executablePath,
|
|
1248
|
-
source: "env"
|
|
1249
|
-
}
|
|
1250
|
-
];
|
|
1251
|
-
}
|
|
1252
|
-
function platformBrowserCandidateDefinitions() {
|
|
1253
|
-
if (process.platform === "darwin") {
|
|
1254
|
-
return [
|
|
1255
|
-
{
|
|
1256
|
-
label: "Google Chrome",
|
|
1257
|
-
executable: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
1258
|
-
source: "wellKnownPath"
|
|
1259
|
-
},
|
|
1260
|
-
{
|
|
1261
|
-
label: "Chromium",
|
|
1262
|
-
executable: "/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
1263
|
-
source: "wellKnownPath"
|
|
1264
|
-
},
|
|
1265
|
-
{
|
|
1266
|
-
label: "Brave Browser",
|
|
1267
|
-
executable: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
1268
|
-
source: "wellKnownPath"
|
|
1269
|
-
},
|
|
1270
|
-
{
|
|
1271
|
-
label: "Microsoft Edge",
|
|
1272
|
-
executable: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
1273
|
-
source: "wellKnownPath"
|
|
1274
|
-
}
|
|
1275
|
-
];
|
|
1276
|
-
}
|
|
1277
|
-
if (process.platform === "win32") {
|
|
1278
|
-
return windowsBrowserCandidateDefinitions();
|
|
1279
|
-
}
|
|
1280
|
-
return [
|
|
1281
|
-
{ label: "Google Chrome", executable: "google-chrome", source: "path" },
|
|
1282
|
-
{ label: "Google Chrome Stable", executable: "google-chrome-stable", source: "path" },
|
|
1283
|
-
{ label: "Chromium", executable: "chromium", source: "path" },
|
|
1284
|
-
{ label: "Chromium Browser", executable: "chromium-browser", source: "path" },
|
|
1285
|
-
{ label: "Brave Browser", executable: "brave-browser", source: "path" },
|
|
1286
|
-
{ label: "Brave", executable: "brave", source: "path" },
|
|
1287
|
-
{ label: "Microsoft Edge", executable: "microsoft-edge", source: "path" },
|
|
1288
|
-
{ label: "Microsoft Edge Stable", executable: "microsoft-edge-stable", source: "path" }
|
|
1289
|
-
];
|
|
1290
|
-
}
|
|
1291
|
-
function windowsBrowserCandidateDefinitions() {
|
|
1292
|
-
const programFiles = [process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.LOCALAPPDATA].filter(
|
|
1293
|
-
(value) => typeof value === "string" && value.length > 0
|
|
1294
|
-
);
|
|
1295
|
-
const wellKnownPaths = programFiles.flatMap((root) => [
|
|
1296
|
-
join2(root, "Google", "Chrome", "Application", "chrome.exe"),
|
|
1297
|
-
join2(root, "Chromium", "Application", "chrome.exe"),
|
|
1298
|
-
join2(root, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
1299
|
-
join2(root, "Microsoft", "Edge", "Application", "msedge.exe")
|
|
1300
|
-
]);
|
|
1301
|
-
return [
|
|
1302
|
-
...wellKnownPaths.map((executable) => ({
|
|
1303
|
-
label: browserLabelFromExecutable(executable),
|
|
1304
|
-
executable,
|
|
1305
|
-
source: "wellKnownPath"
|
|
1306
|
-
})),
|
|
1307
|
-
{ label: "Google Chrome", executable: "chrome.exe", source: "path" },
|
|
1308
|
-
{ label: "Chromium", executable: "chromium.exe", source: "path" },
|
|
1309
|
-
{ label: "Brave Browser", executable: "brave.exe", source: "path" },
|
|
1310
|
-
{ label: "Microsoft Edge", executable: "msedge.exe", source: "path" }
|
|
1311
|
-
];
|
|
1312
|
-
}
|
|
1313
|
-
function browserLabelFromExecutable(executable) {
|
|
1314
|
-
const normalizedExecutable = normalizePathForComparison(executable);
|
|
1315
|
-
if (normalizedExecutable.includes("brave")) return "Brave Browser";
|
|
1316
|
-
if (normalizedExecutable.includes("edge") || normalizedExecutable.includes("msedge")) {
|
|
1317
|
-
return "Microsoft Edge";
|
|
1318
|
-
}
|
|
1319
|
-
if (normalizedExecutable.includes("chromium")) return "Chromium";
|
|
1320
|
-
return "Google Chrome";
|
|
1321
|
-
}
|
|
1322
|
-
function uniqueBrowserCandidates(candidates) {
|
|
1323
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1324
|
-
return candidates.filter((candidate) => {
|
|
1325
|
-
const key = normalizePathForComparison(candidate.executable);
|
|
1326
|
-
if (seen.has(key)) return false;
|
|
1327
|
-
seen.add(key);
|
|
1328
|
-
return true;
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
async function resolveBrowserCandidates(definitions, generation, signal, owner) {
|
|
1332
|
-
const candidates = [];
|
|
1333
|
-
for (const definition of definitions) {
|
|
1334
|
-
const resolvedExecutable = await resolveBrowserExecutable(definition.executable);
|
|
1335
|
-
throwIfBrowserLaunchCancelled(generation, signal, owner);
|
|
1336
|
-
if (!resolvedExecutable) continue;
|
|
1337
|
-
candidates.push({ ...definition, resolvedExecutable });
|
|
1338
|
-
}
|
|
1339
|
-
return uniqueBrowserCandidatesByResolvedPath(candidates);
|
|
1340
|
-
}
|
|
1341
|
-
function uniqueBrowserCandidatesByResolvedPath(candidates) {
|
|
1342
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1343
|
-
return candidates.filter((candidate) => {
|
|
1344
|
-
const key = normalizePathForComparison(resolve2(candidate.resolvedExecutable));
|
|
1345
|
-
if (seen.has(key)) return false;
|
|
1346
|
-
seen.add(key);
|
|
1347
|
-
return true;
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
async function resolveBrowserExecutable(executable) {
|
|
1351
|
-
if (hasPathSeparator(executable) || isAbsolute2(executable)) {
|
|
1352
|
-
const resolvedExecutable = isAbsolute2(executable) ? executable : resolve2(executable);
|
|
1353
|
-
return await canAccessExecutable(resolvedExecutable) ? resolvedExecutable : void 0;
|
|
1354
|
-
}
|
|
1355
|
-
for (const directory of executableSearchPath()) {
|
|
1356
|
-
for (const executableName of executableSearchNames(executable)) {
|
|
1357
|
-
const candidate = join2(directory, executableName);
|
|
1358
|
-
if (await canAccessExecutable(candidate)) return candidate;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
return void 0;
|
|
1362
|
-
}
|
|
1363
|
-
function hasPathSeparator(path) {
|
|
1364
|
-
return path.includes("/") || path.includes("\\");
|
|
1365
|
-
}
|
|
1366
|
-
function executableSearchPath() {
|
|
1367
|
-
return (process.env.PATH ?? "").split(delimiter).filter((part) => part.length > 0);
|
|
1368
|
-
}
|
|
1369
|
-
function executableSearchNames(executable) {
|
|
1370
|
-
if (process.platform !== "win32" || /\.[a-z0-9]+$/i.test(executable)) return [executable];
|
|
1371
|
-
return [executable, `${executable}.exe`, `${executable}.cmd`, `${executable}.bat`];
|
|
1372
|
-
}
|
|
1373
|
-
async function canAccessExecutable(path) {
|
|
1374
|
-
try {
|
|
1375
|
-
await browserManagerOperations.access(path, process.platform === "win32" ? constants2.F_OK : constants2.X_OK);
|
|
1376
|
-
return true;
|
|
1377
|
-
} catch {
|
|
1378
|
-
return false;
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
1504
|
function formatBrowserCandidate(candidate) {
|
|
1382
1505
|
return `${candidate.label} (${candidate.resolvedExecutable})`;
|
|
1383
1506
|
}
|
|
@@ -1435,19 +1558,29 @@ function inspectBrowserVersion(executable, signal) {
|
|
|
1435
1558
|
});
|
|
1436
1559
|
});
|
|
1437
1560
|
}
|
|
1438
|
-
function
|
|
1439
|
-
|
|
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) => {
|
|
1440
1571
|
const server = createServer();
|
|
1441
1572
|
const normalizedHost = host.replace(/^\[(.*)]$/, "$1");
|
|
1442
1573
|
server.unref();
|
|
1443
1574
|
server.once("error", (error) => {
|
|
1444
|
-
if (error.code === "EADDRINUSE")
|
|
1575
|
+
if (error.code === "EADDRINUSE") resolvePort(void 0);
|
|
1445
1576
|
else reject(error);
|
|
1446
1577
|
});
|
|
1447
1578
|
server.listen(port, normalizedHost, () => {
|
|
1579
|
+
const address = server.address();
|
|
1448
1580
|
server.close((error) => {
|
|
1449
1581
|
if (error) reject(error);
|
|
1450
|
-
else
|
|
1582
|
+
else if (!address || typeof address === "string") reject(new Error("DevTools port probe has no address."));
|
|
1583
|
+
else resolvePort(address.port);
|
|
1451
1584
|
});
|
|
1452
1585
|
});
|
|
1453
1586
|
});
|
|
@@ -1468,10 +1601,13 @@ export {
|
|
|
1468
1601
|
WEBMCP_TOOL_NAMES,
|
|
1469
1602
|
CHROME_DEVTOOLS_TOOL_NAMES,
|
|
1470
1603
|
isWebMcpToolName,
|
|
1604
|
+
isChromeDevtoolsToolMode,
|
|
1471
1605
|
waitForSettingsWrites,
|
|
1472
1606
|
loadSettings,
|
|
1473
1607
|
parseBrowserEndpoint,
|
|
1608
|
+
ToolCatalogApplicationError,
|
|
1474
1609
|
saveSettings,
|
|
1610
|
+
saveToolMode,
|
|
1475
1611
|
saveWebMcpSettings,
|
|
1476
1612
|
saveBrowserSettings,
|
|
1477
1613
|
settingsFilePath,
|
|
@@ -1505,4 +1641,4 @@ export {
|
|
|
1505
1641
|
endpointConfigHint,
|
|
1506
1642
|
formatPageListItem
|
|
1507
1643
|
};
|
|
1508
|
-
//# sourceMappingURL=chunk-
|
|
1644
|
+
//# sourceMappingURL=chunk-WTMZID3G.ts.map
|