@oatelauser/crosschat 1.4.0 → 1.5.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/CHANGELOG.md +25 -0
- package/README.md +26 -17
- package/dist/cli.js +232 -20
- package/dist/cli.js.map +1 -1
- package/dist/commands/clean.js +91 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/doctor.js +54 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/send.js +74 -16
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/serve.js +380 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.js +86 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/uninstall.js +66 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/envelope.js +1 -1
- package/dist/envelope.js.map +1 -1
- package/dist/federation.js +14 -7
- package/dist/federation.js.map +1 -1
- package/dist/limits.js +8 -4
- package/dist/limits.js.map +1 -1
- package/dist/serve/broker.js +495 -0
- package/dist/serve/broker.js.map +1 -0
- package/dist/serve/file-channel.js +211 -0
- package/dist/serve/file-channel.js.map +1 -0
- package/dist/serve/node.js +340 -0
- package/dist/serve/node.js.map +1 -0
- package/dist/serve/protocol.js +51 -0
- package/dist/serve/protocol.js.map +1 -0
- package/dist/serve/registry.js +116 -0
- package/dist/serve/registry.js.map +1 -0
- package/dist/serve/via-tcp.js +323 -0
- package/dist/serve/via-tcp.js.map +1 -0
- package/package.json +1 -1
- package/skills/crosschat/SKILL.md +10 -5
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件通道(票B B2):接收侧 FileInbox——inbox 收 whole 文件、parts 暂存
|
|
3
|
+
* 分块与断点锚。发送侧的分块循环在 via-tcp.ts,这里只有两端共用的常量、
|
|
4
|
+
* 文件名安全化与接收状态机。路径约定:~/.crosschat/tcp/{inbox,parts}/。
|
|
5
|
+
*/
|
|
6
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { Buffer } from 'node:buffer';
|
|
10
|
+
/** 单文件上限 64MiB(发送侧读文件时与 broker file-begin 预拒同用此值)。 */
|
|
11
|
+
export const FILE_MAX_BYTES = 64 * 1024 * 1024;
|
|
12
|
+
/** 分块大小 256KiB(base64 后约 350KB,留在一帧 2MiB 上限内)。 */
|
|
13
|
+
export const FILE_CHUNK_BYTES = 256 * 1024;
|
|
14
|
+
/** 懒删除:inbox/parts 落盘超 5 天视为残迹。 */
|
|
15
|
+
export const FILE_STALE_MS = 5 * 24 * 3_600_000;
|
|
16
|
+
/** 懒删除:inbox 总量超 512MiB 从最旧删起。 */
|
|
17
|
+
export const INBOX_MAX_BYTES = 512 * 1024 * 1024;
|
|
18
|
+
/** 原名剥离目录成分与 Windows 危险字符,空则兜底 'file'。 */
|
|
19
|
+
export function safeFileName(name) {
|
|
20
|
+
const base = name.split(/[\\/]/).pop() ?? '';
|
|
21
|
+
// eslint-disable-next-line no-control-regex -- 控制字符必须一并剥掉
|
|
22
|
+
const cleaned = base.replace(/[/\\:*?"<>|\u0000-\u001f]/g, '_').trim();
|
|
23
|
+
return cleaned === '' || cleaned === '.' || cleaned === '..' ? 'file' : cleaned;
|
|
24
|
+
}
|
|
25
|
+
/** 接收完成后的最终路径:inbox/<sha 前 8>-<安全化原名>。 */
|
|
26
|
+
export function inboxPathFor(inboxDir, sha256, name) {
|
|
27
|
+
return join(inboxDir, `${sha256.slice(0, 8)}-${safeFileName(name)}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 一次 file-begin..file-end 的接收状态机(多 id 并发各写各的 parts 文件)。
|
|
31
|
+
* 断点锚 parts/<sha>.part.json{nextSeq} 每块落盘后更新——传输中断后
|
|
32
|
+
* 重新 begin 会拿到 resumeFrom=nextSeq,发方从该块续传。
|
|
33
|
+
*/
|
|
34
|
+
export class FileInbox {
|
|
35
|
+
dirs;
|
|
36
|
+
now;
|
|
37
|
+
transfers = new Map();
|
|
38
|
+
constructor(dirs, now = () => Date.now()) {
|
|
39
|
+
this.dirs = dirs;
|
|
40
|
+
this.now = now;
|
|
41
|
+
}
|
|
42
|
+
/** file-begin 应答:去重秒回 / 断点续传锚 / 全新开始(顺带懒删除)。 */
|
|
43
|
+
begin(frame) {
|
|
44
|
+
this.lazyDelete();
|
|
45
|
+
const { sha256, name, size } = frame;
|
|
46
|
+
const finalPath = inboxPathFor(this.dirs.inboxDir, sha256, name);
|
|
47
|
+
if (existsSync(finalPath)) {
|
|
48
|
+
return { op: 'file-result', id: frame.id, ok: true, path: finalPath, dedup: true };
|
|
49
|
+
}
|
|
50
|
+
this.transfers.set(frame.id, { sha256, name, size });
|
|
51
|
+
const anchor = this.readAnchor(sha256);
|
|
52
|
+
if (anchor !== undefined && anchor.nextSeq > 0) {
|
|
53
|
+
return { op: 'file-result', id: frame.id, ok: true, resumeFrom: anchor.nextSeq };
|
|
54
|
+
}
|
|
55
|
+
// 无锚或锚损坏:清掉可能存在的残片,从零开始(残片混写必致 sha 失败)
|
|
56
|
+
this.cleanup(sha256);
|
|
57
|
+
this.writeAnchor(sha256, 0);
|
|
58
|
+
return { op: 'file-result', id: frame.id, ok: true, resumeFrom: 0 };
|
|
59
|
+
}
|
|
60
|
+
/** 顺序落盘一块;seq 必须等于锚的 nextSeq(发方遵守 resumeFrom 时恒真)。 */
|
|
61
|
+
chunk(frame) {
|
|
62
|
+
const t = this.transfers.get(frame.id);
|
|
63
|
+
if (t === undefined)
|
|
64
|
+
return false;
|
|
65
|
+
const anchor = this.readAnchor(t.sha256);
|
|
66
|
+
if (anchor === undefined || anchor.nextSeq !== frame.seq)
|
|
67
|
+
return false;
|
|
68
|
+
let bytes;
|
|
69
|
+
try {
|
|
70
|
+
bytes = Buffer.from(frame.bytes, 'base64');
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
mkdirSync(this.dirs.partsDir, { recursive: true });
|
|
76
|
+
appendFileSync(this.partFile(t.sha256), bytes);
|
|
77
|
+
this.writeAnchor(t.sha256, frame.seq + 1);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/** file-end:sha256 校验 → 改名转正 → 清残片;失败删残片要求整档重传。 */
|
|
81
|
+
end(id) {
|
|
82
|
+
const t = this.transfers.get(id);
|
|
83
|
+
if (t === undefined) {
|
|
84
|
+
return { op: 'file-result', id, ok: false, error: '未知传输 id(可能已被清理)' };
|
|
85
|
+
}
|
|
86
|
+
this.transfers.delete(id);
|
|
87
|
+
const part = this.partFile(t.sha256);
|
|
88
|
+
let ok = false;
|
|
89
|
+
try {
|
|
90
|
+
ok = existsSync(part) && sha256File(part) === t.sha256;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
ok = false;
|
|
94
|
+
}
|
|
95
|
+
if (!ok) {
|
|
96
|
+
this.cleanup(t.sha256);
|
|
97
|
+
return {
|
|
98
|
+
op: 'file-result',
|
|
99
|
+
id,
|
|
100
|
+
ok: false,
|
|
101
|
+
error: 'sha256 校验失败——残片已清理,请整档重传(重跑同一命令即可)',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const finalPath = inboxPathFor(this.dirs.inboxDir, t.sha256, t.name);
|
|
105
|
+
mkdirSync(this.dirs.inboxDir, { recursive: true });
|
|
106
|
+
// 同名不同 sha 不可能(sha 在文件名里);同 sha 同名已提前 dedup
|
|
107
|
+
renameSync(part, finalPath);
|
|
108
|
+
this.cleanup(t.sha256);
|
|
109
|
+
return { op: 'file-result', id, ok: true, path: finalPath };
|
|
110
|
+
}
|
|
111
|
+
partFile(sha) {
|
|
112
|
+
return join(this.dirs.partsDir, `${sha}.part`);
|
|
113
|
+
}
|
|
114
|
+
anchorFile(sha) {
|
|
115
|
+
return join(this.dirs.partsDir, `${sha}.part.json`);
|
|
116
|
+
}
|
|
117
|
+
/** 无锚/锚损坏统一 undefined(损坏时 begin 会连残片一起清掉重来)。 */
|
|
118
|
+
readAnchor(sha) {
|
|
119
|
+
try {
|
|
120
|
+
const parsed = JSON.parse(readFileSync(this.anchorFile(sha), 'utf8'));
|
|
121
|
+
return typeof parsed?.nextSeq === 'number' ? { nextSeq: parsed.nextSeq } : undefined;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
writeAnchor(sha, nextSeq) {
|
|
128
|
+
mkdirSync(this.dirs.partsDir, { recursive: true });
|
|
129
|
+
writeFileSync(this.anchorFile(sha), `${JSON.stringify({ nextSeq })}\n`, 'utf8');
|
|
130
|
+
}
|
|
131
|
+
cleanup(sha) {
|
|
132
|
+
rmSync(this.partFile(sha), { force: true });
|
|
133
|
+
rmSync(this.anchorFile(sha), { force: true });
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 懒删除(每次 file-begin 顺带执行):inbox 落盘 >5 天删;inbox 总量
|
|
137
|
+
* >512MiB 从最旧删起;parts 残片同 5 天规则。best-effort——单文件删除
|
|
138
|
+
* 失败不阻断传输。
|
|
139
|
+
*/
|
|
140
|
+
lazyDelete() {
|
|
141
|
+
const cutoff = this.now() - FILE_STALE_MS;
|
|
142
|
+
const statOf = (file) => {
|
|
143
|
+
try {
|
|
144
|
+
const s = statSync(file);
|
|
145
|
+
return { mtime: s.mtimeMs, size: s.size };
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
const entries = readdirSync(this.dirs.inboxDir).map((f) => ({
|
|
153
|
+
file: join(this.dirs.inboxDir, f),
|
|
154
|
+
...statOf(join(this.dirs.inboxDir, f)),
|
|
155
|
+
}));
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
if (e.mtime !== undefined && e.mtime < cutoff) {
|
|
158
|
+
try {
|
|
159
|
+
rmSync(e.file, { force: true });
|
|
160
|
+
}
|
|
161
|
+
catch { /* best-effort */ }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const live = entries
|
|
165
|
+
.filter((e) => e.mtime !== undefined && e.mtime >= cutoff && existsSync(e.file))
|
|
166
|
+
.sort((a, b) => a.mtime - b.mtime);
|
|
167
|
+
let total = live.reduce((sum, e) => sum + (e.size ?? 0), 0);
|
|
168
|
+
for (const e of live) {
|
|
169
|
+
if (total <= INBOX_MAX_BYTES)
|
|
170
|
+
break;
|
|
171
|
+
try {
|
|
172
|
+
rmSync(e.file, { force: true });
|
|
173
|
+
total -= e.size ?? 0;
|
|
174
|
+
}
|
|
175
|
+
catch { /* best-effort */ }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// inbox 目录不存在等——首份文件会建目录
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
for (const f of readdirSync(this.dirs.partsDir)) {
|
|
183
|
+
const file = join(this.dirs.partsDir, f);
|
|
184
|
+
const s = statOf(file);
|
|
185
|
+
if (s !== undefined && s.mtime < cutoff) {
|
|
186
|
+
try {
|
|
187
|
+
rmSync(file, { force: true });
|
|
188
|
+
}
|
|
189
|
+
catch { /* best-effort */ }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// parts 目录不存在——忽略
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function sha256File(file) {
|
|
199
|
+
return createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
200
|
+
}
|
|
201
|
+
/** 发送侧文件读取 + 摘要(64MiB 预检在调用方)。 */
|
|
202
|
+
export function readFileForSend(path) {
|
|
203
|
+
const bytes = readFileSync(path);
|
|
204
|
+
return {
|
|
205
|
+
bytes,
|
|
206
|
+
sha256: createHash('sha256').update(bytes).digest('hex'),
|
|
207
|
+
name: (path.split(/[\\/]/).pop() ?? 'file'),
|
|
208
|
+
size: bytes.length,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=file-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-channel.js","sourceRoot":"","sources":["../../src/serve/file-channel.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,cAAc,EACd,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,sDAAsD;AACtD,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC/C,kDAAkD;AAClD,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,CAAC;AAC3C,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;AAChD,kCAAkC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,0CAA0C;AAC1C,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;IAC7C,0DAA0D;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACvE,OAAO,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AAClF,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,MAAc,EAAE,IAAY;IACzE,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC;AAaD;;;;GAIG;AACH,MAAM,OAAO,SAAS;IAID;IACA;IAJF,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAEzD,YACmB,IAAmB,EACnB,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QADpC,SAAI,GAAJ,IAAI,CAAe;QACnB,QAAG,GAAH,GAAG,CAAiC;IACpD,CAAC;IAEJ,gDAAgD;IAChD,KAAK,CAAC,KAAqB;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;QACrC,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QACjE,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACrF,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YAC/C,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;QACnF,CAAC;QACD,uCAAuC;QACvC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IACtE,CAAC;IAED,sDAAsD;IACtD,KAAK,CAAC,KAAqB;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;QACvE,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/C,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,mDAAmD;IACnD,GAAG,CAAC,EAAU;QACZ,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QACxE,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,EAAE,GAAG,KAAK,CAAC;QACf,IAAI,CAAC;YACH,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,EAAE,GAAG,KAAK,CAAC;QACb,CAAC;QACD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACvB,OAAO;gBACL,EAAE,EAAE,aAAa;gBACjB,EAAE;gBACF,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,oCAAoC;aAC5C,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACrE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,4CAA4C;QAC5C,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC9D,CAAC;IAEO,QAAQ,CAAC,GAAW;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;IACjD,CAAC;IAEO,UAAU,CAAC,GAAW;QAC5B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,GAAG,YAAY,CAAC,CAAC;IACtD,CAAC;IAED,gDAAgD;IACxC,UAAU,CAAC,GAAW;QAC5B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAA0B,CAAC;YAC/F,OAAO,OAAO,MAAM,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACvF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,WAAW,CAAC,GAAW,EAAE,OAAe;QAC9C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC;IAEO,OAAO,CAAC,GAAW;QACzB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED;;;;OAIG;IACK,UAAU;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,IAAY,EAA+C,EAAE;YAC3E,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACzB,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,SAAS,CAAC;YACnB,CAAC;QACH,CAAC,CAAC;QACF,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1D,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACjC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;aACvC,CAAC,CAAC,CAAC;YACJ,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;oBAC9C,IAAI,CAAC;wBAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;gBACtE,CAAC;YACH,CAAC;YACD,MAAM,IAAI,GAAG,OAAO;iBACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBAC/E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAM,GAAG,CAAC,CAAC,KAAM,CAAC,CAAC;YACvC,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC5D,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,IAAI,KAAK,IAAI,eAAe;oBAAE,MAAM;gBACpC,IAAI,CAAC;oBACH,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;oBAChC,KAAK,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;gBACvB,CAAC;gBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;QACD,IAAI,CAAC;YACH,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;gBACzC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;gBACvB,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;oBACxC,IAAI,CAAC;wBAAC,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;oBAAC,CAAC;oBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;CACF;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO;QACL,KAAK;QACL,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACxD,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC;QAC3C,IAAI,EAAE,KAAK,CAAC,MAAM;KACnB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 节点客户端(票B B2):`crosschat serve --connect` 的常驻腿——连 broker、
|
|
3
|
+
* 收 deliver 帧进程内调 runSend(与 ssh 远端 CLI 同权:会话状态/限流/发件箱
|
|
4
|
+
* 全是本机既有行为)、收 file-* 帧落盘 inbox。断线指数退避 1s→2s→…封顶
|
|
5
|
+
* 60s 永不放弃;连续失败 ≥5 分钟进静音模式(日志每 10 次一条)。
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { dirname } from 'node:path';
|
|
9
|
+
import { WebSocket } from 'ws';
|
|
10
|
+
import { MultichatError } from '../errors.js';
|
|
11
|
+
import { encodeFrame, MAX_PAYLOAD_BYTES, parseFrame, PROTOCOL_VERSION } from './protocol.js';
|
|
12
|
+
import { runSend } from '../commands/send.js';
|
|
13
|
+
/** 会员凭证缺失/损坏的教学报错(send --via tcp: 与 --connect 同用)。 */
|
|
14
|
+
export const NOT_JOINED_MESSAGE = '本机未入网——向管理员索取 node.json 放到 ~/.crosschat/tcp/(broker 机上 crosschat serve --gen-key 生成)';
|
|
15
|
+
export function readNodeConfig(configFile) {
|
|
16
|
+
let raw;
|
|
17
|
+
try {
|
|
18
|
+
raw = readFileSync(configFile, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new MultichatError('USAGE', NOT_JOINED_MESSAGE);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
if (parsed === null || typeof parsed !== 'object' ||
|
|
26
|
+
typeof parsed.key !== 'string' || parsed.key === '' ||
|
|
27
|
+
typeof parsed.broker?.host !== 'string' || !Number.isInteger(parsed.broker.port)) {
|
|
28
|
+
throw new Error('bad shape');
|
|
29
|
+
}
|
|
30
|
+
return parsed;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new MultichatError('USAGE', `node.json 损坏(${configFile})——向管理员重新索取覆盖;${NOT_JOINED_MESSAGE}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* deliver 帧 → 本机 runSend(broker 侧 deliverLocal 同用此桥):
|
|
38
|
+
* payload=正文,to/conversation/origin 透传;origin 是发送机的
|
|
39
|
+
* encodeOriginIdentity 编码(ssh 远端 CLI 的 --origin 同语义)。
|
|
40
|
+
*/
|
|
41
|
+
export async function deliverViaRunSend(frame, deps) {
|
|
42
|
+
let output;
|
|
43
|
+
try {
|
|
44
|
+
output = await runSend({
|
|
45
|
+
to: frame.to,
|
|
46
|
+
conversation: frame.conversation,
|
|
47
|
+
bodyArg: frame.payload,
|
|
48
|
+
origin: frame.origin,
|
|
49
|
+
json: true,
|
|
50
|
+
}, deps);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err instanceof MultichatError) {
|
|
54
|
+
return { op: 'result', id: frame.id, status: 'error', code: err.code, message: err.message };
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
op: 'result',
|
|
58
|
+
id: frame.id,
|
|
59
|
+
status: 'error',
|
|
60
|
+
code: 'INTERNAL',
|
|
61
|
+
message: err instanceof Error ? err.message : String(err),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// runSend --json 输出:{status,to,turn,replyRef[,queue,mailbox]}
|
|
65
|
+
try {
|
|
66
|
+
const receipt = JSON.parse(output);
|
|
67
|
+
return {
|
|
68
|
+
op: 'result',
|
|
69
|
+
id: frame.id,
|
|
70
|
+
status: receipt.status,
|
|
71
|
+
turn: receipt.turn === undefined ? undefined : String(receipt.turn),
|
|
72
|
+
replyRef: receipt.replyRef,
|
|
73
|
+
output,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { op: 'result', id: frame.id, status: 'error', code: 'INTERNAL', message: `本机回执不是 JSON: ${output.slice(0, 200)}` };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export class NodeClient {
|
|
81
|
+
d;
|
|
82
|
+
stopped = false;
|
|
83
|
+
ws;
|
|
84
|
+
state = 'connecting';
|
|
85
|
+
stateSince = 0;
|
|
86
|
+
frameWaiters = [];
|
|
87
|
+
/** 本连接已收帧(open 与 nextFrame 注册之间到达的帧先落此,注册时回查)。 */
|
|
88
|
+
frameLog = [];
|
|
89
|
+
stateWaiters = [];
|
|
90
|
+
minuteTimer;
|
|
91
|
+
mainLoop;
|
|
92
|
+
stopResolve;
|
|
93
|
+
constructor(deps) {
|
|
94
|
+
this.d = { backoffBaseMs: 1_000, quietAfterMs: 300_000, ...deps };
|
|
95
|
+
}
|
|
96
|
+
/** 阻塞跑直到 stop(serve --connect 前台姿态)。 */
|
|
97
|
+
async run() {
|
|
98
|
+
this.d.onSignal?.('SIGINT', () => void this.stop());
|
|
99
|
+
this.d.onSignal?.('SIGTERM', () => void this.stop());
|
|
100
|
+
this.mainLoop = this.loop();
|
|
101
|
+
await new Promise((resolve) => {
|
|
102
|
+
this.stopResolve = resolve;
|
|
103
|
+
});
|
|
104
|
+
await this.mainLoop;
|
|
105
|
+
}
|
|
106
|
+
/** 停主循环、关连接(幂等);打断退避睡眠立即返回。 */
|
|
107
|
+
async stop() {
|
|
108
|
+
if (this.stopped) {
|
|
109
|
+
await this.mainLoop?.catch(() => { });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.stopped = true;
|
|
113
|
+
this.clearFrameWaiters(new Error('stopped'));
|
|
114
|
+
try {
|
|
115
|
+
this.ws?.close(1000, 'node stopping');
|
|
116
|
+
}
|
|
117
|
+
catch { /* 竞态关闭忽略 */ }
|
|
118
|
+
clearInterval(this.minuteTimer);
|
|
119
|
+
this.stopResolve?.();
|
|
120
|
+
await this.mainLoop?.catch(() => { });
|
|
121
|
+
}
|
|
122
|
+
/** 等到指定状态(测试用);超时拒绝。 */
|
|
123
|
+
waitForState(state, timeoutMs = 5_000) {
|
|
124
|
+
if (this.state === state)
|
|
125
|
+
return Promise.resolve();
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const waiter = {
|
|
128
|
+
state,
|
|
129
|
+
resolve,
|
|
130
|
+
timer: setTimeout(() => {
|
|
131
|
+
const i = this.stateWaiters.indexOf(waiter);
|
|
132
|
+
if (i >= 0)
|
|
133
|
+
this.stateWaiters.splice(i, 1);
|
|
134
|
+
reject(new Error(`waitForState(${state}) timeout`));
|
|
135
|
+
}, timeoutMs),
|
|
136
|
+
};
|
|
137
|
+
this.stateWaiters.push(waiter);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async loop() {
|
|
141
|
+
let attempt = 0;
|
|
142
|
+
let firstFailAt;
|
|
143
|
+
while (!this.stopped) {
|
|
144
|
+
this.setState('connecting');
|
|
145
|
+
const everOnline = await this.connectOnce();
|
|
146
|
+
if (this.stopped)
|
|
147
|
+
break;
|
|
148
|
+
attempt = everOnline ? 0 : attempt + 1;
|
|
149
|
+
if (attempt === 1)
|
|
150
|
+
firstFailAt = this.d.now();
|
|
151
|
+
const failedFor = firstFailAt === undefined ? 0 : this.d.now() - firstFailAt;
|
|
152
|
+
const quiet = failedFor >= this.d.quietAfterMs;
|
|
153
|
+
const delay = Math.min(this.d.backoffBaseMs * 2 ** Math.max(0, attempt - 1), 60_000);
|
|
154
|
+
if (!quiet || attempt % 10 === 1) {
|
|
155
|
+
this.log(`reconnect-wait 第 ${attempt} 次失败后 ${delay}ms 再试(连续失败 ${Math.round(failedFor / 1000)}s)`);
|
|
156
|
+
}
|
|
157
|
+
this.setState('reconnect-wait');
|
|
158
|
+
this.writeStatus(this.d.now() + delay);
|
|
159
|
+
await this.sleepInterruptible(delay);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** 一次连接生命周期;resolve(false)=从未上线即断,resolve(true)=上线后断开。 */
|
|
163
|
+
async connectOnce() {
|
|
164
|
+
const cfg = this.d.config;
|
|
165
|
+
const url = `ws://${cfg.broker.host}:${cfg.broker.port}`;
|
|
166
|
+
this.frameLog = [];
|
|
167
|
+
const ws = new WebSocket(url, { maxPayload: MAX_PAYLOAD_BYTES });
|
|
168
|
+
this.ws = ws;
|
|
169
|
+
let everOnline = false;
|
|
170
|
+
let closed = () => { };
|
|
171
|
+
const closedPromise = new Promise((resolve) => {
|
|
172
|
+
closed = resolve;
|
|
173
|
+
});
|
|
174
|
+
ws.once('close', () => closed());
|
|
175
|
+
ws.once('error', () => closed());
|
|
176
|
+
ws.on('message', (data) => {
|
|
177
|
+
void this.onFrame(data.toString());
|
|
178
|
+
});
|
|
179
|
+
try {
|
|
180
|
+
await new Promise((resolve, reject) => {
|
|
181
|
+
const t = setTimeout(() => reject(new Error('connect timeout')), 15_000);
|
|
182
|
+
ws.once('open', () => { clearTimeout(t); resolve(); });
|
|
183
|
+
ws.once('error', (err) => { clearTimeout(t); reject(err); });
|
|
184
|
+
});
|
|
185
|
+
const info = await this.nextFrame((f) => f.op === 'info', 10_000);
|
|
186
|
+
if (info.op !== 'info' || info.proto !== PROTOCOL_VERSION) {
|
|
187
|
+
throw new Error(`协议不匹配:服务端 ${info.op === 'info' ? info.proto : '?'}`);
|
|
188
|
+
}
|
|
189
|
+
ws.send(encodeFrame({
|
|
190
|
+
op: 'connect',
|
|
191
|
+
proto: PROTOCOL_VERSION,
|
|
192
|
+
role: 'node',
|
|
193
|
+
machineId: this.d.machineId() ?? '',
|
|
194
|
+
auth: cfg.key,
|
|
195
|
+
...(cfg.name === undefined ? {} : { name: cfg.name }),
|
|
196
|
+
}));
|
|
197
|
+
const verdict = await this.nextFrame((f) => f.op === 'connected' || f.op === 'error', 10_000);
|
|
198
|
+
if (verdict.op === 'error') {
|
|
199
|
+
throw new Error(`broker 拒绝: ${verdict.code} ${verdict.message}`);
|
|
200
|
+
}
|
|
201
|
+
everOnline = true;
|
|
202
|
+
this.setState('online');
|
|
203
|
+
this.log(`online(broker ${cfg.broker.alias ?? url})`);
|
|
204
|
+
this.minuteTimer = setInterval(() => {
|
|
205
|
+
if (this.state === 'online')
|
|
206
|
+
this.writeStatus(undefined);
|
|
207
|
+
}, 60_000);
|
|
208
|
+
await closedPromise;
|
|
209
|
+
this.log('offline(连接断开)');
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
this.log(`connect 失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
clearInterval(this.minuteTimer);
|
|
216
|
+
this.clearFrameWaiters(new Error('connection closed'));
|
|
217
|
+
try {
|
|
218
|
+
ws.close();
|
|
219
|
+
}
|
|
220
|
+
catch { /* 已断开 */ }
|
|
221
|
+
if (this.ws === ws)
|
|
222
|
+
this.ws = undefined;
|
|
223
|
+
}
|
|
224
|
+
return everOnline;
|
|
225
|
+
}
|
|
226
|
+
async onFrame(text) {
|
|
227
|
+
const frame = parseFrame(text);
|
|
228
|
+
if (frame === undefined)
|
|
229
|
+
return;
|
|
230
|
+
this.frameLog.push(frame);
|
|
231
|
+
for (let i = 0; i < this.frameWaiters.length; i++) {
|
|
232
|
+
const w = this.frameWaiters[i];
|
|
233
|
+
if (w.pred(frame)) {
|
|
234
|
+
clearTimeout(w.timer);
|
|
235
|
+
this.frameWaiters.splice(i, 1);
|
|
236
|
+
w.resolve(frame);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (frame.op === 'deliver') {
|
|
241
|
+
void this.onDeliver(frame);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (frame.op === 'file-begin' || frame.op === 'file-chunk' || frame.op === 'file-end') {
|
|
245
|
+
this.onFile(frame);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async onDeliver(frame) {
|
|
249
|
+
const result = await deliverViaRunSend(frame, this.d.makeSendDeps());
|
|
250
|
+
this.send(result);
|
|
251
|
+
this.log(`deliver ${frame.id} → ${result.status}${result.status === 'error' ? ` (${result.code})` : ''}`);
|
|
252
|
+
}
|
|
253
|
+
onFile(frame) {
|
|
254
|
+
if (frame.op === 'file-begin') {
|
|
255
|
+
this.send(this.d.fileInbox.begin(frame));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (frame.op === 'file-chunk') {
|
|
259
|
+
if (!this.d.fileInbox.chunk(frame)) {
|
|
260
|
+
this.send({ op: 'file-result', id: frame.id, ok: false, error: '分块失序或未知 id——请重新 begin 续传' });
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const res = this.d.fileInbox.end(frame.id);
|
|
265
|
+
this.send(res);
|
|
266
|
+
this.log(`file ${frame.id} ${res.ok ? `→ ${res.path}` : `失败: ${res.error}`}`);
|
|
267
|
+
}
|
|
268
|
+
nextFrame(pred, timeoutMs) {
|
|
269
|
+
const seen = this.frameLog.find(pred);
|
|
270
|
+
if (seen !== undefined)
|
|
271
|
+
return Promise.resolve(seen);
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
const waiter = {
|
|
274
|
+
pred,
|
|
275
|
+
resolve,
|
|
276
|
+
reject,
|
|
277
|
+
timer: setTimeout(() => {
|
|
278
|
+
const i = this.frameWaiters.indexOf(waiter);
|
|
279
|
+
if (i >= 0)
|
|
280
|
+
this.frameWaiters.splice(i, 1);
|
|
281
|
+
reject(new Error('frame timeout'));
|
|
282
|
+
}, timeoutMs),
|
|
283
|
+
};
|
|
284
|
+
this.frameWaiters.push(waiter);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
clearFrameWaiters(err) {
|
|
288
|
+
for (const w of this.frameWaiters.splice(0)) {
|
|
289
|
+
clearTimeout(w.timer);
|
|
290
|
+
w.reject(err);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
send(frame) {
|
|
294
|
+
if (this.ws !== undefined && this.ws.readyState === WebSocket.OPEN) {
|
|
295
|
+
this.ws.send(encodeFrame(frame));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
setState(state) {
|
|
299
|
+
this.state = state;
|
|
300
|
+
this.stateSince = this.d.now();
|
|
301
|
+
this.writeStatus(undefined);
|
|
302
|
+
for (let i = this.stateWaiters.length - 1; i >= 0; i--) {
|
|
303
|
+
const w = this.stateWaiters[i];
|
|
304
|
+
if (w.state !== state)
|
|
305
|
+
continue;
|
|
306
|
+
clearTimeout(w.timer);
|
|
307
|
+
this.stateWaiters.splice(i, 1);
|
|
308
|
+
w.resolve();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
writeStatus(nextAttemptAt) {
|
|
312
|
+
const status = {
|
|
313
|
+
state: this.state,
|
|
314
|
+
brokerAlias: this.d.config.broker.alias ?? `${this.d.config.broker.host}:${this.d.config.broker.port}`,
|
|
315
|
+
since: new Date(this.stateSince).toISOString(),
|
|
316
|
+
lastUpdated: new Date(this.d.now()).toISOString(),
|
|
317
|
+
...(nextAttemptAt === undefined ? {} : { nextAttemptAt: new Date(nextAttemptAt).toISOString() }),
|
|
318
|
+
};
|
|
319
|
+
try {
|
|
320
|
+
mkdirSync(dirname(this.d.statusFile), { recursive: true });
|
|
321
|
+
writeFileSync(this.d.statusFile, `${JSON.stringify(status, null, 2)}\n`, 'utf8');
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// 状态文件写失败不倒客户端
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/** 退避睡眠可被 stop 打断(100ms 片轮询),避免 SIGINT 卡最长 60s。 */
|
|
328
|
+
async sleepInterruptible(ms) {
|
|
329
|
+
let left = ms;
|
|
330
|
+
while (left > 0 && !this.stopped) {
|
|
331
|
+
const slice = Math.min(left, 100);
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, slice));
|
|
333
|
+
left -= slice;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
log(event) {
|
|
337
|
+
this.d.log(`${new Date(this.d.now()).toISOString()} ${event}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/serve/node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tcp broker 协议层(票B B1):单文件、客户端服务端共用。JSON 单帧单 ws
|
|
3
|
+
* 文本消息。B2 才有行为实现的帧(file-* 等)类型先定义全。
|
|
4
|
+
*/
|
|
5
|
+
export const PROTOCOL_VERSION = 1;
|
|
6
|
+
export const MAX_PAYLOAD_BYTES = 2 * 1024 * 1024;
|
|
7
|
+
/** 协议错误码(error.code),与各端报错文案共用。 */
|
|
8
|
+
export const ERR = {
|
|
9
|
+
BAD_AUTH: 'BAD_AUTH',
|
|
10
|
+
BAD_PROTO: 'BAD_PROTO',
|
|
11
|
+
UNKNOWN_MACHINE: 'UNKNOWN_MACHINE',
|
|
12
|
+
PEER_OFFLINE: 'PEER_OFFLINE',
|
|
13
|
+
MACHINE_NAME_COLLISION: 'MACHINE_NAME_COLLISION',
|
|
14
|
+
NOT_READY: 'NOT_READY',
|
|
15
|
+
/** B2 修订:file-begin 尺寸预拒(>64MiB)。 */
|
|
16
|
+
FILE_TOO_LARGE: 'FILE_TOO_LARGE',
|
|
17
|
+
};
|
|
18
|
+
const OPS = new Set([
|
|
19
|
+
'info',
|
|
20
|
+
'connect',
|
|
21
|
+
'connected',
|
|
22
|
+
'error',
|
|
23
|
+
'send',
|
|
24
|
+
'receipt',
|
|
25
|
+
'deliver',
|
|
26
|
+
'result',
|
|
27
|
+
'file-begin',
|
|
28
|
+
'file-chunk',
|
|
29
|
+
'file-end',
|
|
30
|
+
'file-result',
|
|
31
|
+
'roster',
|
|
32
|
+
'roster-result',
|
|
33
|
+
]);
|
|
34
|
+
/** 解析一帧 ws 文本消息;JSON 损坏或 op 未知返回 undefined(调用方丢弃)。 */
|
|
35
|
+
export function parseFrame(text) {
|
|
36
|
+
let obj;
|
|
37
|
+
try {
|
|
38
|
+
obj = JSON.parse(text);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (typeof obj !== 'object' || obj === null)
|
|
44
|
+
return undefined;
|
|
45
|
+
const op = obj.op;
|
|
46
|
+
return typeof op === 'string' && OPS.has(op) ? obj : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function encodeFrame(frame) {
|
|
49
|
+
return JSON.stringify(frame);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/serve/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,mCAAmC;AACnC,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;IAC5B,sBAAsB,EAAE,wBAAwB;IAChD,SAAS,EAAE,WAAW;IACtB,qCAAqC;IACrC,cAAc,EAAE,gBAAgB;CACxB,CAAC;AAgIX,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IAClB,MAAM;IACN,SAAS;IACT,WAAW;IACX,OAAO;IACP,MAAM;IACN,SAAS;IACT,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,aAAa;IACb,QAAQ;IACR,eAAe;CAChB,CAAC,CAAC;AAEH,sDAAsD;AACtD,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,OAAO,OAAO,EAAE,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,GAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC"}
|