Skip to content

Commit 62860a5

Browse files
committed
feat: 实现单实例锁机制,防止重复启动;优化终端清屏逻辑
1 parent 9feda2d commit 62860a5

2 files changed

Lines changed: 84 additions & 3 deletions

File tree

electron/main.js

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
1+
const { app, BrowserWindow, Menu, shell, ipcMain, dialog } = require('electron');
22
const path = require('path');
33
const net = require('net');
44

@@ -7,6 +7,21 @@ const DEFAULT_PORT = 3000;
77
let backendServer = null;
88
let currentBackendPort = null;
99

10+
// 单实例锁
11+
const gotTheLock = app.requestSingleInstanceLock();
12+
13+
if (!gotTheLock) {
14+
// 已有实例在运行,退出当前实例
15+
// 注意:不能在这里直接调用 dialog,因为 app 可能还未准备好
16+
console.log('WebSSH 应用已在运行中,退出当前实例...');
17+
18+
// 使用延迟退出,确保应用完全退出
19+
process.nextTick(() => {
20+
app.quit();
21+
process.exit(0);
22+
});
23+
}
24+
1025
function isPortInUse(port) {
1126
return new Promise((resolve) => {
1227
const server = net.createServer();
@@ -19,6 +34,29 @@ function isPortInUse(port) {
1934
});
2035
}
2136

37+
// 检查应用是否已在运行(端口检测作为后备)
38+
async function isAppAlreadyRunning() {
39+
// 检查默认端口是否被占用
40+
const defaultPortInUse = await isPortInUse(DEFAULT_PORT);
41+
if (defaultPortInUse) {
42+
// 检查是否是WebSSH后端(发送HTTP请求验证)
43+
try {
44+
const response = await new Promise((resolve) => {
45+
const http = require('http');
46+
const req = http.get(`http://localhost:${DEFAULT_PORT}/api/crypto/public-key`, (res) => {
47+
resolve(res.statusCode === 200);
48+
});
49+
req.on('error', () => resolve(false));
50+
req.setTimeout(1000, () => resolve(false));
51+
});
52+
return response;
53+
} catch (error) {
54+
return false;
55+
}
56+
}
57+
return false;
58+
}
59+
2260
function getAvailablePort(startPort = DEFAULT_PORT, maxPort = 3100) {
2361
return new Promise((resolve) => {
2462
const tryPort = (port) => {
@@ -195,7 +233,44 @@ ipcMain.handle('get-backend-port', () => {
195233
return currentBackendPort || DEFAULT_PORT;
196234
});
197235

198-
app.whenReady().then(() => {
236+
// 当第二个实例启动时,激活已有窗口
237+
app.on('second-instance', (event, commandLine, workingDirectory) => {
238+
// 有人尝试运行第二个实例,我们应该聚焦到已有窗口
239+
if (mainWindow) {
240+
if (mainWindow.isMinimized()) {
241+
mainWindow.restore();
242+
}
243+
mainWindow.focus();
244+
}
245+
});
246+
247+
app.whenReady().then(async () => {
248+
// 如果单实例锁获取失败,显示对话框并退出
249+
if (!gotTheLock) {
250+
dialog.showMessageBoxSync({
251+
type: 'info',
252+
title: 'WebSSH',
253+
message: 'WebSSH 应用已在运行中,请勿重复启动。',
254+
detail: '如果您需要启动新窗口,请在已运行的实例中打开新标签页。'
255+
});
256+
app.quit();
257+
return;
258+
}
259+
260+
// 额外检查应用是否已在运行
261+
const alreadyRunning = await isAppAlreadyRunning();
262+
if (alreadyRunning) {
263+
// 双重确认,如果检测到应用在运行,仍然退出
264+
dialog.showMessageBoxSync({
265+
type: 'info',
266+
title: 'WebSSH',
267+
message: '检测到 WebSSH 应用已在运行中,请勿重复启动。',
268+
detail: '如果您需要启动新窗口,请在已运行的实例中打开新标签页。'
269+
});
270+
app.quit();
271+
return;
272+
}
273+
199274
createWindow();
200275
});
201276

frontend/src/components/XtermTerminal.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,13 @@ const selectAllFromContextMenu = () => {
493493
}
494494
495495
const clearTerminal = () => {
496-
reset()
496+
if (props.isConnected) {
497+
// 如果连接状态,发送 clear 命令到后端
498+
emit('data', 'clear\n')
499+
} else {
500+
// 如果未连接状态,本地清屏
501+
clear()
502+
}
497503
hideContextMenu()
498504
}
499505

0 commit comments

Comments
 (0)