fix terminal popup release behavior (#1403)

This commit is contained in:
陈大猫
2026-06-11 14:48:52 +08:00
committed by GitHub
parent afefbd953f
commit 9013a7e312
29 changed files with 705 additions and 84 deletions

View File

@@ -128,6 +128,10 @@ function captureError(source, err, extra) {
}
}
function captureDiagnostic(source, message, extra) {
captureError(source, new Error(String(message || "diagnostic")), extra);
}
/**
* Delete log files older than LOG_RETENTION_DAYS.
*/
@@ -317,10 +321,21 @@ function registerHandlers(ipcMain) {
ipcMain.handle("netcatty:crashLogs:read", async (_event, { fileName }) => readLog(fileName));
ipcMain.handle("netcatty:crashLogs:clear", async () => clearLogs());
ipcMain.handle("netcatty:crashLogs:openDir", async () => openDir());
ipcMain.handle("netcatty:diagnostics:log", async (_event, payload) => {
const source = typeof payload?.source === "string" && payload.source.trim()
? payload.source.trim()
: "renderer-diagnostic";
const message = typeof payload?.message === "string" && payload.message.trim()
? payload.message.trim()
: "diagnostic";
captureDiagnostic(source, message, payload?.extra);
return { success: true };
});
}
module.exports = {
init,
captureError,
captureDiagnostic,
registerHandlers,
};

View File

