@wenbin_wb/dsh-bridge 2.8.7 → 2.9.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 +295 -250
- package/README.en.md +408 -572
- package/README.md +430 -570
- package/client/client.js +3844 -3764
- package/client/index.js +140 -876
- package/client/mobile-styles.js +802 -0
- package/docs/fix-plan-202608.md +108 -0
- package/lib/auth/login-template.js +381 -381
- package/lib/auth/manager.js +531 -469
- package/lib/bridge-rpc.js +436 -511
- package/lib/cloudflared-manager.mjs +361 -345
- package/lib/compat.js +129 -0
- package/lib/feishu/index.js +225 -222
- package/lib/feishu/node.js +433 -409
- package/lib/index.js +1765 -1804
- package/lib/platform/base.js +147 -156
- package/lib/platform/commands.js +221 -0
- package/lib/platform/conversation-bridge.js +816 -1570
- package/lib/platform/dsh-storage.js +117 -0
- package/lib/platform/index.js +10 -10
- package/lib/platform/message-split.js +191 -0
- package/lib/platform/session-catalog.js +372 -0
- package/lib/platform/stream-slices.js +21 -0
- package/lib/qq/index.js +312 -309
- package/lib/qq/node.js +532 -533
- package/lib/telegram/index.js +215 -212
- package/lib/telegram/node.js +348 -350
- package/lib/tunnel-client.mjs +39 -15
- package/lib/wechat/gateway.js +973 -960
- package/lib/wechat/index.js +244 -241
- package/lib/wechat/media.js +285 -281
- package/lib/wechat/node.js +352 -350
- package/package.json +106 -102
package/lib/bridge-rpc.js
CHANGED
|
@@ -1,511 +1,436 @@
|
|
|
1
|
-
// DSH Bridge - RPC Interface (server side)
|
|
2
|
-
// Loopback-only RPC methods for browser UI
|
|
3
|
-
|
|
4
|
-
import QRCode from 'qrcode';
|
|
5
|
-
import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from './bridge-rpc-constants.js';
|
|
6
|
-
import { RateLimiter } from './security/rate-limiter.js';
|
|
7
|
-
|
|
8
|
-
export { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS };
|
|
9
|
-
|
|
10
|
-
const rpcRateLimiter = new RateLimiter({ maxRequests: 30, windowMs: 60000 });
|
|
11
|
-
|
|
12
|
-
function ok(value) {
|
|
13
|
-
return { ok: true, value };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function fail(code, message, details = {}) {
|
|
17
|
-
const allowedCodes = new Set([
|
|
18
|
-
'bad-request', 'cancelled', 'internal', 'settings-rejected', 'command-error'
|
|
19
|
-
]);
|
|
20
|
-
const safeCode = allowedCodes.has(code) ? code : 'bad-request';
|
|
21
|
-
return {
|
|
22
|
-
ok: false,
|
|
23
|
-
error: {
|
|
24
|
-
code: safeCode,
|
|
25
|
-
message,
|
|
26
|
-
details: { issues: [{ message }], ...details },
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 把登录态里的二维码载荷渲染成浏览器可展示的 dataURL(带缓存,避免重复生成)
|
|
32
|
-
async function renderQr(loginState) {
|
|
33
|
-
if (!loginState?.qrPayload) return null;
|
|
34
|
-
const cacheKey = `${loginState.qrKind}:${loginState.qrPayload.slice(0, 80)}`;
|
|
35
|
-
if (renderQr.cache && renderQr.cache.key === cacheKey) return renderQr.cache.url;
|
|
36
|
-
let url
|
|
37
|
-
const payload = loginState.qrPayload;
|
|
38
|
-
if (loginState.qrKind === 'img') {
|
|
39
|
-
url = /^data:/i.test(payload) ? payload : `data:image/png;base64,${payload}`;
|
|
40
|
-
} else {
|
|
41
|
-
try {
|
|
42
|
-
url = await QRCode.toDataURL(payload, {
|
|
43
|
-
width: 300, margin: 2, color: { dark: '#1F2421', light: '#FFFFFF' },
|
|
44
|
-
});
|
|
45
|
-
} catch { url = null; }
|
|
46
|
-
}
|
|
47
|
-
renderQr.cache = { key: cacheKey, url };
|
|
48
|
-
return url;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return fail('bad-request',
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
const
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatLogin) {
|
|
439
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
440
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
441
|
-
if (adminErr) return adminErr;
|
|
442
|
-
|
|
443
|
-
const { qrType } = payload;
|
|
444
|
-
const result = await wechat.login({ qrType });
|
|
445
|
-
if (!result.ok) return fail('bad-request', result.error ?? '登录启动失败');
|
|
446
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
447
|
-
return ok(value);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatSetAllowFrom) {
|
|
451
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
452
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
453
|
-
if (adminErr) return adminErr;
|
|
454
|
-
|
|
455
|
-
await wechat.setAllowFrom(payload.allowFrom);
|
|
456
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
457
|
-
return ok(value);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatSetConfig) {
|
|
461
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
462
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
463
|
-
if (adminErr) return adminErr;
|
|
464
|
-
|
|
465
|
-
await wechat.setConfig(payload);
|
|
466
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
467
|
-
return ok(value);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatStop) {
|
|
471
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
472
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
473
|
-
if (adminErr) return adminErr;
|
|
474
|
-
|
|
475
|
-
await wechat.stop();
|
|
476
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
477
|
-
return ok(value);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatStart) {
|
|
481
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
482
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
483
|
-
if (adminErr) return adminErr;
|
|
484
|
-
|
|
485
|
-
await wechat.gateway.start().catch((err) => {
|
|
486
|
-
logger.error('wechat start enabled: %s', err?.message ?? err);
|
|
487
|
-
});
|
|
488
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
489
|
-
return ok(value);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
if (endpoint === BRIDGE_ENDPOINTS.wechatUnbind) {
|
|
493
|
-
if (!wechat) return fail('bad-request', '微信 Bot 未初始化');
|
|
494
|
-
const adminErr = checkAdminAuth(authManager, payload);
|
|
495
|
-
if (adminErr) return adminErr;
|
|
496
|
-
|
|
497
|
-
await wechat.unbind();
|
|
498
|
-
const value = await wechatStatusValue(wechat, logger);
|
|
499
|
-
return ok(value);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return fail('bad-request', `Unknown endpoint: ${endpoint}`);
|
|
503
|
-
} catch (err) {
|
|
504
|
-
logger.error('RPC endpoint %s failed: %s', endpoint, err.message);
|
|
505
|
-
return fail('bad-request', err.message);
|
|
506
|
-
}
|
|
507
|
-
},
|
|
508
|
-
{ authority: 'loopback' }
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
|
|
1
|
+
// DSH Bridge - RPC Interface (server side)
|
|
2
|
+
// Loopback-only RPC methods for browser UI
|
|
3
|
+
|
|
4
|
+
import QRCode from 'qrcode';
|
|
5
|
+
import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from './bridge-rpc-constants.js';
|
|
6
|
+
import { RateLimiter } from './security/rate-limiter.js';
|
|
7
|
+
|
|
8
|
+
export { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS };
|
|
9
|
+
|
|
10
|
+
const rpcRateLimiter = new RateLimiter({ maxRequests: 30, windowMs: 60000 });
|
|
11
|
+
|
|
12
|
+
function ok(value) {
|
|
13
|
+
return { ok: true, value };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fail(code, message, details = {}) {
|
|
17
|
+
const allowedCodes = new Set([
|
|
18
|
+
'bad-request', 'cancelled', 'internal', 'settings-rejected', 'command-error'
|
|
19
|
+
]);
|
|
20
|
+
const safeCode = allowedCodes.has(code) ? code : 'bad-request';
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
error: {
|
|
24
|
+
code: safeCode,
|
|
25
|
+
message,
|
|
26
|
+
details: { issues: [{ message }], ...details },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 把登录态里的二维码载荷渲染成浏览器可展示的 dataURL(带缓存,避免重复生成)
|
|
32
|
+
async function renderQr(loginState) {
|
|
33
|
+
if (!loginState?.qrPayload) return null;
|
|
34
|
+
const cacheKey = `${loginState.qrKind}:${loginState.qrPayload.slice(0, 80)}`;
|
|
35
|
+
if (renderQr.cache && renderQr.cache.key === cacheKey) return renderQr.cache.url;
|
|
36
|
+
let url;
|
|
37
|
+
const payload = loginState.qrPayload;
|
|
38
|
+
if (loginState.qrKind === 'img') {
|
|
39
|
+
url = /^data:/i.test(payload) ? payload : `data:image/png;base64,${payload}`;
|
|
40
|
+
} else {
|
|
41
|
+
try {
|
|
42
|
+
url = await QRCode.toDataURL(payload, {
|
|
43
|
+
width: 300, margin: 2, color: { dark: '#1F2421', light: '#FFFFFF' },
|
|
44
|
+
});
|
|
45
|
+
} catch { url = null; }
|
|
46
|
+
}
|
|
47
|
+
renderQr.cache = { key: cacheKey, url };
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function checkAdminAuth(authManager, payload, { requireConfigured = false } = {}) {
|
|
52
|
+
if (!authManager) return null;
|
|
53
|
+
if (authManager.adminPolicy === 'open') return null;
|
|
54
|
+
// 若系统尚未设置任何管理密码或访客密码,允许免密管理
|
|
55
|
+
const hasAnyPassword = authManager.hasAdminPassword || authManager.hasPassword;
|
|
56
|
+
// T2.9:高危操作(备份导出/导入、隧道配置与启动、目录浏览、添加工作区、升级、重启)
|
|
57
|
+
// 在系统从未设置任何密码时不再静默放行,强制先完成一次密码设置,
|
|
58
|
+
// 杜绝"未设密码 = 局域网/隧道内任何人都可导出全部凭证"的裸奔状态被直接利用
|
|
59
|
+
if (requireConfigured && !hasAnyPassword) {
|
|
60
|
+
return fail('bad-request', '该操作涉及敏感配置:请先在「安全认证」中设置访问密码或管理密码后再执行');
|
|
61
|
+
}
|
|
62
|
+
if (!hasAnyPassword) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// 已设置密码时,必须提供经服务端校验有效的 adminToken(绝不依赖客户端自称的 isLocalhost)
|
|
66
|
+
if (payload?.adminToken && authManager.validateAdminSession(payload.adminToken)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return fail('bad-request', '操作已被拦截:需要管理员权限,请先在控制台输入管理密码解锁');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function installBridgeRpc(ctx, { service, authManager, platformManager, logger, saveCustomTunnelConfig, exportBackup, importBackup }) {
|
|
73
|
+
if (!ctx?.connection?.rpc?.handle) {
|
|
74
|
+
logger.warn('dsh-bridge: Connection RPC unavailable — UI will not work');
|
|
75
|
+
return () => {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return ctx.connection.rpc.handle(
|
|
79
|
+
BRIDGE_RPC_CHANNEL,
|
|
80
|
+
async (endpoint, payload = {}, signal) => {
|
|
81
|
+
if (signal?.aborted) return fail('cancelled', 'Request was cancelled');
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
if (endpoint === BRIDGE_ENDPOINTS.getStatus) {
|
|
85
|
+
const isAdmin = checkAdminAuth(authManager, payload) === null;
|
|
86
|
+
const status = await service.getStatus({ adminAuthValid: isAdmin });
|
|
87
|
+
return ok(status);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ---- 访问安全认证 ----
|
|
91
|
+
|
|
92
|
+
if (endpoint === BRIDGE_ENDPOINTS.authGetStatus) {
|
|
93
|
+
if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
|
|
94
|
+
const isAdmin = checkAdminAuth(authManager, payload) === null;
|
|
95
|
+
return ok(authManager.getStatus({ masked: !isAdmin }));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (endpoint === BRIDGE_ENDPOINTS.authUpdateConfig) {
|
|
99
|
+
if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
|
|
100
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
101
|
+
if (adminErr) return adminErr;
|
|
102
|
+
|
|
103
|
+
const { enabled, mode, scope, adminPolicy, password, adminPassword } = payload;
|
|
104
|
+
if (enabled != null) await authManager.setEnabled(enabled);
|
|
105
|
+
if (mode != null) await authManager.setMode(mode);
|
|
106
|
+
if (scope != null) await authManager.setScope(scope);
|
|
107
|
+
if (adminPolicy != null) await authManager.setAdminPolicy(adminPolicy);
|
|
108
|
+
if (password !== undefined) await authManager.setPassword(password);
|
|
109
|
+
if (adminPassword !== undefined) await authManager.setAdminPassword(adminPassword);
|
|
110
|
+
return ok(authManager.getStatus({ masked: false }));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (endpoint === BRIDGE_ENDPOINTS.authRegenerateToken) {
|
|
114
|
+
if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
|
|
115
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
116
|
+
if (adminErr) return adminErr;
|
|
117
|
+
|
|
118
|
+
await authManager.regenerateSecretToken();
|
|
119
|
+
return ok(authManager.getStatus({ masked: false }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (endpoint === BRIDGE_ENDPOINTS.authAdminUnlock) {
|
|
123
|
+
if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
|
|
124
|
+
const { password } = payload;
|
|
125
|
+
const res = await authManager.unlockAdmin(password);
|
|
126
|
+
if (res.ok) return ok({ adminToken: res.adminToken });
|
|
127
|
+
return fail('bad-request', res.error || '管理员密码错误');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (endpoint === BRIDGE_ENDPOINTS.authAdminLock) {
|
|
131
|
+
if (!authManager) return fail('bad-request', 'AuthManager 未初始化');
|
|
132
|
+
if (payload?.adminToken) authManager.revokeAdminSession(payload.adminToken);
|
|
133
|
+
return ok({ locked: true });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (endpoint === BRIDGE_ENDPOINTS.saveCustomTunnelConfig) {
|
|
137
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
138
|
+
if (adminErr) return adminErr;
|
|
139
|
+
|
|
140
|
+
// 未提供的字段保持 undefined 透传:服务端视为"保留现值"
|
|
141
|
+
const { serverUrl, accessToken } = payload;
|
|
142
|
+
await saveCustomTunnelConfig(serverUrl, accessToken);
|
|
143
|
+
const status = await service.getStatus();
|
|
144
|
+
return ok(status);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (endpoint === BRIDGE_ENDPOINTS.saveCloudflaredConfig) {
|
|
148
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
149
|
+
if (adminErr) return adminErr;
|
|
150
|
+
|
|
151
|
+
const { token, hostname } = payload;
|
|
152
|
+
await service.saveCloudflaredConfig({ token, hostname });
|
|
153
|
+
const status = await service.getStatus();
|
|
154
|
+
return ok(status);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (endpoint === BRIDGE_ENDPOINTS.setTunnelAutoStart) {
|
|
158
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
159
|
+
if (adminErr) return adminErr;
|
|
160
|
+
|
|
161
|
+
const { tunnel, autoStart } = payload;
|
|
162
|
+
await service.setTunnelAutoStart({ tunnel, autoStart });
|
|
163
|
+
const status = await service.getStatus();
|
|
164
|
+
return ok(status);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (endpoint === BRIDGE_ENDPOINTS.setLanIp) {
|
|
168
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
169
|
+
if (adminErr) return adminErr;
|
|
170
|
+
|
|
171
|
+
const { ip } = payload;
|
|
172
|
+
const status = await service.setLanIp({ ip });
|
|
173
|
+
return ok(status);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (endpoint === BRIDGE_ENDPOINTS.startCustomTunnel) {
|
|
177
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
178
|
+
if (adminErr) return adminErr;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
await service.startCustomTunnel();
|
|
182
|
+
const status = await service.getStatus();
|
|
183
|
+
return ok(status);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
logger.error('Failed to start custom tunnel: %s', err.message);
|
|
186
|
+
return fail('bad-request', err.message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (endpoint === BRIDGE_ENDPOINTS.stopCustomTunnel) {
|
|
191
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
192
|
+
if (adminErr) return adminErr;
|
|
193
|
+
|
|
194
|
+
service.stopCustomTunnel();
|
|
195
|
+
const status = await service.getStatus();
|
|
196
|
+
return ok(status);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (endpoint === BRIDGE_ENDPOINTS.startCloudflared) {
|
|
200
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
201
|
+
if (adminErr) return adminErr;
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
await service.startCloudflared();
|
|
205
|
+
const status = await service.getStatus();
|
|
206
|
+
return ok(status);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
logger.error('Failed to start cloudflared: %s', err.message);
|
|
209
|
+
return fail('bad-request', err.message);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (endpoint === BRIDGE_ENDPOINTS.stopCloudflared) {
|
|
214
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
215
|
+
if (adminErr) return adminErr;
|
|
216
|
+
|
|
217
|
+
service.stopCloudflared();
|
|
218
|
+
const status = await service.getStatus();
|
|
219
|
+
return ok(status);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (endpoint === BRIDGE_ENDPOINTS.resetCloudflared) {
|
|
223
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
224
|
+
if (adminErr) return adminErr;
|
|
225
|
+
|
|
226
|
+
await service.resetCloudflared();
|
|
227
|
+
const status = await service.getStatus();
|
|
228
|
+
return ok(status);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (endpoint === BRIDGE_ENDPOINTS.checkVersion) {
|
|
232
|
+
const result = await service.checkVersion();
|
|
233
|
+
return ok(result);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (endpoint === BRIDGE_ENDPOINTS.upgradePlugin) {
|
|
237
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
238
|
+
if (adminErr) return adminErr;
|
|
239
|
+
|
|
240
|
+
const result = await service.upgradePlugin(payload);
|
|
241
|
+
return ok(result);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (endpoint === BRIDGE_ENDPOINTS.restartDsh) {
|
|
245
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
246
|
+
if (adminErr) return adminErr;
|
|
247
|
+
|
|
248
|
+
const result = await service.restartDsh();
|
|
249
|
+
return ok(result);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (endpoint === BRIDGE_ENDPOINTS.exportBackup) {
|
|
253
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
254
|
+
if (adminErr) return adminErr;
|
|
255
|
+
|
|
256
|
+
if (!exportBackup) return fail('bad-request', '备份导出服务不可用');
|
|
257
|
+
const backup = await exportBackup();
|
|
258
|
+
return ok(backup);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (endpoint === BRIDGE_ENDPOINTS.importBackup) {
|
|
262
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
263
|
+
if (adminErr) return adminErr;
|
|
264
|
+
|
|
265
|
+
if (!importBackup) return fail('bad-request', '备份导入服务不可用');
|
|
266
|
+
const result = await importBackup(payload?.backup);
|
|
267
|
+
const status = await service.getStatus({ adminAuthValid: true });
|
|
268
|
+
return ok({ result, status });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (endpoint === BRIDGE_ENDPOINTS.diagnoseNetwork) {
|
|
272
|
+
const result = await service.diagnoseNetwork();
|
|
273
|
+
return ok(result);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (endpoint === BRIDGE_ENDPOINTS.getSystemMetrics) {
|
|
277
|
+
const metrics = service.getSystemMetrics();
|
|
278
|
+
return ok(metrics);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ---- 远程工作区管理与目录浏览 ----
|
|
282
|
+
|
|
283
|
+
if (endpoint === BRIDGE_ENDPOINTS.listRemoteDirectories) {
|
|
284
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
285
|
+
if (adminErr) return adminErr;
|
|
286
|
+
|
|
287
|
+
const clientKey = payload?.clientIp || payload?.adminToken || 'default';
|
|
288
|
+
const rateCheck = rpcRateLimiter.check(clientKey, 30);
|
|
289
|
+
if (!rateCheck.allowed) {
|
|
290
|
+
return fail('bad-request', `请求过于频繁,请等待 ${rateCheck.retryAfterSec} 秒后再试`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = await service.listRemoteDirectories(payload?.path);
|
|
294
|
+
return ok(result);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (endpoint === BRIDGE_ENDPOINTS.addRemoteWorkspace) {
|
|
298
|
+
const adminErr = checkAdminAuth(authManager, payload, { requireConfigured: true });
|
|
299
|
+
if (adminErr) return adminErr;
|
|
300
|
+
|
|
301
|
+
const clientKey = payload?.clientIp || payload?.adminToken || 'default';
|
|
302
|
+
const rateCheck = rpcRateLimiter.check(clientKey, 20);
|
|
303
|
+
if (!rateCheck.allowed) {
|
|
304
|
+
return fail('bad-request', `添加工作区请求过于频繁,请等待 ${rateCheck.retryAfterSec} 秒后再试`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const result = await service.addWorkspace(payload?.path);
|
|
308
|
+
return ok(result);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (endpoint === BRIDGE_ENDPOINTS.listWorkspaces) {
|
|
312
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
313
|
+
if (adminErr) return adminErr;
|
|
314
|
+
|
|
315
|
+
const result = await service.getWorkspaces();
|
|
316
|
+
return ok(result);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---- 平台管理器(多 IM 平台)----
|
|
320
|
+
|
|
321
|
+
if (endpoint === BRIDGE_ENDPOINTS.listPlatforms) {
|
|
322
|
+
if (!platformManager) return ok({});
|
|
323
|
+
// 每个平台的 login.qrPayload 渲染为 dataURL 后返回
|
|
324
|
+
const raw = platformManager.getStatus();
|
|
325
|
+
const out = {};
|
|
326
|
+
for (const [id, status] of Object.entries(raw)) {
|
|
327
|
+
let qr = null;
|
|
328
|
+
try { qr = await renderQr(status.login).catch(() => null); } catch { /* ignore */ }
|
|
329
|
+
out[id] = { ...status, login: { ...(status.login ?? {}), qr, qrPayload: undefined, qrKind: undefined } };
|
|
330
|
+
}
|
|
331
|
+
return ok(out);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ---- 平台操作(统一接口)----
|
|
335
|
+
|
|
336
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformLogin) {
|
|
337
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
338
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
339
|
+
if (adminErr) return adminErr;
|
|
340
|
+
|
|
341
|
+
const { platformId, qrType } = payload;
|
|
342
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
343
|
+
const platform = platformManager.get(platformId);
|
|
344
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
345
|
+
const result = await platform.login({ qrType });
|
|
346
|
+
if (!result.ok) return fail('bad-request', result.error ?? '登录启动失败');
|
|
347
|
+
const status = platform.getStatus();
|
|
348
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
349
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformSetAllowFrom) {
|
|
353
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
354
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
355
|
+
if (adminErr) return adminErr;
|
|
356
|
+
|
|
357
|
+
const { platformId, allowFrom } = payload;
|
|
358
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
359
|
+
const platform = platformManager.get(platformId);
|
|
360
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
361
|
+
await platform.setAllowFrom(allowFrom);
|
|
362
|
+
const status = platform.getStatus();
|
|
363
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
364
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformSetConfig) {
|
|
368
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
369
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
370
|
+
if (adminErr) return adminErr;
|
|
371
|
+
|
|
372
|
+
const { platformId, ...config } = payload;
|
|
373
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
374
|
+
const platform = platformManager.get(platformId);
|
|
375
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
376
|
+
await platform.setConfig(config);
|
|
377
|
+
const status = platform.getStatus();
|
|
378
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
379
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformStop) {
|
|
383
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
384
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
385
|
+
if (adminErr) return adminErr;
|
|
386
|
+
|
|
387
|
+
const { platformId } = payload;
|
|
388
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
389
|
+
const platform = platformManager.get(platformId);
|
|
390
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
391
|
+
await platform.stop();
|
|
392
|
+
const status = platform.getStatus();
|
|
393
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
394
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformStart) {
|
|
398
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
399
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
400
|
+
if (adminErr) return adminErr;
|
|
401
|
+
|
|
402
|
+
const { platformId } = payload;
|
|
403
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
404
|
+
const platform = platformManager.get(platformId);
|
|
405
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
406
|
+
await platform.start();
|
|
407
|
+
const status = platform.getStatus();
|
|
408
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
409
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (endpoint === BRIDGE_ENDPOINTS.platformUnbind) {
|
|
413
|
+
if (!platformManager) return fail('bad-request', 'PlatformManager 未初始化');
|
|
414
|
+
const adminErr = checkAdminAuth(authManager, payload);
|
|
415
|
+
if (adminErr) return adminErr;
|
|
416
|
+
|
|
417
|
+
const { platformId } = payload;
|
|
418
|
+
if (!platformId) return fail('bad-request', '缺少 platformId 参数');
|
|
419
|
+
const platform = platformManager.get(platformId);
|
|
420
|
+
if (!platform) return fail('bad-request', `平台未注册: ${platformId}`);
|
|
421
|
+
await platform.unbind();
|
|
422
|
+
const status = platform.getStatus();
|
|
423
|
+
const qr = await renderQr(status.login).catch(() => null);
|
|
424
|
+
return ok({ ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return fail('bad-request', `Unknown endpoint: ${endpoint}`);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
logger.error('RPC endpoint %s failed: %s', endpoint, err.message);
|
|
430
|
+
return fail('bad-request', err.message);
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
{ authority: 'loopback' }
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|