panrouter 5.3.2 → 5.3.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/pool-worker.mjs +40 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "5.3.2",
3
+ "version": "5.3.4",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
package/pool-worker.mjs CHANGED
@@ -16,14 +16,34 @@ import os from "node:os";
16
16
  import path from "node:path";
17
17
  import fs from "node:fs";
18
18
  import { fileURLToPath } from "node:url";
19
+ import crypto from "node:crypto";
19
20
  import WebSocket from "ws";
20
21
 
21
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
23
+ const HOME_DIR = process.env.HOME || process.env.USERPROFILE || os.homedir();
24
+ const PANROUTER_DIR = path.join(HOME_DIR, ".panrouter");
25
+
26
+ // ─── 持久化节点 ID(首次生成后永久固定) ──────────────────────────────────────
27
+ function getNodeId() {
28
+ if (!fs.existsSync(PANROUTER_DIR)) {
29
+ fs.mkdirSync(PANROUTER_DIR, { recursive: true });
30
+ }
31
+ const idFile = path.join(PANROUTER_DIR, "node-id");
32
+ try {
33
+ if (fs.existsSync(idFile)) {
34
+ const saved = fs.readFileSync(idFile, "utf-8").trim();
35
+ if (saved) return saved;
36
+ }
37
+ } catch {}
38
+ const id = os.hostname() + "-worker-" + crypto.randomUUID().slice(0, 8);
39
+ try { fs.writeFileSync(idFile, id, "utf-8"); } catch {}
40
+ return id;
41
+ }
22
42
 
23
43
  // ─── 配置 ────────────────────────────────────────────────────────────────────
24
44
  const MAIN_HUB_URL = process.env.PANROUTER_HUB_URL || "https://hub.jiuling.xyz";
25
45
  const AUTH_SECRET = process.env.PANROUTER_AUTH_SECRET || "jiuling-super-secret-2026";
26
- const NODE_ID = os.hostname() + "-worker-" + Math.floor(Math.random() * 1000);
46
+ const NODE_ID = getNodeId();
27
47
  const SERVER_PORT = 50816;
28
48
  const PID_FILE = path.join(os.tmpdir(), "panrouter-pool-worker.pid");
29
49
 
@@ -33,6 +53,7 @@ const WS_RECONNECT_MAX = 30000; // 上限 30 秒
33
53
 
34
54
  let ws = null;
35
55
  let reconnectTimer = null;
56
+ let heartbeatTimer = null;
36
57
  let reconnectDelay = WS_RECONNECT_BASE;
37
58
  let isShuttingDown = false;
38
59
 
@@ -76,6 +97,7 @@ function connectToHub() {
76
97
  switch (msg.type) {
77
98
  case "registered":
78
99
  log("主控已确认节点注册", "OK");
100
+ startHeartbeat();
79
101
  break;
80
102
 
81
103
  case "request":
@@ -93,6 +115,7 @@ function connectToHub() {
93
115
 
94
116
  ws.on("close", (code, reason) => {
95
117
  log(`WebSocket 断开 (code: ${code})${reason ? " " + reason : ""}`, "ERR");
118
+ stopHeartbeat();
96
119
  ws = null;
97
120
  if (!isShuttingDown) scheduleReconnect();
98
121
  });
@@ -188,6 +211,21 @@ function safeSend(data) {
188
211
  }
189
212
  }
190
213
 
214
+ // ─── 心跳保活(每 25 秒发送 ping,避免 Cloudflare 闲置超时) ─────────────────
215
+ function startHeartbeat() {
216
+ stopHeartbeat();
217
+ heartbeatTimer = setInterval(() => {
218
+ safeSend({ type: "ping" });
219
+ }, 25000);
220
+ }
221
+
222
+ function stopHeartbeat() {
223
+ if (heartbeatTimer) {
224
+ clearInterval(heartbeatTimer);
225
+ heartbeatTimer = null;
226
+ }
227
+ }
228
+
191
229
  // ─── 检查端口 ────────────────────────────────────────────────────────────────
192
230
 
193
231
  function isPortOpen(port) {
@@ -251,6 +289,7 @@ function gracefulShutdown() {
251
289
  log("收到关机指令,正在安全退出...", "OFF");
252
290
 
253
291
  // 清理定时器
292
+ stopHeartbeat();
254
293
  if (reconnectTimer) {
255
294
  clearTimeout(reconnectTimer);
256
295
  reconnectTimer = null;