panrouter 1.4.1 → 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 -75
- 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,76 +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
|
-
// 用 node 直接 spawn 子进程, 避免 cmd /c 的额外 shell
|
|
74
|
-
const child = spawn(process.execPath, [serverPath], {
|
|
75
|
-
cwd: appDir,
|
|
76
|
-
stdio: "ignore",
|
|
77
|
-
windowsHide: true,
|
|
78
|
-
detached: true,
|
|
79
|
-
shell: false,
|
|
80
|
-
});
|
|
81
|
-
child.unref();
|
|
82
|
-
log(`Server started PID=${child.pid}`);
|
|
83
|
-
return child;
|
|
84
|
-
})();
|
|
85
|
-
|
|
86
|
-
// ─── 等服务器就绪 ─────────────────────────────────
|
|
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 托盘 ──────────────────
|
|
87
63
|
(async () => {
|
|
88
64
|
let ready = false;
|
|
89
65
|
for (let i = 0; i < 20; i++) {
|
|
90
66
|
if (await isOnline()) { ready = true; break; }
|
|
91
67
|
await new Promise(r => setTimeout(r, 500));
|
|
92
68
|
}
|
|
93
|
-
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
|
+
}
|
|
94
75
|
|
|
95
76
|
// ─── 启动 PS 托盘 (pipe, 不 detached!) ──────────
|
|
96
|
-
//
|
|
77
|
+
// 9Router 的做法: PS 必须与 daemon 同 session 才能创建通知图标
|
|
97
78
|
log("Starting PS tray...");
|
|
98
79
|
const psProcess = spawn("powershell.exe", [
|
|
99
80
|
"-NoProfile",
|
|
@@ -104,37 +85,35 @@ const serverProcess = (() => {
|
|
|
104
85
|
stdio: ["pipe", "pipe", "pipe"],
|
|
105
86
|
windowsHide: true,
|
|
106
87
|
shell: false,
|
|
107
|
-
// ⚠ 没有 detached
|
|
88
|
+
// ⚠ 没有 detached — PS 保持子进程身份才能访问 Window Station
|
|
108
89
|
});
|
|
109
90
|
|
|
110
|
-
|
|
91
|
+
psProcess.on("error", (err) => log(`PS spawn ERROR: ${err.message}`));
|
|
92
|
+
|
|
93
|
+
// 读取 PS 事件 (stdout)
|
|
111
94
|
createInterface({ input: psProcess.stdout }).on("line", (line) => {
|
|
112
95
|
try {
|
|
113
96
|
const evt = JSON.parse(line);
|
|
114
|
-
log(`PS
|
|
115
|
-
if (evt.type === "click") {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
setTimeout(() => process.exit(0), 500);
|
|
120
|
-
}
|
|
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);
|
|
121
102
|
}
|
|
122
|
-
} catch
|
|
103
|
+
} catch {}
|
|
123
104
|
});
|
|
124
105
|
|
|
125
|
-
psProcess.on("
|
|
126
|
-
psProcess.
|
|
127
|
-
psProcess.on("exit", code => {
|
|
106
|
+
psProcess.stderr.on("data", (d) => log(`[ps-err] ${d.toString().trim()}`));
|
|
107
|
+
psProcess.on("exit", (code) => {
|
|
128
108
|
log(`PS exited code=${code}`);
|
|
129
109
|
process.exit(0);
|
|
130
110
|
});
|
|
131
111
|
|
|
132
|
-
//
|
|
112
|
+
// 发送菜单配置
|
|
133
113
|
function psSend(cmd) {
|
|
134
|
-
|
|
135
|
-
psProcess.stdin.write(JSON.stringify(cmd) + "\n");
|
|
136
|
-
}
|
|
114
|
+
try { psProcess.stdin.write(JSON.stringify(cmd) + "\n"); } catch {}
|
|
137
115
|
}
|
|
116
|
+
await new Promise(r => setTimeout(r, 500)); // 等 PS 就绪
|
|
138
117
|
psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
|
|
139
118
|
psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
|
|
140
119
|
psSend({ action: "add-item", index: 2, title: "退出", enabled: true });
|
|
@@ -145,4 +124,4 @@ const serverProcess = (() => {
|
|
|
145
124
|
})();
|
|
146
125
|
|
|
147
126
|
process.on("SIGTERM", () => { log("SIGTERM"); process.exit(0); });
|
|
148
|
-
process.on("SIGINT",
|
|
127
|
+
process.on("SIGINT", () => { log("SIGINT"); process.exit(0); });
|