@caoruhua/open-claude-remote 0.1.0
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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/api/auth-routes.d.ts +4 -0
- package/dist/api/auth-routes.d.ts.map +1 -0
- package/dist/api/auth-routes.js +7 -0
- package/dist/api/auth-routes.js.map +1 -0
- package/dist/api/config-routes.d.ts +4 -0
- package/dist/api/config-routes.d.ts.map +1 -0
- package/dist/api/config-routes.js +180 -0
- package/dist/api/config-routes.js.map +1 -0
- package/dist/api/health-routes.d.ts +3 -0
- package/dist/api/health-routes.d.ts.map +1 -0
- package/dist/api/health-routes.js +9 -0
- package/dist/api/health-routes.js.map +1 -0
- package/dist/api/hook-routes.d.ts +4 -0
- package/dist/api/hook-routes.d.ts.map +1 -0
- package/dist/api/hook-routes.js +32 -0
- package/dist/api/hook-routes.js.map +1 -0
- package/dist/api/instance-routes.d.ts +20 -0
- package/dist/api/instance-routes.d.ts.map +1 -0
- package/dist/api/instance-routes.js +128 -0
- package/dist/api/instance-routes.js.map +1 -0
- package/dist/api/push-routes.d.ts +5 -0
- package/dist/api/push-routes.d.ts.map +1 -0
- package/dist/api/push-routes.js +45 -0
- package/dist/api/push-routes.js.map +1 -0
- package/dist/api/router.d.ts +19 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +37 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/status-routes.d.ts +5 -0
- package/dist/api/status-routes.d.ts.map +1 -0
- package/dist/api/status-routes.js +17 -0
- package/dist/api/status-routes.js.map +1 -0
- package/dist/attach.d.ts +9 -0
- package/dist/attach.d.ts.map +1 -0
- package/dist/attach.js +155 -0
- package/dist/attach.js.map +1 -0
- package/dist/auth/auth-middleware.d.ts +52 -0
- package/dist/auth/auth-middleware.d.ts.map +1 -0
- package/dist/auth/auth-middleware.js +124 -0
- package/dist/auth/auth-middleware.js.map +1 -0
- package/dist/auth/rate-limiter.d.ts +37 -0
- package/dist/auth/rate-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limiter.js +81 -0
- package/dist/auth/rate-limiter.js.map +1 -0
- package/dist/auth/token-generator.d.ts +9 -0
- package/dist/auth/token-generator.d.ts.map +1 -0
- package/dist/auth/token-generator.js +15 -0
- package/dist/auth/token-generator.js.map +1 -0
- package/dist/cli-utils.d.ts +19 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/cli-utils.js +132 -0
- package/dist/cli-utils.js.map +1 -0
- package/dist/cli.d.ts +21 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +146 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +329 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/hook-receiver.d.ts +39 -0
- package/dist/hooks/hook-receiver.d.ts.map +1 -0
- package/dist/hooks/hook-receiver.js +46 -0
- package/dist/hooks/hook-receiver.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +90 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/notification/dingtalk-service.d.ts +24 -0
- package/dist/notification/dingtalk-service.d.ts.map +1 -0
- package/dist/notification/dingtalk-service.js +94 -0
- package/dist/notification/dingtalk-service.js.map +1 -0
- package/dist/pty/output-buffer.d.ts +25 -0
- package/dist/pty/output-buffer.d.ts.map +1 -0
- package/dist/pty/output-buffer.js +58 -0
- package/dist/pty/output-buffer.js.map +1 -0
- package/dist/pty/pty-manager.d.ts +45 -0
- package/dist/pty/pty-manager.d.ts.map +1 -0
- package/dist/pty/pty-manager.js +108 -0
- package/dist/pty/pty-manager.js.map +1 -0
- package/dist/pty/types.d.ts +11 -0
- package/dist/pty/types.d.ts.map +1 -0
- package/dist/pty/types.js +2 -0
- package/dist/pty/types.js.map +1 -0
- package/dist/pty/virtual-pty.d.ts +37 -0
- package/dist/pty/virtual-pty.d.ts.map +1 -0
- package/dist/pty/virtual-pty.js +161 -0
- package/dist/pty/virtual-pty.js.map +1 -0
- package/dist/push/push-service.d.ts +87 -0
- package/dist/push/push-service.d.ts.map +1 -0
- package/dist/push/push-service.js +301 -0
- package/dist/push/push-service.js.map +1 -0
- package/dist/registry/instance-registry.d.ts +32 -0
- package/dist/registry/instance-registry.d.ts.map +1 -0
- package/dist/registry/instance-registry.js +115 -0
- package/dist/registry/instance-registry.js.map +1 -0
- package/dist/registry/instance-spawner.d.ts +33 -0
- package/dist/registry/instance-spawner.d.ts.map +1 -0
- package/dist/registry/instance-spawner.js +91 -0
- package/dist/registry/instance-spawner.js.map +1 -0
- package/dist/registry/port-finder.d.ts +8 -0
- package/dist/registry/port-finder.d.ts.map +1 -0
- package/dist/registry/port-finder.js +35 -0
- package/dist/registry/port-finder.js.map +1 -0
- package/dist/registry/shared-token.d.ts +11 -0
- package/dist/registry/shared-token.d.ts.map +1 -0
- package/dist/registry/shared-token.js +82 -0
- package/dist/registry/shared-token.js.map +1 -0
- package/dist/registry/stop-instances.d.ts +27 -0
- package/dist/registry/stop-instances.d.ts.map +1 -0
- package/dist/registry/stop-instances.js +212 -0
- package/dist/registry/stop-instances.js.map +1 -0
- package/dist/session/session-controller.d.ts +58 -0
- package/dist/session/session-controller.d.ts.map +1 -0
- package/dist/session/session-controller.js +273 -0
- package/dist/session/session-controller.js.map +1 -0
- package/dist/terminal/terminal-relay.d.ts +29 -0
- package/dist/terminal/terminal-relay.d.ts.map +1 -0
- package/dist/terminal/terminal-relay.js +106 -0
- package/dist/terminal/terminal-relay.js.map +1 -0
- package/dist/utils/ansi-filter.d.ts +41 -0
- package/dist/utils/ansi-filter.d.ts.map +1 -0
- package/dist/utils/ansi-filter.js +147 -0
- package/dist/utils/ansi-filter.js.map +1 -0
- package/dist/utils/file-lock.d.ts +23 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +125 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/ip-monitor.d.ts +39 -0
- package/dist/utils/ip-monitor.d.ts.map +1 -0
- package/dist/utils/ip-monitor.js +114 -0
- package/dist/utils/ip-monitor.js.map +1 -0
- package/dist/utils/network.d.ts +19 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +54 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/pid-file.d.ts +10 -0
- package/dist/utils/pid-file.d.ts.map +1 -0
- package/dist/utils/pid-file.js +30 -0
- package/dist/utils/pid-file.js.map +1 -0
- package/dist/utils/qrcode-banner.d.ts +6 -0
- package/dist/utils/qrcode-banner.d.ts.map +1 -0
- package/dist/utils/qrcode-banner.js +16 -0
- package/dist/utils/qrcode-banner.js.map +1 -0
- package/dist/ws/ws-handler.d.ts +10 -0
- package/dist/ws/ws-handler.d.ts.map +1 -0
- package/dist/ws/ws-handler.js +36 -0
- package/dist/ws/ws-handler.js.map +1 -0
- package/dist/ws/ws-server.d.ts +78 -0
- package/dist/ws/ws-server.d.ts.map +1 -0
- package/dist/ws/ws-server.js +223 -0
- package/dist/ws/ws-server.js.map +1 -0
- package/frontend-dist/assets/index-BKudo1Dw.css +32 -0
- package/frontend-dist/assets/index-BqqB1hYe.js +141 -0
- package/frontend-dist/index.html +15 -0
- package/frontend-dist/sw.js +46 -0
- package/package.json +79 -0
- package/shared-dist/constants.d.ts +10 -0
- package/shared-dist/constants.d.ts.map +1 -0
- package/shared-dist/constants.js +10 -0
- package/shared-dist/constants.js.map +1 -0
- package/shared-dist/defaults.d.ts +30 -0
- package/shared-dist/defaults.d.ts.map +1 -0
- package/shared-dist/defaults.js +31 -0
- package/shared-dist/defaults.js.map +1 -0
- package/shared-dist/index.d.ts +5 -0
- package/shared-dist/index.d.ts.map +1 -0
- package/shared-dist/index.js +5 -0
- package/shared-dist/index.js.map +1 -0
- package/shared-dist/instance.d.ts +25 -0
- package/shared-dist/instance.d.ts.map +1 -0
- package/shared-dist/instance.js +7 -0
- package/shared-dist/instance.js.map +1 -0
- package/shared-dist/question-utils.d.ts +15 -0
- package/shared-dist/question-utils.d.ts.map +1 -0
- package/shared-dist/question-utils.js +24 -0
- package/shared-dist/question-utils.js.map +1 -0
- package/shared-dist/ws-protocol.d.ts +60 -0
- package/shared-dist/ws-protocol.d.ts.map +1 -0
- package/shared-dist/ws-protocol.js +5 -0
- package/shared-dist/ws-protocol.js.map +1 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { mkdirSync, rmdirSync, statSync } from 'node:fs';
|
|
2
|
+
const DEFAULTS = {
|
|
3
|
+
retries: 50,
|
|
4
|
+
retryIntervalMs: 50,
|
|
5
|
+
staleMs: 10_000,
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* 尝试一次 mkdir 获取锁。
|
|
9
|
+
* mkdirSync 在 POSIX 上是原子操作:成功即持有锁,EEXIST 则需重试。
|
|
10
|
+
* 返回三种结果:获取成功 / 僵尸锁已清理可立即重试 / 需等待后重试。
|
|
11
|
+
*/
|
|
12
|
+
function tryOnce(lockPath, staleMs) {
|
|
13
|
+
try {
|
|
14
|
+
mkdirSync(lockPath);
|
|
15
|
+
return 'acquired';
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err.code !== 'EEXIST') {
|
|
19
|
+
throw err;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// 检查是否为僵尸锁
|
|
23
|
+
try {
|
|
24
|
+
const stat = statSync(lockPath);
|
|
25
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
26
|
+
try {
|
|
27
|
+
rmdirSync(lockPath);
|
|
28
|
+
}
|
|
29
|
+
catch { /* 另一个进程可能已清理 */ }
|
|
30
|
+
return 'retry';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// stat 失败说明锁已被释放,直接重试
|
|
35
|
+
return 'retry';
|
|
36
|
+
}
|
|
37
|
+
return 'wait';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 释放锁(删除目录)。
|
|
41
|
+
*/
|
|
42
|
+
function releaseLock(lockPath) {
|
|
43
|
+
try {
|
|
44
|
+
rmdirSync(lockPath);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 锁目录可能已被清理(比如被判定为僵尸锁),忽略
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 同步阻塞等待指定毫秒数。
|
|
52
|
+
* 使用 Atomics.wait 实现,依赖 Node.js 的 SharedArrayBuffer 支持。
|
|
53
|
+
*/
|
|
54
|
+
function blockWait(ms) {
|
|
55
|
+
const buf = new SharedArrayBuffer(4);
|
|
56
|
+
const arr = new Int32Array(buf);
|
|
57
|
+
Atomics.wait(arr, 0, 0, ms);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 同步文件锁。在 fn 执行期间持有 lockPath 目录锁。
|
|
61
|
+
* fn 无论正常返回还是抛异常,锁都会被释放。
|
|
62
|
+
*
|
|
63
|
+
* 注意:锁竞争时使用 Atomics.wait 阻塞事件循环。
|
|
64
|
+
* 仅适合启动/关闭阶段使用,请求路径上应使用 withFileLockAsync。
|
|
65
|
+
*/
|
|
66
|
+
export function withFileLock(lockPath, fn, options) {
|
|
67
|
+
const opts = { ...DEFAULTS, ...options };
|
|
68
|
+
let acquired = false;
|
|
69
|
+
for (let attempt = 0; attempt <= opts.retries; attempt++) {
|
|
70
|
+
const result = tryOnce(lockPath, opts.staleMs);
|
|
71
|
+
if (result === 'acquired') {
|
|
72
|
+
acquired = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (result === 'retry')
|
|
76
|
+
continue;
|
|
77
|
+
// result === 'wait'
|
|
78
|
+
if (attempt >= opts.retries) {
|
|
79
|
+
throw new Error(`Failed to acquire file lock: ${lockPath} after ${opts.retries} retries`);
|
|
80
|
+
}
|
|
81
|
+
blockWait(opts.retryIntervalMs);
|
|
82
|
+
}
|
|
83
|
+
if (!acquired) {
|
|
84
|
+
throw new Error(`Failed to acquire file lock: ${lockPath} after ${opts.retries} retries`);
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return fn();
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
releaseLock(lockPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 异步文件锁。在 fn 执行期间持有 lockPath 目录锁。
|
|
95
|
+
* fn 无论 resolve 还是 reject,锁都会被释放。
|
|
96
|
+
* 使用非阻塞 setTimeout 重试,不阻塞事件循环。
|
|
97
|
+
*/
|
|
98
|
+
export async function withFileLockAsync(lockPath, fn, options) {
|
|
99
|
+
const opts = { ...DEFAULTS, ...options };
|
|
100
|
+
let acquired = false;
|
|
101
|
+
for (let attempt = 0; attempt <= opts.retries; attempt++) {
|
|
102
|
+
const result = tryOnce(lockPath, opts.staleMs);
|
|
103
|
+
if (result === 'acquired') {
|
|
104
|
+
acquired = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (result === 'retry')
|
|
108
|
+
continue;
|
|
109
|
+
// result === 'wait'
|
|
110
|
+
if (attempt >= opts.retries) {
|
|
111
|
+
throw new Error(`Failed to acquire file lock: ${lockPath} after ${opts.retries} retries`);
|
|
112
|
+
}
|
|
113
|
+
await new Promise(resolve => setTimeout(resolve, opts.retryIntervalMs));
|
|
114
|
+
}
|
|
115
|
+
if (!acquired) {
|
|
116
|
+
throw new Error(`Failed to acquire file lock: ${lockPath} after ${opts.retries} retries`);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
return await fn();
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
releaseLock(lockPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-lock.js","sourceRoot":"","sources":["../../src/utils/file-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type IpChangeCallback = (newIp: string, oldIp: string) => void;
|
|
2
|
+
export type GetIpFunction = () => string | null;
|
|
3
|
+
/**
|
|
4
|
+
* IP 变化监控器。
|
|
5
|
+
*
|
|
6
|
+
* 采用轮询检测 + 稳定性阈值策略:
|
|
7
|
+
* - 每隔 intervalMs 检测一次 IP
|
|
8
|
+
* - 连续 stabilityThreshold 次检测到相同新 IP 才触发回调
|
|
9
|
+
* - 避免短暂网络波动导致误报
|
|
10
|
+
*/
|
|
11
|
+
export declare class IpMonitor {
|
|
12
|
+
private readonly onChange;
|
|
13
|
+
private readonly intervalMs;
|
|
14
|
+
private readonly stabilityThreshold;
|
|
15
|
+
private readonly getIp;
|
|
16
|
+
private intervalId;
|
|
17
|
+
private pendingIp;
|
|
18
|
+
private stabilityCount;
|
|
19
|
+
private _currentIp;
|
|
20
|
+
constructor(onChange: IpChangeCallback, intervalMs?: number, stabilityThreshold?: number, getIp?: GetIpFunction);
|
|
21
|
+
/**
|
|
22
|
+
* 启动 IP 监控。
|
|
23
|
+
* @param initialIp 初始 IP 地址
|
|
24
|
+
*/
|
|
25
|
+
start(initialIp: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* 停止 IP 监控。
|
|
28
|
+
*/
|
|
29
|
+
stop(): void;
|
|
30
|
+
/**
|
|
31
|
+
* 检测 IP 变化。
|
|
32
|
+
*/
|
|
33
|
+
private check;
|
|
34
|
+
/**
|
|
35
|
+
* 获取当前 IP。
|
|
36
|
+
*/
|
|
37
|
+
get currentIp(): string;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ip-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-monitor.d.ts","sourceRoot":"","sources":["../../src/utils/ip-monitor.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;AACtE,MAAM,MAAM,aAAa,GAAG,MAAM,MAAM,GAAG,IAAI,CAAC;AAShD;;;;;;;GAOG;AACH,qBAAa,SAAS;IAOlB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK;IATxB,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,UAAU,CAAc;gBAGb,QAAQ,EAAE,gBAAgB,EAC1B,UAAU,SAAQ,EAClB,kBAAkB,SAAI,EACtB,KAAK,GAAE,aAA4B;IAGtD;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAkB9B;;OAEG;IACH,IAAI,IAAI,IAAI;IASZ;;OAEG;IACH,OAAO,CAAC,KAAK;IAgDb;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;CACF"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { detectLanIp, detectNonLoopbackIp } from './network.js';
|
|
2
|
+
import { logger } from '../logger/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* 默认的 IP 检测函数:优先使用 LAN IP,其次使用非回环 IP
|
|
5
|
+
*/
|
|
6
|
+
function defaultGetIp() {
|
|
7
|
+
return detectLanIp() ?? detectNonLoopbackIp();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* IP 变化监控器。
|
|
11
|
+
*
|
|
12
|
+
* 采用轮询检测 + 稳定性阈值策略:
|
|
13
|
+
* - 每隔 intervalMs 检测一次 IP
|
|
14
|
+
* - 连续 stabilityThreshold 次检测到相同新 IP 才触发回调
|
|
15
|
+
* - 避免短暂网络波动导致误报
|
|
16
|
+
*/
|
|
17
|
+
export class IpMonitor {
|
|
18
|
+
onChange;
|
|
19
|
+
intervalMs;
|
|
20
|
+
stabilityThreshold;
|
|
21
|
+
getIp;
|
|
22
|
+
intervalId = null;
|
|
23
|
+
pendingIp = null;
|
|
24
|
+
stabilityCount = 0;
|
|
25
|
+
_currentIp = '';
|
|
26
|
+
constructor(onChange, intervalMs = 30000, stabilityThreshold = 2, getIp = defaultGetIp) {
|
|
27
|
+
this.onChange = onChange;
|
|
28
|
+
this.intervalMs = intervalMs;
|
|
29
|
+
this.stabilityThreshold = stabilityThreshold;
|
|
30
|
+
this.getIp = getIp;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 启动 IP 监控。
|
|
34
|
+
* @param initialIp 初始 IP 地址
|
|
35
|
+
*/
|
|
36
|
+
start(initialIp) {
|
|
37
|
+
this.stop();
|
|
38
|
+
this._currentIp = initialIp;
|
|
39
|
+
this.pendingIp = null;
|
|
40
|
+
this.stabilityCount = 0;
|
|
41
|
+
this.intervalId = setInterval(() => {
|
|
42
|
+
this.check();
|
|
43
|
+
}, this.intervalMs);
|
|
44
|
+
// 允许 Node.js 事件循环在没有其他活动时退出
|
|
45
|
+
if (this.intervalId.unref) {
|
|
46
|
+
this.intervalId.unref();
|
|
47
|
+
}
|
|
48
|
+
logger.info({ initialIp, intervalMs: this.intervalMs }, 'IP monitor started');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 停止 IP 监控。
|
|
52
|
+
*/
|
|
53
|
+
stop() {
|
|
54
|
+
if (this.intervalId) {
|
|
55
|
+
clearInterval(this.intervalId);
|
|
56
|
+
this.intervalId = null;
|
|
57
|
+
}
|
|
58
|
+
this.pendingIp = null;
|
|
59
|
+
this.stabilityCount = 0;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 检测 IP 变化。
|
|
63
|
+
*/
|
|
64
|
+
check() {
|
|
65
|
+
const newIp = this.getIp();
|
|
66
|
+
// 网络断开时不处理
|
|
67
|
+
if (!newIp) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// IP 未变化,重置待确认状态
|
|
71
|
+
if (newIp === this._currentIp) {
|
|
72
|
+
if (this.pendingIp !== null) {
|
|
73
|
+
logger.debug('IP stabilized to original, resetting monitor state');
|
|
74
|
+
}
|
|
75
|
+
this.pendingIp = null;
|
|
76
|
+
this.stabilityCount = 0;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// IP 变化了
|
|
80
|
+
if (newIp === this.pendingIp) {
|
|
81
|
+
// 与上次检测到的新 IP 相同,增加稳定性计数
|
|
82
|
+
this.stabilityCount++;
|
|
83
|
+
logger.debug({
|
|
84
|
+
pendingIp: this.pendingIp,
|
|
85
|
+
stabilityCount: this.stabilityCount,
|
|
86
|
+
threshold: this.stabilityThreshold,
|
|
87
|
+
}, 'IP change detected, counting stability');
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// 检测到不同的新 IP,重置稳定性计数
|
|
91
|
+
this.pendingIp = newIp;
|
|
92
|
+
this.stabilityCount = 1;
|
|
93
|
+
logger.debug({
|
|
94
|
+
newIp,
|
|
95
|
+
}, 'New IP detected, waiting for stability');
|
|
96
|
+
}
|
|
97
|
+
// 达到稳定性阈值,触发回调
|
|
98
|
+
if (this.stabilityCount >= this.stabilityThreshold) {
|
|
99
|
+
const oldIp = this._currentIp;
|
|
100
|
+
this._currentIp = newIp;
|
|
101
|
+
this.pendingIp = null;
|
|
102
|
+
this.stabilityCount = 0;
|
|
103
|
+
logger.info({ oldIp, newIp }, 'IP change confirmed, triggering callback');
|
|
104
|
+
this.onChange(newIp, oldIp);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 获取当前 IP。
|
|
109
|
+
*/
|
|
110
|
+
get currentIp() {
|
|
111
|
+
return this._currentIp;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=ip-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-monitor.js","sourceRoot":"","sources":["../../src/utils/ip-monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if an IPv4 address is in a private RFC 1918 range.
|
|
3
|
+
* - 10.0.0.0/8
|
|
4
|
+
* - 172.16.0.0/12 (172.16.x.x – 172.31.x.x)
|
|
5
|
+
* - 192.168.0.0/16
|
|
6
|
+
*/
|
|
7
|
+
export declare function isPrivateIp(address: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Detect the first LAN IP address (RFC 1918 private range).
|
|
10
|
+
* Returns null if no LAN IP found.
|
|
11
|
+
*/
|
|
12
|
+
export declare function detectLanIp(): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Detect the first non-internal IPv4 address.
|
|
15
|
+
* This is more permissive than detectLanIp - it will return any non-loopback IP.
|
|
16
|
+
* Useful for environments with non-RFC1918 internal networks (e.g., corporate networks).
|
|
17
|
+
*/
|
|
18
|
+
export declare function detectNonLoopbackIp(): string | null;
|
|
19
|
+
//# sourceMappingURL=network.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/utils/network.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CASpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,GAAG,IAAI,CAY3C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAYnD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { networkInterfaces } from 'node:os';
|
|
2
|
+
import { logger } from '../logger/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Check if an IPv4 address is in a private RFC 1918 range.
|
|
5
|
+
* - 10.0.0.0/8
|
|
6
|
+
* - 172.16.0.0/12 (172.16.x.x – 172.31.x.x)
|
|
7
|
+
* - 192.168.0.0/16
|
|
8
|
+
*/
|
|
9
|
+
export function isPrivateIp(address) {
|
|
10
|
+
if (address.startsWith('10.') || address.startsWith('192.168.')) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (address.startsWith('172.')) {
|
|
14
|
+
const secondOctet = parseInt(address.split('.')[1], 10);
|
|
15
|
+
return secondOctet >= 16 && secondOctet <= 31;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Detect the first LAN IP address (RFC 1918 private range).
|
|
21
|
+
* Returns null if no LAN IP found.
|
|
22
|
+
*/
|
|
23
|
+
export function detectLanIp() {
|
|
24
|
+
const nets = networkInterfaces();
|
|
25
|
+
for (const name of Object.keys(nets)) {
|
|
26
|
+
for (const net of nets[name] ?? []) {
|
|
27
|
+
if (net.family === 'IPv4' && !net.internal && isPrivateIp(net.address)) {
|
|
28
|
+
logger.info({ ip: net.address, interface: name }, 'Detected LAN IP');
|
|
29
|
+
return net.address;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
logger.warn('No LAN IP detected, falling back to 127.0.0.1');
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Detect the first non-internal IPv4 address.
|
|
38
|
+
* This is more permissive than detectLanIp - it will return any non-loopback IP.
|
|
39
|
+
* Useful for environments with non-RFC1918 internal networks (e.g., corporate networks).
|
|
40
|
+
*/
|
|
41
|
+
export function detectNonLoopbackIp() {
|
|
42
|
+
const nets = networkInterfaces();
|
|
43
|
+
for (const name of Object.keys(nets)) {
|
|
44
|
+
for (const net of nets[name] ?? []) {
|
|
45
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
46
|
+
logger.info({ ip: net.address, interface: name }, 'Detected non-loopback IP');
|
|
47
|
+
return net.address;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
logger.warn('No non-loopback IP detected');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/utils/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,WAAW,IAAI,EAAE,IAAI,WAAW,IAAI,EAAE,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACnC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBACvE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,iBAAiB,CAAC,CAAC;gBACrE,OAAO,GAAG,CAAC,OAAO,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACnC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,0BAA0B,CAAC,CAAC;gBAC9E,OAAO,GAAG,CAAC,OAAO,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC3C,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes the current process PID to a file.
|
|
3
|
+
* Creates parent directories if they don't exist.
|
|
4
|
+
*/
|
|
5
|
+
export declare function writePidFile(filePath: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Removes the PID file. Silently ignores if file doesn't exist.
|
|
8
|
+
*/
|
|
9
|
+
export declare function removePidFile(filePath: string): void;
|
|
10
|
+
//# sourceMappingURL=pid-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pid-file.d.ts","sourceRoot":"","sources":["../../src/utils/pid-file.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CASpD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { writeFileSync, unlinkSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { logger } from '../logger/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* Writes the current process PID to a file.
|
|
6
|
+
* Creates parent directories if they don't exist.
|
|
7
|
+
*/
|
|
8
|
+
export function writePidFile(filePath) {
|
|
9
|
+
const dir = dirname(filePath);
|
|
10
|
+
if (!existsSync(dir)) {
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
writeFileSync(filePath, String(process.pid), 'utf-8');
|
|
14
|
+
logger.info({ pid: process.pid, path: filePath }, 'PID file written');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Removes the PID file. Silently ignores if file doesn't exist.
|
|
18
|
+
*/
|
|
19
|
+
export function removePidFile(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
unlinkSync(filePath);
|
|
22
|
+
logger.info({ path: filePath }, 'PID file removed');
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err.code !== 'ENOENT') {
|
|
26
|
+
logger.warn({ err, path: filePath }, 'Failed to remove PID file');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=pid-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pid-file.js","sourceRoot":"","sources":["../../src/utils/pid-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,kBAAkB,CAAC,CAAC;AACxE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,CAAC;QACH,UAAU,CAAC,QAAQ,CAAC,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,kBAAkB,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,2BAA2B,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qrcode-banner.d.ts","sourceRoot":"","sources":["../../src/utils/qrcode-banner.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAS7C"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import qrcode from 'qrcode-terminal';
|
|
2
|
+
/**
|
|
3
|
+
* 在终端打印 ASCII 二维码(同步调用)
|
|
4
|
+
* @param url 包含 token 的完整 URL
|
|
5
|
+
*/
|
|
6
|
+
export function printQRCode(url) {
|
|
7
|
+
qrcode.generate(url, { small: true }, (qrString) => {
|
|
8
|
+
process.stderr.write('\n║ 扫码连接:\n');
|
|
9
|
+
qrString.split('\n').forEach((line) => {
|
|
10
|
+
if (line.trim()) {
|
|
11
|
+
process.stderr.write(`║ ${line}\n`);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=qrcode-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qrcode-banner.js","sourceRoot":"","sources":["../../src/utils/qrcode-banner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,iBAAiB,CAAC;AAErC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,QAAgB,EAAE,EAAE;QACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACpC,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;YACvC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
export interface WsHandlerCallbacks {
|
|
3
|
+
onUserInput: (data: string) => void;
|
|
4
|
+
onResize: (cols: number, rows: number) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Parse and route incoming WebSocket messages.
|
|
8
|
+
*/
|
|
9
|
+
export declare function handleWsMessage(ws: WebSocket, raw: string, callbacks: WsHandlerCallbacks): void;
|
|
10
|
+
//# sourceMappingURL=ws-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-handler.d.ts","sourceRoot":"","sources":["../../src/ws/ws-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAI/B,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAgC/F"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { logger } from '../logger/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse and route incoming WebSocket messages.
|
|
5
|
+
*/
|
|
6
|
+
export function handleWsMessage(ws, raw, callbacks) {
|
|
7
|
+
let msg;
|
|
8
|
+
try {
|
|
9
|
+
msg = JSON.parse(raw);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
logger.warn({ raw: raw.substring(0, 200) }, 'Invalid JSON in WS message');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
switch (msg.type) {
|
|
16
|
+
case 'user_input':
|
|
17
|
+
if (typeof msg.data === 'string') {
|
|
18
|
+
callbacks.onUserInput(msg.data);
|
|
19
|
+
}
|
|
20
|
+
break;
|
|
21
|
+
case 'resize':
|
|
22
|
+
if (typeof msg.cols === 'number' && typeof msg.rows === 'number') {
|
|
23
|
+
callbacks.onResize(msg.cols, msg.rows);
|
|
24
|
+
}
|
|
25
|
+
break;
|
|
26
|
+
case 'heartbeat':
|
|
27
|
+
// Reply with server heartbeat
|
|
28
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
29
|
+
ws.send(JSON.stringify({ type: 'heartbeat', timestamp: Date.now() }));
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
default:
|
|
33
|
+
logger.warn({ type: msg.type }, 'Unknown WS message type');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ws-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-handler.js","sourceRoot":"","sources":["../../src/ws/ws-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAO7C;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,EAAa,EAAE,GAAW,EAAE,SAA6B;IACvF,IAAI,GAAkB,CAAC;IACvB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,4BAA4B,CAAC,CAAC;QAC1E,OAAO;IACT,CAAC;IAED,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,YAAY;YACf,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACjC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClC,CAAC;YACD,MAAM;QAER,KAAK,QAAQ;YACX,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACjE,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACzC,CAAC;YACD,MAAM;QAER,KAAK,WAAW;YACd,8BAA8B;YAC9B,IAAI,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACrC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;YACxE,CAAC;YACD,MAAM;QAER;YACE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAG,GAAyB,CAAC,IAAI,EAAE,EAAE,yBAAyB,CAAC,CAAC;IACtF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Server as HttpServer } from 'node:http';
|
|
2
|
+
import { WebSocket } from 'ws';
|
|
3
|
+
import type { ServerMessage } from '@claude-remote/shared';
|
|
4
|
+
import { AuthModule } from '../auth/auth-middleware.js';
|
|
5
|
+
/** 客户端类型:attach(从控端)或 webapp(主控端) */
|
|
6
|
+
export type ClientType = 'attach' | 'webapp';
|
|
7
|
+
/**
|
|
8
|
+
* WebSocket server with session-based auth and heartbeat.
|
|
9
|
+
* 支持双重认证:
|
|
10
|
+
* 1. Cookie Session 认证(现有)
|
|
11
|
+
* 2. URL 参数 ?token=xxx 认证(用于 attach 命令)
|
|
12
|
+
*/
|
|
13
|
+
export declare class WsServer {
|
|
14
|
+
private httpServer;
|
|
15
|
+
private authModule;
|
|
16
|
+
private wss;
|
|
17
|
+
private clients;
|
|
18
|
+
private heartbeatInterval;
|
|
19
|
+
private messageHandler;
|
|
20
|
+
private connectHandler;
|
|
21
|
+
private disconnectHandler;
|
|
22
|
+
private upgradeClientTypes;
|
|
23
|
+
constructor(httpServer: HttpServer, authModule: AuthModule);
|
|
24
|
+
/**
|
|
25
|
+
* Set a handler for incoming messages from clients.
|
|
26
|
+
*/
|
|
27
|
+
onMessage(handler: (ws: WebSocket, data: string, clientType: ClientType) => void): void;
|
|
28
|
+
/**
|
|
29
|
+
* Set a handler for new client connections.
|
|
30
|
+
*/
|
|
31
|
+
onConnect(handler: (ws: WebSocket, clientType: ClientType) => void): void;
|
|
32
|
+
/**
|
|
33
|
+
* Set a handler for client disconnections (after client is removed from set).
|
|
34
|
+
*/
|
|
35
|
+
onDisconnect(handler: (clientCounts: {
|
|
36
|
+
attach: number;
|
|
37
|
+
webapp: number;
|
|
38
|
+
}) => void): void;
|
|
39
|
+
/**
|
|
40
|
+
* HTTP upgrade → authenticate via cookie or token, then establish WS.
|
|
41
|
+
* 支持两种认证方式:
|
|
42
|
+
* 1. Cookie Session 认证(现有流程)
|
|
43
|
+
* 2. URL 参数 ?token=xxx 认证(用于 attach 命令)
|
|
44
|
+
*/
|
|
45
|
+
private setupUpgrade;
|
|
46
|
+
/**
|
|
47
|
+
* Handle new WebSocket connections.
|
|
48
|
+
*/
|
|
49
|
+
private setupConnection;
|
|
50
|
+
/**
|
|
51
|
+
* Broadcast a message to all connected clients.
|
|
52
|
+
*/
|
|
53
|
+
broadcast(message: ServerMessage): void;
|
|
54
|
+
/**
|
|
55
|
+
* Send a message to a specific client.
|
|
56
|
+
*/
|
|
57
|
+
sendTo(ws: WebSocket, message: ServerMessage): void;
|
|
58
|
+
/**
|
|
59
|
+
* Start heartbeat pings.
|
|
60
|
+
*/
|
|
61
|
+
private startHeartbeat;
|
|
62
|
+
/**
|
|
63
|
+
* Get connected client count.
|
|
64
|
+
*/
|
|
65
|
+
get clientCount(): number;
|
|
66
|
+
/**
|
|
67
|
+
* Get client counts by type.
|
|
68
|
+
*/
|
|
69
|
+
getClientCounts(): {
|
|
70
|
+
attach: number;
|
|
71
|
+
webapp: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Shutdown the WebSocket server.
|
|
75
|
+
*/
|
|
76
|
+
destroy(): void;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=ws-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-server.d.ts","sourceRoot":"","sources":["../../src/ws/ws-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAmB,MAAM,WAAW,CAAC;AAClE,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGxD,qCAAqC;AACrC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAQ7C;;;;;GAKG;AACH,qBAAa,QAAQ;IAUjB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,UAAU;IAVpB,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,iBAAiB,CAA+C;IACxE,OAAO,CAAC,cAAc,CAAgF;IACtG,OAAO,CAAC,cAAc,CAAkE;IACxF,OAAO,CAAC,iBAAiB,CAA6E;IACtG,OAAO,CAAC,kBAAkB,CAA8C;gBAG9D,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,UAAU;IAQhC;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIvF;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIzE;;OAEG;IACH,YAAY,CAAC,OAAO,EAAE,CAAC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI;IAIvF;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IA2DpB;;OAEG;IACH,OAAO,CAAC,eAAe;IA4CvB;;OAEG;IACH,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IASvC;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI;IAMnD;;OAEG;IACH,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,eAAe,IAAI;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IAarD;;OAEG;IACH,OAAO,IAAI,IAAI;CAWhB"}
|