dsh-bridge-gateway 0.2.2 → 0.3.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 +74 -0
- package/README.md +17 -3
- package/README.zh.md +18 -2
- package/assets/apple-touch-icon-180.png +0 -0
- package/assets/icon-192.png +0 -0
- package/assets/icon-512.png +0 -0
- package/assets/icon-maskable-192.png +0 -0
- package/assets/icon-maskable-512.png +0 -0
- package/client/client.js +302 -41
- package/client/index.js +438 -42
- package/docs/banner.jpg +0 -0
- package/docs/custom-tunnel.md +5 -5
- package/docs/platform-abstraction-design.md +1 -1
- package/docs/qq-usage.md +6 -6
- package/docs/screenshots/remote-auth-login-light.jpg +0 -0
- package/docs/screenshots/remote-auth-login.jpg +0 -0
- package/docs/wechat-usage.md +1 -1
- package/lib/auth/brand-assets.js +131 -0
- package/lib/auth/login-template.js +503 -361
- package/lib/auth/manager.js +47 -12
- package/lib/auth/theme-tokens.js +142 -0
- package/lib/bridge-rpc.js +2 -2
- package/lib/cloudflared-manager.mjs +1 -1
- package/lib/feishu/gateway.js +17 -17
- package/lib/feishu/index.js +3 -3
- package/lib/feishu/lark-bundled.mjs +288 -469
- package/lib/feishu/node.js +7 -7
- package/lib/gateway/cert.js +1 -1
- package/lib/index.js +210 -80
- package/lib/paths.js +43 -14
- package/lib/platform/base.js +2 -2
- package/lib/platform/conversation-bridge.js +17 -17
- package/lib/platform/index.js +1 -1
- package/lib/platform/manager.js +4 -4
- package/lib/qq/gateway.js +10 -10
- package/lib/qq/index.js +11 -11
- package/lib/qq/node.js +17 -17
- package/lib/telegram/gateway.js +10 -10
- package/lib/telegram/index.js +3 -3
- package/lib/telegram/node.js +5 -5
- package/lib/wechat/gateway.js +6 -6
- package/lib/wechat/index.js +4 -4
- package/lib/wechat/media.js +1 -1
- package/lib/wechat/node.js +13 -13
- package/package.json +6 -3
package/lib/auth/manager.js
CHANGED
|
@@ -320,7 +320,7 @@ export class AuthManager {
|
|
|
320
320
|
allowLoopback: this.allowLoopback,
|
|
321
321
|
})
|
|
322
322
|
} catch (err) {
|
|
323
|
-
this.logger.error?.('[dsh-bridge auth] persist failed:', err?.message ?? err)
|
|
323
|
+
this.logger.error?.('[dsh-bridge-gateway auth] persist failed:', err?.message ?? err)
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
}
|
|
@@ -422,6 +422,28 @@ export class AuthManager {
|
|
|
422
422
|
return false
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
/**
|
|
426
|
+
* 剩余锁定毫秒数(只读,不新增状态)。
|
|
427
|
+
*
|
|
428
|
+
* 登录页用它在被限流时展示倒计时并置灰提交按钮 —— 此前 `locked` 虽是
|
|
429
|
+
* renderLoginPage 的入参却完全没被使用,被锁的访客看到的是一个可以一直
|
|
430
|
+
* 点击、一直收 401 的表单。
|
|
431
|
+
*
|
|
432
|
+
* @param {string} ip 客户端 IP
|
|
433
|
+
* @returns {number} 剩余毫秒;未锁定时为 0
|
|
434
|
+
*/
|
|
435
|
+
lockRemainingMs(ip) {
|
|
436
|
+
if (!ip) return 0
|
|
437
|
+
const record = this.rateLimits.get(ip)
|
|
438
|
+
if (!record || !record.lockUntil) return 0
|
|
439
|
+
const remain = record.lockUntil - Date.now()
|
|
440
|
+
if (remain <= 0) {
|
|
441
|
+
this.rateLimits.delete(ip)
|
|
442
|
+
return 0
|
|
443
|
+
}
|
|
444
|
+
return remain
|
|
445
|
+
}
|
|
446
|
+
|
|
425
447
|
recordFailedAttempt(ip) {
|
|
426
448
|
if (!ip) return
|
|
427
449
|
const now = Date.now()
|
|
@@ -429,7 +451,7 @@ export class AuthManager {
|
|
|
429
451
|
record.failedCount += 1
|
|
430
452
|
if (record.failedCount >= MAX_FAILED_ATTEMPTS) {
|
|
431
453
|
record.lockUntil = now + LOCKOUT_PERIOD_MS
|
|
432
|
-
this.logger.warn?.(`[dsh-bridge auth] IP ${ip} locked out for 60s due to repeated failed attempts`)
|
|
454
|
+
this.logger.warn?.(`[dsh-bridge-gateway auth] IP ${ip} locked out for 60s due to repeated failed attempts`)
|
|
433
455
|
}
|
|
434
456
|
this.rateLimits.set(ip, record)
|
|
435
457
|
}
|
|
@@ -474,7 +496,7 @@ export class AuthManager {
|
|
|
474
496
|
try {
|
|
475
497
|
await this.onPersist?.({ blacklist: [...this.blacklist] })
|
|
476
498
|
} catch (err) {
|
|
477
|
-
this.logger?.warn?.('[dsh-bridge auth] blacklist persist failed:', err?.message ?? err)
|
|
499
|
+
this.logger?.warn?.('[dsh-bridge-gateway auth] blacklist persist failed:', err?.message ?? err)
|
|
478
500
|
}
|
|
479
501
|
}
|
|
480
502
|
|
|
@@ -500,6 +522,19 @@ export class AuthManager {
|
|
|
500
522
|
const isCloudflare = Boolean(isLoopback && (req.headers?.['cf-ray'] || req.headers?.['cf-connecting-ip']))
|
|
501
523
|
const isPublicTunnel = isCustomTunnel || isCloudflare
|
|
502
524
|
|
|
525
|
+
// 「真正的宿主机物理访问」判定 —— 三个条件缺一不可:
|
|
526
|
+
// 1) TCP 对端是环回
|
|
527
|
+
// 2) 不是隧道转发(隧道对端同样是 127.0.0.1)
|
|
528
|
+
// 3) Host 指向本机(**这条不能省**:同机反向代理 Caddy/Nginx 转发公网流量时,
|
|
529
|
+
// 对端也是 127.0.0.1,且不带 Cloudflare 标头,只看 1)+2) 会把公网流量误判为本机)
|
|
530
|
+
// 该判定同时服务两处:环回免认证,以及登录页"是否展示 reset-auth 救急路径"。
|
|
531
|
+
// 收敛成一个变量,避免两处各写一份而漂移。
|
|
532
|
+
const hostHeader = String(req.headers?.host || '')
|
|
533
|
+
const isGenuineLocal = Boolean(
|
|
534
|
+
isLoopback && !isPublicTunnel &&
|
|
535
|
+
(hostHeader.startsWith('127.0.0.1') || hostHeader.startsWith('localhost') || hostHeader === '')
|
|
536
|
+
)
|
|
537
|
+
|
|
503
538
|
// 黑名单判定 —— 判定依据是【隧道转发的真实来源】,不是 TCP 对端。
|
|
504
539
|
// 经隧道时 TCP 对端恒为 127.0.0.1,真实 IP 在 x-forwarded-for / cf-connecting-ip;
|
|
505
540
|
// 两个都取不到时退回对端地址(直连场景)。此判定必须在 loopback 放行之前 ——
|
|
@@ -509,16 +544,14 @@ export class AuthManager {
|
|
|
509
544
|
|| ''
|
|
510
545
|
const judgeIp = (isPublicTunnel && forwarded) ? forwarded : remote
|
|
511
546
|
if (this.isBlacklisted(judgeIp)) {
|
|
512
|
-
this.logger.warn?.(`[dsh-bridge auth] blacklisted IP ${judgeIp} rejected (${isPublicTunnel ? 'tunnel' : 'direct'})`)
|
|
513
|
-
|
|
547
|
+
this.logger.warn?.(`[dsh-bridge-gateway auth] blacklisted IP ${judgeIp} rejected (${isPublicTunnel ? 'tunnel' : 'direct'})`)
|
|
548
|
+
// 被拉黑者仍会看到登录页,故同样带上来源判定(展示用,不参与鉴权)
|
|
549
|
+
return { authenticated: false, blacklisted: true, genuineLocal: false }
|
|
514
550
|
}
|
|
515
551
|
|
|
516
552
|
// 2. 本地环回免认证(真正的宿主机物理浏览器 127.0.0.1 访问,非 Tunnel 转发)
|
|
517
|
-
if (this.allowLoopback &&
|
|
518
|
-
|
|
519
|
-
if (host.startsWith('127.0.0.1') || host.startsWith('localhost') || host === '') {
|
|
520
|
-
return { authenticated: true, loopback: true }
|
|
521
|
-
}
|
|
553
|
+
if (this.allowLoopback && isGenuineLocal) {
|
|
554
|
+
return { authenticated: true, loopback: true, genuineLocal: true }
|
|
522
555
|
}
|
|
523
556
|
|
|
524
557
|
// 3. 检查防护范围 (scope: 'all' | 'public_only' | 'lan_only')
|
|
@@ -551,8 +584,10 @@ export class AuthManager {
|
|
|
551
584
|
}
|
|
552
585
|
}
|
|
553
586
|
|
|
554
|
-
//
|
|
555
|
-
|
|
587
|
+
// 未授权。
|
|
588
|
+
// genuineLocal 是**展示用**元信息(登录页据此决定是否展示 reset-auth 救急
|
|
589
|
+
// 路径),不参与任何鉴权决策 —— 鉴权在上面的各分支已经判完。
|
|
590
|
+
return { authenticated: false, genuineLocal: isGenuineLocal }
|
|
556
591
|
}
|
|
557
592
|
|
|
558
593
|
_cleanupExpired() {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// lib/auth/theme-tokens.js
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// 登录页设计 token 层:DSH 设计系统的静态快照 + 亮/暗双主题生成
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// 为什么需要这一层(两个硬约束):
|
|
6
|
+
//
|
|
7
|
+
// 1. 登录页跑在 DSH 之前 —— 它是网关在未认证时自己吐出的 HTML,DSH 的
|
|
8
|
+
// `--dsw-*` 样式表根本没加载。所以每个 token 都必须带静态 hex 兜底,
|
|
9
|
+
// 写成 `var(--dsw-x, #hex)` 形式:DSH token 在场就跟随,不在场就用快照。
|
|
10
|
+
// 这与本仓库既有惯例一致(见 client/index.js 里每一处内联样式)。
|
|
11
|
+
//
|
|
12
|
+
// 2. `color-mix()` 在 Chrome 100 上整条声明失效 —— 本插件的浏览器下限是
|
|
13
|
+
// Chrome 100(client/build.mjs 的 target: ['chrome100'],另有 2026-10-11
|
|
14
|
+
// 统信 UOS / 360 浏览器 Chrome 108 实测记录)。DSH 的 token 表里有若干别名
|
|
15
|
+
// 是用 color-mix() 算出来的(--dsw-alias-label-deep-diving / -shimmer /
|
|
16
|
+
// --dsw-alias-tooltip-key-bg / --dsw-alias-bg-document-selection 等)。
|
|
17
|
+
// 在旧内核上这些变量解析为空 —— 不是降级,是"文字直接消失"。
|
|
18
|
+
// → 因此本文件**只收录可直接写死的静态色值**,一个 color-mix() 都不引。
|
|
19
|
+
//
|
|
20
|
+
// 快照来源:@deepseek-ai/dsh-client-ui-theme lib/client.js (~line 1148)
|
|
21
|
+
// 即 DSH 的 design-platform 样式表(--dsw-static-* 原始调色板 +
|
|
22
|
+
// --dsw-alias-* 语义层)。DSH 升级后如需跟随,重新比对该处即可;
|
|
23
|
+
// 即使不跟也**不会崩**,只是配色停留在快照版本。
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* token 表:本地变量名 ← DSH token 名 + 亮/暗静态快照值。
|
|
28
|
+
*
|
|
29
|
+
* 只收录登录页**真正用得到**的语义槽位 —— 每个 token 都必须在 login-template.js
|
|
30
|
+
* 里被 var() 引用,否则就该删掉(同一个"无死参数"标准,见模板头部注释第 3 条)。
|
|
31
|
+
* `dsh` 字段仅作溯源与 var() 兜底用。
|
|
32
|
+
*
|
|
33
|
+
* 值为纯静态色(hex / rgba),**禁止**出现 color-mix() 等 Chrome 100 不支持的函数。
|
|
34
|
+
* 注意:这里有意不收 DSH 的成功绿 #22c55e —— 旧版登录页误把"成功色"当成品牌主色,
|
|
35
|
+
* 测试里有反向断言钉死它不得再出现。
|
|
36
|
+
*/
|
|
37
|
+
export const TOKEN_TABLE = [
|
|
38
|
+
// ---- 画布与层次 ----
|
|
39
|
+
// 画布有意不用 --dsw-alias-bg-base:DSH 里那个值是纯白(亮)/ 近黑(暗),
|
|
40
|
+
// 因为 DSH 的卡片永远叠在有遮罩的弹层上。登录页没有遮罩,白卡片贴纯白画布
|
|
41
|
+
// 会失去层次。亮色改用 DSH 的模块层色 #f5f6f7 把白卡片托起来(dsh: null 表示
|
|
42
|
+
// 这是刻意的快照值,不跟随任何 DSH token)。
|
|
43
|
+
{ name: 'bg', dsh: null, light: '#f5f6f7', dark: '#151517', note: '页面画布(亮色取 DSH 模块层色以拉开层次)' },
|
|
44
|
+
{ name: 'layer-2', dsh: '--dsw-alias-bg-layer-2', light: '#ffffff', dark: '#2c2c2e', note: '卡片面' },
|
|
45
|
+
{ name: 'module', dsh: '--dsw-alias-bg-module-platform', light: '#f5f6f7', dark: '#353638', note: '模块底(代码块/帮助面板)' },
|
|
46
|
+
|
|
47
|
+
// ---- 文字 ----
|
|
48
|
+
{ name: 'label', dsh: '--dsw-alias-label-primary', light: '#0f1115', dark: '#f9fafb', note: '正文' },
|
|
49
|
+
{ name: 'label-2', dsh: '--dsw-alias-label-secondary', light: '#61666b', dark: '#cfd3d6', note: '次要' },
|
|
50
|
+
{ name: 'label-3', dsh: '--dsw-alias-label-tertiary', light: '#81858c', dark: '#adb2b8', note: '三级(页脚/占位符)' },
|
|
51
|
+
{ name: 'label-fg', dsh: '--dsw-alias-label-primary-foreground', light: '#ffffff', dark: '#0f1115', note: '强调色上的前景' },
|
|
52
|
+
|
|
53
|
+
// ---- 边框 ----
|
|
54
|
+
{ name: 'border', dsh: '--dsw-alias-border-l2', light: 'rgba(0,0,0,0.10)', dark: 'rgba(255,255,255,0.12)', note: '常规描边' },
|
|
55
|
+
{ name: 'border-strong', dsh: '--dsw-alias-border-l4', light: 'rgba(0,0,0,0.16)', dark: 'rgba(255,255,255,0.20)', note: '强描边' },
|
|
56
|
+
|
|
57
|
+
// ---- 表单 ----
|
|
58
|
+
{ name: 'input-bg', dsh: '--dsw-specific-login-input', light: '#f9fafb', dark: '#1b1b1c', note: 'DSH 专有"登录输入底"token' },
|
|
59
|
+
|
|
60
|
+
// ---- 强调 / 业务色 ----
|
|
61
|
+
{ name: 'accent', dsh: '--dsw-alias-state-business-primary', light: '#4176e6', dark: '#7aaaff', note: '主 CTA + 鲸鱼标(DSH 业务蓝)' },
|
|
62
|
+
{ name: 'accent-hover', dsh: '--dsw-alias-button-info-hover', light: '#7aaaff', dark: '#4176e6', note: 'CTA 悬停(同 DSH info 按钮语义)' },
|
|
63
|
+
{ name: 'focus-ring', dsh: '--dsw-alias-state-business-primary', light: 'rgba(65,118,230,0.22)', dark: 'rgba(122,170,255,0.28)', note: '输入框聚焦光晕' },
|
|
64
|
+
{ name: 'link', dsh: '--dsw-alias-link', light: '#4176e6', dark: '#7aaaff', note: '链接' },
|
|
65
|
+
|
|
66
|
+
// ---- 交互态 ----
|
|
67
|
+
{ name: 'hover', dsh: '--dsw-alias-interactive-bg-hover', light: 'rgba(38,49,72,0.06)', dark: 'rgba(255,255,255,0.08)', note: '通用悬停底' },
|
|
68
|
+
|
|
69
|
+
// ---- 状态:错误 ----
|
|
70
|
+
{ name: 'danger', dsh: '--dsw-alias-state-error-primary', light: '#ec1313', dark: '#f25a5a', note: '错误文字' },
|
|
71
|
+
{ name: 'danger-bg', dsh: '--dsw-alias-interactive-bg-hover-danger', light: 'rgba(236,19,19,0.05)', dark: 'rgba(242,90,90,0.15)', note: '错误底' },
|
|
72
|
+
{ name: 'danger-border', dsh: '--dsw-alias-state-error-secondary', light: 'rgba(242,90,90,0.45)', dark: 'rgba(242,90,90,0.40)', note: '错误描边' },
|
|
73
|
+
|
|
74
|
+
// ---- 状态:警示(锁定倒计时)----
|
|
75
|
+
{ name: 'warn', dsh: '--dsw-alias-state-warn-primary', light: '#f59e0b', dark: '#f59e0b', note: '警示文字' },
|
|
76
|
+
{ name: 'warn-bg', dsh: '--dsw-alias-state-warn-tertiary', light: '#fef5e7', dark: '#27241f', note: '警示底' },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 圆角梯度,对齐 DSH --dsw-radius-*。
|
|
81
|
+
*
|
|
82
|
+
* 只保留登录页真正用到的档位(xs / sm / md / xl)—— 与 TOKEN_TABLE 同一条标准:
|
|
83
|
+
* 没人用的就不放进来,需要时再加。DSH 自己全量输出 6 档,但那是它的设计系统;
|
|
84
|
+
* 这里是一个具体页面的 token 层。
|
|
85
|
+
*/
|
|
86
|
+
const RADIUS = { xs: 4, sm: 8, md: 12, xl: 20 };
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
/** 动效时长与缓动(对齐 DSH --ds-transition-duration* / --ds-ease-in-out)。 */
|
|
90
|
+
const MOTION = {
|
|
91
|
+
ease: 'cubic-bezier(.4, 0, .2, 1)',
|
|
92
|
+
fast: '.1s',
|
|
93
|
+
base: '.2s',
|
|
94
|
+
slow: '.3s',
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 把 token 表渲染成 CSS 自定义属性块。
|
|
99
|
+
*
|
|
100
|
+
* 输出结构:
|
|
101
|
+
* :root { ...亮色... } ← 默认(无 JS 也正确)
|
|
102
|
+
* @media (prefers-color-scheme: dark) { :root:not([data-gw-theme=light]) {...} }
|
|
103
|
+
* :root[data-gw-theme=dark] { ...暗色... } ← 显式覆盖
|
|
104
|
+
*
|
|
105
|
+
* 默认走亮色是有意的:DSH 自身默认亮色(见 docs/screenshots/admin-lock-screen.jpg),
|
|
106
|
+
* 旧版登录页写死暗色,导致远程用户经历"深绿 → 纯白"的硬切闪变。
|
|
107
|
+
*
|
|
108
|
+
* @returns {string} CSS 文本(不含 <style> 标签)
|
|
109
|
+
*/
|
|
110
|
+
export function renderTokenCss() {
|
|
111
|
+
// dsh 为 null 的 token 是刻意的快照值,不跟随任何 DSH token(见 TOKEN_TABLE 注释)
|
|
112
|
+
const val = (t, v) => (t.dsh ? `var(${t.dsh}, ${v})` : v);
|
|
113
|
+
const rootVars = TOKEN_TABLE.map((t) => ` --gw-${t.name}: ${val(t, t.light)};`).join('\n');
|
|
114
|
+
const darkVarsList = TOKEN_TABLE.map((t) => ` --gw-${t.name}: ${val(t, t.dark)};`).join('\n');
|
|
115
|
+
const darkExplicit = TOKEN_TABLE.map((t) => ` --gw-${t.name}: ${val(t, t.dark)};`).join('\n');
|
|
116
|
+
// 从 RADIUS 对象生成,而不是逐个手抄 —— 手抄会在增删档位时漂移成 `undefinedpx`
|
|
117
|
+
const radiusVars = Object.entries(RADIUS)
|
|
118
|
+
.map(([k, v]) => ` --gw-radius-${k}: ${v}px;`)
|
|
119
|
+
.join('\n');
|
|
120
|
+
|
|
121
|
+
return `:root {
|
|
122
|
+
${rootVars}
|
|
123
|
+
${radiusVars}
|
|
124
|
+
--gw-ease: ${MOTION.ease};
|
|
125
|
+
--gw-dur-fast: ${MOTION.fast};
|
|
126
|
+
--gw-dur: ${MOTION.base};
|
|
127
|
+
--gw-dur-slow: ${MOTION.slow};
|
|
128
|
+
--gw-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
129
|
+
--gw-font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', Consolas, 'Liberation Mono', Menlo, Courier, 'PingFang SC', 'Microsoft YaHei', monospace;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@media (prefers-color-scheme: dark) {
|
|
133
|
+
:root:not([data-gw-theme='light']) {
|
|
134
|
+
${darkVarsList}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:root[data-gw-theme='dark'] {
|
|
139
|
+
${darkExplicit}
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
}
|
package/lib/bridge-rpc.js
CHANGED
|
@@ -52,7 +52,7 @@ async function renderQr(loginState) {
|
|
|
52
52
|
async function wechatStatusValue(wechatService, logger) {
|
|
53
53
|
const status = wechatService.getStatus();
|
|
54
54
|
const qr = await renderQr(status.login).catch((err) => {
|
|
55
|
-
logger.warn('dsh-bridge: render wechat qr failed: %s', err?.message ?? err);
|
|
55
|
+
logger.warn('dsh-bridge-gateway: render wechat qr failed: %s', err?.message ?? err);
|
|
56
56
|
return null;
|
|
57
57
|
});
|
|
58
58
|
return { ...status, login: { ...status.login, qr, qrPayload: undefined, qrKind: undefined } };
|
|
@@ -185,7 +185,7 @@ async function bridgeChannelRequest(req, res, connection, channel, handler) {
|
|
|
185
185
|
|
|
186
186
|
export function installBridgeRpc(ctx, { service, authManager, wechat, platformManager, logger, saveCustomTunnelConfig, exportBackup, importBackup }) {
|
|
187
187
|
if (!ctx?.connection) {
|
|
188
|
-
logger.warn('dsh-bridge: Connection service unavailable — UI will not work');
|
|
188
|
+
logger.warn('dsh-bridge-gateway: Connection service unavailable — UI will not work');
|
|
189
189
|
return () => {};
|
|
190
190
|
}
|
|
191
191
|
|
|
@@ -173,7 +173,7 @@ export class CloudflaredManager {
|
|
|
173
173
|
const binPath = join(binDir, name);
|
|
174
174
|
this.binaryPath = binPath;
|
|
175
175
|
|
|
176
|
-
// 2. 检查本地 cloudflared 缓存(默认 ~/.dsh-bridge-cloudflared/bin)里是否已存在且可用
|
|
176
|
+
// 2. 检查本地 cloudflared 缓存(默认 ~/.dsh-bridge-gateway-cloudflared/bin)里是否已存在且可用
|
|
177
177
|
if (existsSync(binPath)) {
|
|
178
178
|
try {
|
|
179
179
|
const s = await stat(binPath);
|
package/lib/feishu/gateway.js
CHANGED
|
@@ -110,14 +110,14 @@ export class FeishuGateway extends Service {
|
|
|
110
110
|
try {
|
|
111
111
|
await this._handleMessageReceive(data)
|
|
112
112
|
} catch (err) {
|
|
113
|
-
this.logger.error?.('[dsh-bridge feishu] error in im.message.receive_v1:', err)
|
|
113
|
+
this.logger.error?.('[dsh-bridge-gateway feishu] error in im.message.receive_v1:', err)
|
|
114
114
|
}
|
|
115
115
|
},
|
|
116
116
|
'card.action.trigger': async (data) => {
|
|
117
117
|
try {
|
|
118
118
|
return await this._handleCardActionTrigger(data)
|
|
119
119
|
} catch (err) {
|
|
120
|
-
this.logger.error?.('[dsh-bridge feishu] error in card.action.trigger:', err)
|
|
120
|
+
this.logger.error?.('[dsh-bridge-gateway feishu] error in card.action.trigger:', err)
|
|
121
121
|
return { toast: { type: 'error', content: '处理失败' } }
|
|
122
122
|
}
|
|
123
123
|
},
|
|
@@ -138,20 +138,20 @@ export class FeishuGateway extends Service {
|
|
|
138
138
|
appName: botRes.bot.app_name,
|
|
139
139
|
avatarUrl: botRes.bot.avatar_url,
|
|
140
140
|
}
|
|
141
|
-
this.logger.info?.(`[dsh-bridge feishu] bot authenticated: ${this.botInfo.appName} (${this.botInfo.openId})`)
|
|
141
|
+
this.logger.info?.(`[dsh-bridge-gateway feishu] bot authenticated: ${this.botInfo.appName} (${this.botInfo.openId})`)
|
|
142
142
|
}
|
|
143
143
|
} catch (authErr) {
|
|
144
|
-
this.logger.warn?.(`[dsh-bridge feishu] fetch bot info error: ${authErr.message}`)
|
|
144
|
+
this.logger.warn?.(`[dsh-bridge-gateway feishu] fetch bot info error: ${authErr.message}`)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
// 5. 启动 WebSocket 长连接
|
|
148
148
|
await this.wsClient.start({ eventDispatcher: this.eventDispatcher })
|
|
149
149
|
this.setStatus('online')
|
|
150
150
|
this._startWatchdog()
|
|
151
|
-
this.logger.info?.('[dsh-bridge feishu] WebSocket connected to Feishu Open Platform')
|
|
151
|
+
this.logger.info?.('[dsh-bridge-gateway feishu] WebSocket connected to Feishu Open Platform')
|
|
152
152
|
return true
|
|
153
153
|
} catch (err) {
|
|
154
|
-
this.logger.error?.('[dsh-bridge feishu] start failed:', err?.message ?? err)
|
|
154
|
+
this.logger.error?.('[dsh-bridge-gateway feishu] start failed:', err?.message ?? err)
|
|
155
155
|
this.setStatus('error', err)
|
|
156
156
|
return false
|
|
157
157
|
} finally {
|
|
@@ -169,7 +169,7 @@ export class FeishuGateway extends Service {
|
|
|
169
169
|
try {
|
|
170
170
|
await this.client.bot.v3.bot.get({}).catch(() => null)
|
|
171
171
|
} catch (err) {
|
|
172
|
-
this.logger?.warn?.('[dsh-bridge feishu] liveness probe warning: %s', err?.message ?? err)
|
|
172
|
+
this.logger?.warn?.('[dsh-bridge-gateway feishu] liveness probe warning: %s', err?.message ?? err)
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
}, 60_000)
|
|
@@ -193,7 +193,7 @@ export class FeishuGateway extends Service {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
} catch (err) {
|
|
196
|
-
this.logger.warn?.('[dsh-bridge feishu] stop error:', err)
|
|
196
|
+
this.logger.warn?.('[dsh-bridge-gateway feishu] stop error:', err)
|
|
197
197
|
} finally {
|
|
198
198
|
this.client = null
|
|
199
199
|
this.wsClient = null
|
|
@@ -295,7 +295,7 @@ export class FeishuGateway extends Service {
|
|
|
295
295
|
}
|
|
296
296
|
return null
|
|
297
297
|
} catch (err) {
|
|
298
|
-
this.logger?.warn?.('[dsh-bridge feishu] downloadMessageResource error: %s', err?.message ?? err)
|
|
298
|
+
this.logger?.warn?.('[dsh-bridge-gateway feishu] downloadMessageResource error: %s', err?.message ?? err)
|
|
299
299
|
return null
|
|
300
300
|
}
|
|
301
301
|
}
|
|
@@ -339,7 +339,7 @@ export class FeishuGateway extends Service {
|
|
|
339
339
|
}
|
|
340
340
|
|
|
341
341
|
try {
|
|
342
|
-
this.logger.debug?.(`[dsh-bridge feishu] sending text to ${receiveId} (${receiveIdType})`)
|
|
342
|
+
this.logger.debug?.(`[dsh-bridge-gateway feishu] sending text to ${receiveId} (${receiveIdType})`)
|
|
343
343
|
const res = await this.client.im.v1.message.create({
|
|
344
344
|
params: { receive_id_type: receiveIdType },
|
|
345
345
|
data: {
|
|
@@ -350,7 +350,7 @@ export class FeishuGateway extends Service {
|
|
|
350
350
|
})
|
|
351
351
|
return res?.data
|
|
352
352
|
} catch (err) {
|
|
353
|
-
this.logger.error?.(`[dsh-bridge feishu] sendText error (${receiveId}, ${receiveIdType}):`, err?.message ?? err)
|
|
353
|
+
this.logger.error?.(`[dsh-bridge-gateway feishu] sendText error (${receiveId}, ${receiveIdType}):`, err?.message ?? err)
|
|
354
354
|
throw err
|
|
355
355
|
}
|
|
356
356
|
}
|
|
@@ -372,7 +372,7 @@ export class FeishuGateway extends Service {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
try {
|
|
375
|
-
this.logger.debug?.(`[dsh-bridge feishu] sending card to ${receiveId} (${receiveIdType})`)
|
|
375
|
+
this.logger.debug?.(`[dsh-bridge-gateway feishu] sending card to ${receiveId} (${receiveIdType})`)
|
|
376
376
|
const res = await this.client.im.v1.message.create({
|
|
377
377
|
params: { receive_id_type: receiveIdType },
|
|
378
378
|
data: {
|
|
@@ -383,7 +383,7 @@ export class FeishuGateway extends Service {
|
|
|
383
383
|
})
|
|
384
384
|
return res?.data
|
|
385
385
|
} catch (err) {
|
|
386
|
-
this.logger.error?.(`[dsh-bridge feishu] sendCard error (${receiveId}, ${receiveIdType}):`, err?.message ?? err)
|
|
386
|
+
this.logger.error?.(`[dsh-bridge-gateway feishu] sendCard error (${receiveId}, ${receiveIdType}):`, err?.message ?? err)
|
|
387
387
|
throw err
|
|
388
388
|
}
|
|
389
389
|
}
|
|
@@ -436,7 +436,7 @@ export class FeishuGateway extends Service {
|
|
|
436
436
|
},
|
|
437
437
|
}
|
|
438
438
|
try {
|
|
439
|
-
this.logger.debug?.(`[dsh-bridge feishu] patching card message ${messageId}`)
|
|
439
|
+
this.logger.debug?.(`[dsh-bridge-gateway feishu] patching card message ${messageId}`)
|
|
440
440
|
const res = await this.client.im.v1.message.patch({
|
|
441
441
|
path: { message_id: messageId },
|
|
442
442
|
data: {
|
|
@@ -445,7 +445,7 @@ export class FeishuGateway extends Service {
|
|
|
445
445
|
})
|
|
446
446
|
return res?.data
|
|
447
447
|
} catch (err) {
|
|
448
|
-
this.logger.error?.(`[dsh-bridge feishu] patchCard error (${messageId}):`, err?.message ?? err)
|
|
448
|
+
this.logger.error?.(`[dsh-bridge-gateway feishu] patchCard error (${messageId}):`, err?.message ?? err)
|
|
449
449
|
throw err
|
|
450
450
|
}
|
|
451
451
|
}
|
|
@@ -484,7 +484,7 @@ export class FeishuGateway extends Service {
|
|
|
484
484
|
})
|
|
485
485
|
return res?.data
|
|
486
486
|
} catch (err) {
|
|
487
|
-
this.logger.warn?.(`[dsh-bridge feishu] sendLocalImage error (${receiveId}, ${filePath}):`, err?.message ?? err)
|
|
487
|
+
this.logger.warn?.(`[dsh-bridge-gateway feishu] sendLocalImage error (${receiveId}, ${filePath}):`, err?.message ?? err)
|
|
488
488
|
return null
|
|
489
489
|
}
|
|
490
490
|
}
|
|
@@ -525,7 +525,7 @@ export class FeishuGateway extends Service {
|
|
|
525
525
|
})
|
|
526
526
|
return res?.data
|
|
527
527
|
} catch (err) {
|
|
528
|
-
this.logger.warn?.(`[dsh-bridge feishu] sendLocalFile error (${receiveId}, ${filePath}):`, err?.message ?? err)
|
|
528
|
+
this.logger.warn?.(`[dsh-bridge-gateway feishu] sendLocalFile error (${receiveId}, ${filePath}):`, err?.message ?? err)
|
|
529
529
|
return null
|
|
530
530
|
}
|
|
531
531
|
}
|
package/lib/feishu/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-bridge Feishu / Lark platform adapter
|
|
1
|
+
// dsh-bridge-gateway Feishu / Lark platform adapter
|
|
2
2
|
// 编排 FeishuGateway(官方 OpenAPI/WSClient 网关)+ FeishuConversationNode(飞书⇄DSH 会话桥)。
|
|
3
3
|
// 作为 Platform 子类,注册进 PlatformManager 统一管理。
|
|
4
4
|
|
|
@@ -46,7 +46,7 @@ export class FeishuService extends Platform {
|
|
|
46
46
|
|
|
47
47
|
if (this.gateway.configured) {
|
|
48
48
|
void this.start().catch((err) => {
|
|
49
|
-
this.logger.error?.('[dsh-bridge feishu] start failed:', err?.message ?? err)
|
|
49
|
+
this.logger.error?.('[dsh-bridge-gateway feishu] start failed:', err?.message ?? err)
|
|
50
50
|
})
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -212,7 +212,7 @@ export class FeishuService extends Platform {
|
|
|
212
212
|
// 凭证配置完成后自动启动网关(前端「保存并连接」自动连接)
|
|
213
213
|
if (this.gateway.configured && (appId !== undefined || appSecret !== undefined)) {
|
|
214
214
|
await this.start().catch((err) => {
|
|
215
|
-
this.logger?.warn?.('[dsh-bridge feishu] auto-start failed: %s', err?.message ?? err)
|
|
215
|
+
this.logger?.warn?.('[dsh-bridge-gateway feishu] auto-start failed: %s', err?.message ?? err)
|
|
216
216
|
})
|
|
217
217
|
}
|
|
218
218
|
return { success: true }
|