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.
Files changed (45) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +17 -3
  3. package/README.zh.md +18 -2
  4. package/assets/apple-touch-icon-180.png +0 -0
  5. package/assets/icon-192.png +0 -0
  6. package/assets/icon-512.png +0 -0
  7. package/assets/icon-maskable-192.png +0 -0
  8. package/assets/icon-maskable-512.png +0 -0
  9. package/client/client.js +302 -41
  10. package/client/index.js +438 -42
  11. package/docs/banner.jpg +0 -0
  12. package/docs/custom-tunnel.md +5 -5
  13. package/docs/platform-abstraction-design.md +1 -1
  14. package/docs/qq-usage.md +6 -6
  15. package/docs/screenshots/remote-auth-login-light.jpg +0 -0
  16. package/docs/screenshots/remote-auth-login.jpg +0 -0
  17. package/docs/wechat-usage.md +1 -1
  18. package/lib/auth/brand-assets.js +131 -0
  19. package/lib/auth/login-template.js +503 -361
  20. package/lib/auth/manager.js +47 -12
  21. package/lib/auth/theme-tokens.js +142 -0
  22. package/lib/bridge-rpc.js +2 -2
  23. package/lib/cloudflared-manager.mjs +1 -1
  24. package/lib/feishu/gateway.js +17 -17
  25. package/lib/feishu/index.js +3 -3
  26. package/lib/feishu/lark-bundled.mjs +288 -469
  27. package/lib/feishu/node.js +7 -7
  28. package/lib/gateway/cert.js +1 -1
  29. package/lib/index.js +210 -80
  30. package/lib/paths.js +43 -14
  31. package/lib/platform/base.js +2 -2
  32. package/lib/platform/conversation-bridge.js +17 -17
  33. package/lib/platform/index.js +1 -1
  34. package/lib/platform/manager.js +4 -4
  35. package/lib/qq/gateway.js +10 -10
  36. package/lib/qq/index.js +11 -11
  37. package/lib/qq/node.js +17 -17
  38. package/lib/telegram/gateway.js +10 -10
  39. package/lib/telegram/index.js +3 -3
  40. package/lib/telegram/node.js +5 -5
  41. package/lib/wechat/gateway.js +6 -6
  42. package/lib/wechat/index.js +4 -4
  43. package/lib/wechat/media.js +1 -1
  44. package/lib/wechat/node.js +13 -13
  45. package/package.json +6 -3
@@ -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
- return { authenticated: false, blacklisted: true }
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 && isLoopback && !isPublicTunnel) {
518
- const host = String(req.headers?.host || '')
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
- return { authenticated: false }
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);
@@ -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
  }
@@ -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 }