@@ -1,5 +1,7 @@
/* eslint-disable no-undef */
const crashLogBridge = require("../crashLogBridge.cjs");
function createTerminalPopupWindowApi(ctx) {
with (ctx) {
const terminalPopupWindows = new Map();
@@ -29,6 +31,14 @@ function createTerminalPopupWindowApi(ctx) {
? payload.title.trim()
: "Terminal";
crashLogBridge.captureDiagnostic("terminal-popup", "creating popup window", {
popupId: payload?.popupId,
title,
isDev,
popupX,
popupY,
});
const win = new BrowserWindow({
title,
width: popupWidth,
@@ -39,9 +49,8 @@ function createTerminalPopupWindowApi(ctx) {
backgroundColor,
icon: appIcon,
show: false,
frame: isMac,
titleBarStyle: isMac ? "hiddenInset" : undefined,
trafficLightPosition: isMac ? { x: 12, y: 12 } : undefined,
frame: false,
...(isMac ? { trafficLightPosition: { x: 12, y: 12 } } : {}),
webPreferences: {
preload,
contextIsolation: true,
@@ -53,6 +62,11 @@ function createTerminalPopupWindowApi(ctx) {
const popupId = String(payload?.popupId || Date.now());
terminalPopupWindows.set(popupId, win);
crashLogBridge.captureDiagnostic("terminal-popup", "popup BrowserWindow created", {
popupId,
title,
webContentsId: win.webContents?.id,
});
try {
win.webContents?.setWindowOpenHandler?.(
@@ -66,6 +80,30 @@ function createTerminalPopupWindowApi(ctx) {
terminalPopupWindows.delete(popupId);
});
try {
win.webContents?.on?.("did-fail-load", (_event, errorCode, errorDescription, validatedURL) => {
console.warn("[TerminalPopup] Failed to load renderer", {
popupId,
errorCode,
errorDescription,
validatedURL,
});
});
win.webContents?.on?.("render-process-gone", (_event, details) => {
console.warn("[TerminalPopup] Renderer process gone", { popupId, details });
});
win.webContents?.on?.("console-message", (_event, level, message, line, sourceId) => {
crashLogBridge.captureDiagnostic("terminal-popup-console", message, {
popupId,
level,
line,
sourceId,
});
});
} catch {
// ignore diagnostics wiring failures
}
win.on("page-title-updated", (e) => { e.preventDefault(); });
try {
@@ -76,34 +114,56 @@ function createTerminalPopupWindowApi(ctx) {
applyWindowOpacityToWindow(win);
const popupPath = "/#/terminal-popup";
if (isMac) {
try {
win.setWindowButtonVisibility(true);
} catch {
// ignore
}
try {
win.setWindowButtonPosition({ x: 12, y: 12 });
} catch {
// ignore
}
}
const popupPath = "#/terminal-popup";
if (isDev) {
try {
const baseUrl = getDevRendererBaseUrl(devServerUrl);
crashLogBridge.captureDiagnostic("terminal-popup", "loading dev popup URL", {
popupId,
url: `${baseUrl}${popupPath}`,
});
await win.loadURL(`${baseUrl}${popupPath}`);
} catch (e) {
console.warn("[TerminalPopup] Dev server not reachable", e);
crashLogBridge.captureError("terminal-popup", e, {
popupId,
step: "load dev popup URL",
});
await win.loadURL(`app://netcatty/index.html${popupPath}`);
}
} else {
crashLogBridge.captureDiagnostic("terminal-popup", "loading packaged popup URL", {
popupId,
url: `app://netcatty/index.html${popupPath}`,
});
await win.loadURL(`app://netcatty/index.html${popupPath}`);
}
const delivery = await sendWhenRendererReady(
win,
"netcatty:window:terminalPopupConfig",
{ ...payload, popupId },
{ timeoutMs: 10000 },
);
if (!delivery.success) {
try { win.destroy(); } catch { /* ignore */ }
terminalPopupWindows.delete(popupId);
return { success: false, error: delivery.error || "Popup failed to receive config" };
}
win.webContents.send("netcatty:window:terminalPopupConfig", { ...payload, popupId });
crashLogBridge.captureDiagnostic("terminal-popup", "popup config delivered", {
popupId,
title,
});
showAndFocusWindow(win);
crashLogBridge.captureDiagnostic("terminal-popup", "popup window shown", {
popupId,
title,
visible: typeof win.isVisible === "function" ? win.isVisible() : undefined,
});
return { success: true, popupId };
}

View File

@@ -360,8 +360,16 @@ function createBridgeRegistrar(context) {
if (!payload || typeof payload !== "object") {
return { success: false, error: "Invalid popup payload" };
}
crashLogBridge.captureDiagnostic("terminal-popup", "openTerminalPopup IPC received", {
title: payload.title,
parentSessionId: payload.parentSessionId,
startupCommand: payload.startupCommand,
sourceSessionId: payload.sourceSession?.id,
sourceProtocol: payload.sourceSession?.protocol,
sourceHostLabel: payload.sourceSession?.hostLabel,
});
const sourceWindow = BrowserWindow.fromWebContents(event.sender);
return await getWindowManager().openTerminalPopupWindow(electronModule, {
const result = await getWindowManager().openTerminalPopupWindow(electronModule, {
preload,
devServerUrl: effectiveDevServerUrl,
isDev,
@@ -370,7 +378,18 @@ function createBridgeRegistrar(context) {
electronDir,
sourceWindow,
}, payload);
crashLogBridge.captureDiagnostic("terminal-popup", "openTerminalPopup IPC result", {
title: payload.title,
success: result?.success,
error: result?.error,
popupId: result?.popupId,
});
return result;
} catch (err) {
crashLogBridge.captureError("terminal-popup", err, {
title: payload?.title,
parentSessionId: payload?.parentSessionId,
});
console.error("[Main] Failed to open terminal popup:", err);
return { success: false, error: err?.message || "Failed to open terminal popup" };
}

View File

@@ -31,6 +31,10 @@ const updateAvailableListeners = new Set();
const updateNotAvailableListeners = new Set();
const updateErrorListeners = new Set();
const updateNeedsSaveListeners = new Set();
const terminalPopupConfigState = {
pending: null,
listeners: new Set(),
};
function cleanupTransferListeners(transferId) {
transferProgressListeners.delete(transferId);
@@ -129,6 +133,20 @@ ipcRenderer.on("netcatty:zmodem:detect", (_event, payload) => {
if (!set) return;
set.forEach((cb) => { try { cb({ type: "detect", ...payload }); } catch {} });
});
ipcRenderer.on("netcatty:window:terminalPopupConfig", (_event, payload) => {
if (terminalPopupConfigState.listeners.size === 0) {
terminalPopupConfigState.pending = payload;
return;
}
terminalPopupConfigState.listeners.forEach((cb) => {
try {
cb(payload);
} catch (err) {
console.error("Terminal popup config callback failed", err);
}
});
});
ipcRenderer.on("netcatty:zmodem:progress", (_event, payload) => {
const set = zmodemListeners.get(payload.sessionId);
if (!set) return;
@@ -656,6 +674,7 @@ const api = createPreloadApi({
updateNotAvailableListeners,
updateErrorListeners,
updateNeedsSaveListeners,
terminalPopupConfigState,
uploadProgressListeners,
uploadCompleteListeners,
uploadErrorListeners,

View File

@@ -128,10 +128,23 @@ function createPreloadApi(ctx) {
openTerminalPopup: async (payload) => {
return ipcRenderer.invoke("netcatty:window:openTerminalPopup", payload);
},
logDiagnostic: async (payload) => {
return ipcRenderer.invoke("netcatty:diagnostics:log", payload);
},
onTerminalPopupConfig: (cb) => {
const handler = (_event, payload) => cb(payload);
ipcRenderer.on("netcatty:window:terminalPopupConfig", handler);
return () => ipcRenderer.removeListener("netcatty:window:terminalPopupConfig", handler);
terminalPopupConfigState.listeners.add(cb);
if (terminalPopupConfigState.pending) {
const pending = terminalPopupConfigState.pending;
terminalPopupConfigState.pending = null;
queueMicrotask(() => {
try {
cb(pending);
} catch (err) {
console.error("Terminal popup config callback failed", err);
}
});
}
return () => terminalPopupConfigState.listeners.delete(cb);
},
readRemoteHistory: async (sessionId, limit) => {
return ipcRenderer.invoke("netcatty:ssh:readRemoteHistory", { sessionId, limit });