feat(conflict): gestione interattiva conflitti profilo/porta attiva su browser_start
This commit is contained in:
+87
-14
@@ -163,8 +163,25 @@ async function withRetry<T>(fn: () => Promise<T>, label: string, retries = 2): P
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ciclo di vita: Firefox + bridge, con recupero da sessione orfana
|
||||
// Ciclo di vita: Firefox + bridge, con recupero da sessione orfana e gestione conflitti
|
||||
// ---------------------------------------------------------------------------
|
||||
export type ConflictAction = "close" | "wait" | "dedicated" | "ask";
|
||||
|
||||
interface ConflictCheck {
|
||||
portBusy: boolean;
|
||||
firefoxRunning: boolean;
|
||||
mainProfile: string;
|
||||
}
|
||||
|
||||
async function checkFirefoxConflict(): Promise<ConflictCheck> {
|
||||
try {
|
||||
const { stdout } = await execFileAsync(SCRIPT, ["check"], { timeout: 5_000 });
|
||||
return JSON.parse(stdout.trim());
|
||||
} catch {
|
||||
return { portBusy: await isPortOpen(9222), firefoxRunning: false, mainProfile: "" };
|
||||
}
|
||||
}
|
||||
|
||||
async function bridgeAlive(): Promise<boolean> {
|
||||
try {
|
||||
const status = await bridge("/status", undefined, STATUS_TIMEOUT_MS);
|
||||
@@ -174,14 +191,55 @@ async function bridgeAlive(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
async function startFirefox(profile: "dedicated" | "main"): Promise<void> {
|
||||
const cmd = profile === "main" ? "start-main" : "start";
|
||||
// I tool non dispongono di stdin interattivo: il profilo principale deve
|
||||
// confermare esplicitamente la chiusura di un Firefox già attivo. Il tool
|
||||
// documenta già tale effetto e conserva cookie/login al riavvio.
|
||||
const env = profile === "main"
|
||||
? { ...process.env, FIREFOX_BIDI_FORCE: "1" }
|
||||
: process.env;
|
||||
async function startFirefox(
|
||||
profile: "dedicated" | "main",
|
||||
action: ConflictAction = "ask",
|
||||
ctx?: any,
|
||||
): Promise<void> {
|
||||
let effectiveProfile = profile;
|
||||
let resolvedAction = action;
|
||||
|
||||
if (profile === "main") {
|
||||
const conflict = await checkFirefoxConflict();
|
||||
if (conflict.firefoxRunning || conflict.portBusy) {
|
||||
if (resolvedAction === "ask") {
|
||||
if (ctx?.ui?.select) {
|
||||
const prompt =
|
||||
conflict.firefoxRunning
|
||||
? "Firefox o il profilo utente predefinito sono già attivi in un'altra sessione. Come desideri procedere?"
|
||||
: "La porta WebDriver BiDi (9222) è già occupata da un'altra sessione. Come desideri procedere?";
|
||||
const choice = await ctx.ui.select(prompt, [
|
||||
"Chiudi il browser/sessione attiva e avvia con BiDi (profilo principale)",
|
||||
"Attendi la fine dell'altra sessione",
|
||||
"Apri un profilo separato/indipendente (bidi-profile)",
|
||||
"Annulla operazione",
|
||||
]);
|
||||
|
||||
if (!choice || choice.includes("Annulla")) {
|
||||
throw new Error("Avvio Firefox BiDi annullato dall'utente.");
|
||||
} else if (choice.includes("Chiudi")) {
|
||||
resolvedAction = "close";
|
||||
} else if (choice.includes("Attendi")) {
|
||||
resolvedAction = "wait";
|
||||
} else if (choice.includes("separato")) {
|
||||
resolvedAction = "dedicated";
|
||||
effectiveProfile = "dedicated";
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
"Conflitto rilevato: Firefox o il profilo principale sono già in esecuzione in un'altra sessione. " +
|
||||
"Specifica il parametro 'onConflict' ('close', 'wait', 'dedicated') per scegliere l'azione desiderata.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cmd = effectiveProfile === "main" ? "start-main" : "start";
|
||||
const env = {
|
||||
...process.env,
|
||||
FIREFOX_BIDI_ACTION: resolvedAction,
|
||||
};
|
||||
await execFileAsync(SCRIPT, [cmd], { timeout: 90_000, env });
|
||||
}
|
||||
|
||||
@@ -304,10 +362,12 @@ function isPortOpen(port: number, host = "127.0.0.1"): Promise<boolean> {
|
||||
|
||||
async function ensureBridge(
|
||||
profile: "dedicated" | "main" = "dedicated",
|
||||
onConflict: ConflictAction = "ask",
|
||||
ctx?: any,
|
||||
): Promise<void> {
|
||||
if (await bridgeAlive()) return;
|
||||
if (!(await isPortOpen(9222))) {
|
||||
await startFirefox(profile);
|
||||
await startFirefox(profile, onConflict, ctx);
|
||||
} else {
|
||||
log("Firefox già attivo su 9222: avvio solo il bridge");
|
||||
}
|
||||
@@ -343,8 +403,10 @@ export default function (pi: ExtensionAPI) {
|
||||
label: "Browser Start",
|
||||
description:
|
||||
"Avvia Firefox con WebDriver BiDi (porta 9222) e il Browser Bridge. " +
|
||||
"profile=main usa il profilo principale (login/cookie intatti, chiude l'istanza attiva); " +
|
||||
"profile=dedicated (default) usa il profilo di automazione bidi-profile.",
|
||||
"profile=main usa il profilo principale (login/cookie reali); " +
|
||||
"profile=dedicated (default) usa il profilo di automazione isolato bidi-profile. " +
|
||||
"In caso di conflitto su profilo o porta già attivi, consente di chiedere all'utente o " +
|
||||
"specificare l'azione desiderata (close, wait, dedicated).",
|
||||
parameters: Type.Object({
|
||||
profile: Type.Optional(
|
||||
Type.Enum(
|
||||
@@ -352,9 +414,20 @@ export default function (pi: ExtensionAPI) {
|
||||
{ description: "Profilo Firefox: dedicated (automazione) o main (login reali)", default: "dedicated" },
|
||||
),
|
||||
),
|
||||
onConflict: Type.Optional(
|
||||
Type.Enum(
|
||||
{ close: "close", wait: "wait", dedicated: "dedicated", ask: "ask" },
|
||||
{
|
||||
description:
|
||||
"Azione se il profilo/browser è già attivo: 'close' (chiude l'istanza e avvia BiDi), " +
|
||||
"'wait' (attende il rilascio), 'dedicated' (avvia un profilo indipendente), 'ask' (chiede conferma all'utente)",
|
||||
default: "ask",
|
||||
},
|
||||
),
|
||||
),
|
||||
}),
|
||||
async execute(_id, params) {
|
||||
await ensureBridge(params.profile ?? "dedicated");
|
||||
async execute(_id, params, _signal, _onUpdate, ctx) {
|
||||
await ensureBridge(params.profile ?? "dedicated", params.onConflict ?? "ask", ctx);
|
||||
const st = await bridge("/status");
|
||||
return {
|
||||
content: [{ type: "text", text: `Firefox BiDi attivo: ${JSON.stringify(st)}` }],
|
||||
|
||||
Reference in New Issue
Block a user