panrouter 1.4.0 → 1.4.2
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/cli.mjs +1 -5
- package/daemon.mjs +54 -74
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -160,11 +160,7 @@ function startTray() {
|
|
|
160
160
|
|
|
161
161
|
log("..", "正在以托盘模式启动 Pan Router...", "yellow");
|
|
162
162
|
|
|
163
|
-
const child = spawn(process.execPath, [
|
|
164
|
-
daemonPath,
|
|
165
|
-
`--serverPath="${path.join(__dirname, "server.mjs")}"`,
|
|
166
|
-
`--trayPsPath="${path.join(__dirname, "tray-daemon.ps1")}"`,
|
|
167
|
-
], {
|
|
163
|
+
const child = spawn(process.execPath, [daemonPath], {
|
|
168
164
|
stdio: "ignore",
|
|
169
165
|
windowsHide: true,
|
|
170
166
|
detached: true,
|
package/daemon.mjs
CHANGED
|
@@ -3,20 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Pan Router Daemon — 后台守护进程
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* 让 server.mjs + tray-daemon.ps1 在无窗口下运行。
|
|
7
|
+
* server.mjs 用 detached + windowsHide 启动。
|
|
8
|
+
* tray-daemon.ps1 用 pipe 连接 (不 detached — 否则失去 Window Station)。
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
* daemon.mjs
|
|
11
|
-
* ├─ spawn server.mjs (hidden, UseShellExecute=true)
|
|
12
|
-
* └─ spawn powershell tray-daemon.ps1 (pipe, NOT detached)
|
|
13
|
-
* └─ NotifyIcon (右下角)
|
|
10
|
+
* 用法 (由 cli.mjs --tray 调用):
|
|
11
|
+
* node daemon.mjs
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
* 否则失去 Window Station 无法创建 UI 通知区图标。
|
|
13
|
+
* 所有路径自动从 __dirname 解析 (同包目录)。
|
|
17
14
|
*/
|
|
18
15
|
|
|
19
|
-
import { spawn
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
20
17
|
import { createInterface } from "node:readline";
|
|
21
18
|
import { fileURLToPath } from "node:url";
|
|
22
19
|
import path from "node:path";
|
|
@@ -24,74 +21,60 @@ import fs from "node:fs";
|
|
|
24
21
|
import http from "node:http";
|
|
25
22
|
|
|
26
23
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const appDir = __dirname;
|
|
25
|
+
const serverPath = path.join(appDir, "server.mjs");
|
|
26
|
+
const trayPsPath = path.join(appDir, "tray-daemon.ps1");
|
|
27
27
|
|
|
28
|
-
// ─── 解析参数 ────────────────────────────────────
|
|
29
|
-
const serverPath = process.argv.find(a => a.startsWith("--serverPath="))?.split("=")[1];
|
|
30
|
-
const trayPsPath = process.argv.find(a => a.startsWith("--trayPsPath="))?.split("=")[1];
|
|
31
|
-
if (!serverPath || !trayPsPath) { process.exit(1); }
|
|
32
|
-
|
|
33
|
-
const appDir = path.dirname(serverPath);
|
|
34
28
|
const LOG = path.join(process.env.TEMP || "/tmp", "panrouter-daemon.log");
|
|
35
|
-
|
|
36
29
|
function log(msg) {
|
|
37
30
|
try { fs.appendFileSync(LOG, `${new Date().toISOString().slice(11,19)} ${msg}\n`); } catch {}
|
|
38
31
|
}
|
|
39
32
|
|
|
40
|
-
log("=== PanRouter Daemon (
|
|
33
|
+
log("=== PanRouter Daemon (v3) ===");
|
|
41
34
|
log(`serverPath=${serverPath}`);
|
|
35
|
+
log(`trayPsPath=${trayPsPath}`);
|
|
42
36
|
|
|
43
37
|
// ─── 健康检查 ─────────────────────────────────────
|
|
44
38
|
function isOnline() {
|
|
45
39
|
return new Promise(rs => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
try {
|
|
41
|
+
const req = http.get("http://127.0.0.1:50816/health", () => {});
|
|
42
|
+
req.on("response", () => { req.destroy(); rs(true); });
|
|
43
|
+
req.on("error", () => rs(false));
|
|
44
|
+
req.setTimeout(1500, () => { req.destroy(); rs(false); });
|
|
45
|
+
} catch { rs(false); }
|
|
50
46
|
});
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
// ─── 启动隐藏的 server.mjs ────────────────────────
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
} catch {}
|
|
71
|
-
|
|
72
|
-
log("Starting server...");
|
|
73
|
-
const child = spawn("cmd.exe", ["/c", "start", "/B", "node", serverPath], {
|
|
74
|
-
cwd: appDir,
|
|
75
|
-
stdio: "ignore",
|
|
76
|
-
windowsHide: true,
|
|
77
|
-
shell: false,
|
|
78
|
-
});
|
|
79
|
-
child.unref();
|
|
80
|
-
log(`Server started via cmd /c start /B`);
|
|
81
|
-
return child;
|
|
82
|
-
})();
|
|
83
|
-
|
|
84
|
-
// ─── 等服务器就绪 ─────────────────────────────────
|
|
50
|
+
log("Starting server...");
|
|
51
|
+
const serverProcess = spawn(process.execPath, [serverPath], {
|
|
52
|
+
cwd: appDir,
|
|
53
|
+
stdio: "ignore",
|
|
54
|
+
windowsHide: true,
|
|
55
|
+
detached: true,
|
|
56
|
+
shell: false,
|
|
57
|
+
});
|
|
58
|
+
serverProcess.on("error", (err) => log(`Server spawn ERROR: ${err.message}`));
|
|
59
|
+
serverProcess.unref();
|
|
60
|
+
log(`Server spawned PID=${serverProcess.pid || "(no pid)"}`);
|
|
61
|
+
|
|
62
|
+
// ─── 等服务器就绪后启动 PS 托盘 ──────────────────
|
|
85
63
|
(async () => {
|
|
86
64
|
let ready = false;
|
|
87
65
|
for (let i = 0; i < 20; i++) {
|
|
88
66
|
if (await isOnline()) { ready = true; break; }
|
|
89
67
|
await new Promise(r => setTimeout(r, 500));
|
|
90
68
|
}
|
|
91
|
-
log(`Server
|
|
69
|
+
log(`Server online=${ready}`);
|
|
70
|
+
|
|
71
|
+
if (!fs.existsSync(trayPsPath)) {
|
|
72
|
+
log(`ERROR: tray-daemon.ps1 not found at ${trayPsPath}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
92
75
|
|
|
93
76
|
// ─── 启动 PS 托盘 (pipe, 不 detached!) ──────────
|
|
94
|
-
//
|
|
77
|
+
// 9Router 的做法: PS 必须与 daemon 同 session 才能创建通知图标
|
|
95
78
|
log("Starting PS tray...");
|
|
96
79
|
const psProcess = spawn("powershell.exe", [
|
|
97
80
|
"-NoProfile",
|
|
@@ -102,38 +85,35 @@ const serverProcess = (() => {
|
|
|
102
85
|
stdio: ["pipe", "pipe", "pipe"],
|
|
103
86
|
windowsHide: true,
|
|
104
87
|
shell: false,
|
|
105
|
-
// ⚠ 没有 detached
|
|
88
|
+
// ⚠ 没有 detached — PS 保持子进程身份才能访问 Window Station
|
|
106
89
|
});
|
|
107
90
|
|
|
108
|
-
|
|
91
|
+
psProcess.on("error", (err) => log(`PS spawn ERROR: ${err.message}`));
|
|
92
|
+
|
|
93
|
+
// 读取 PS 事件 (stdout)
|
|
109
94
|
createInterface({ input: psProcess.stdout }).on("line", (line) => {
|
|
110
95
|
try {
|
|
111
96
|
const evt = JSON.parse(line);
|
|
112
|
-
log(`PS
|
|
113
|
-
if (evt.type === "click") {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
psSend({ action: "kill" });
|
|
118
|
-
setTimeout(() => process.exit(0), 500);
|
|
119
|
-
}
|
|
97
|
+
log(`PS: ${evt.type} idx=${evt.index}`);
|
|
98
|
+
if (evt.type === "click" && evt.index === 2) {
|
|
99
|
+
log("Exit from PS menu");
|
|
100
|
+
try { psProcess.stdin.write(JSON.stringify({ action: "kill" }) + "\n"); } catch {}
|
|
101
|
+
setTimeout(() => process.exit(0), 300);
|
|
120
102
|
}
|
|
121
|
-
} catch
|
|
103
|
+
} catch {}
|
|
122
104
|
});
|
|
123
105
|
|
|
124
|
-
psProcess.on("
|
|
125
|
-
psProcess.
|
|
126
|
-
psProcess.on("exit", code => {
|
|
106
|
+
psProcess.stderr.on("data", (d) => log(`[ps-err] ${d.toString().trim()}`));
|
|
107
|
+
psProcess.on("exit", (code) => {
|
|
127
108
|
log(`PS exited code=${code}`);
|
|
128
109
|
process.exit(0);
|
|
129
110
|
});
|
|
130
111
|
|
|
131
|
-
//
|
|
112
|
+
// 发送菜单配置
|
|
132
113
|
function psSend(cmd) {
|
|
133
|
-
|
|
134
|
-
psProcess.stdin.write(JSON.stringify(cmd) + "\n");
|
|
135
|
-
}
|
|
114
|
+
try { psProcess.stdin.write(JSON.stringify(cmd) + "\n"); } catch {}
|
|
136
115
|
}
|
|
116
|
+
await new Promise(r => setTimeout(r, 500)); // 等 PS 就绪
|
|
137
117
|
psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
|
|
138
118
|
psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
|
|
139
119
|
psSend({ action: "add-item", index: 2, title: "退出", enabled: true });
|
|
@@ -144,4 +124,4 @@ const serverProcess = (() => {
|
|
|
144
124
|
})();
|
|
145
125
|
|
|
146
126
|
process.on("SIGTERM", () => { log("SIGTERM"); process.exit(0); });
|
|
147
|
-
process.on("SIGINT",
|
|
127
|
+
process.on("SIGINT", () => { log("SIGINT"); process.exit(0); });
|