@wenbin_wb/dsh-bridge 1.2.4 → 2.0.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.
@@ -0,0 +1,10 @@
1
+ // dsh-bridge 平台抽象层统一导出
2
+ //
3
+ // 提供多平台 IM 接入的基础设施:
4
+ // Platform 平台适配器基类(协议/连接/登录/收发消息)
5
+ // ConversationBridge 平台无关会话桥(白名单/会话/审批/命令/digest)
6
+ // PlatformManager 多平台注册与状态聚合
7
+
8
+ export { Platform } from './base.js'
9
+ export { ConversationBridge, conversationBridgeHelpers, BRIDGE_MARK, textOfAssistantMessage } from './conversation-bridge.js'
10
+ export { PlatformManager } from './manager.js'
@@ -0,0 +1,74 @@
1
+ // dsh-bridge 平台管理器
2
+ //
3
+ // 注册/协调多个 IM 平台适配器(Platform 子类),向主插件与 RPC 层提供:
4
+ // - 统一的多平台状态聚合(getStatus)
5
+ // - 平台实例查找(get)
6
+ // - 统一生命周期(dispose)
7
+ //
8
+ // 主插件注入已构造的平台实例列表,本类只负责登记与聚合,不负责构造平台。
9
+
10
+ export class PlatformManager {
11
+ /**
12
+ * @param {object} opts
13
+ * @param {object} opts.logger 日志器
14
+ */
15
+ constructor({ logger } = {}) {
16
+ this.logger = logger
17
+ this.platforms = new Map() // platformId -> Platform
18
+ }
19
+
20
+ /** 注册一个平台实例。重复注册同 id 时替换旧实例并 dispose 旧的。 */
21
+ register(platform) {
22
+ if (!platform || !platform.id) {
23
+ this.logger?.warn?.('[dsh-bridge] PlatformManager.register: 平台缺少 id,已忽略')
24
+ return platform
25
+ }
26
+ const existing = this.platforms.get(platform.id)
27
+ if (existing && existing !== platform) {
28
+ try {
29
+ const result = existing.dispose()
30
+ if (result instanceof Promise) {
31
+ // 异步 dispose,记录警告(当前架构假定同步清理)
32
+ this.logger?.warn?.(`[dsh-bridge] Platform ${platform.id} dispose is async but not awaited (may leave dangling resources)`)
33
+ }
34
+ } catch { /* 忽略 */ }
35
+ }
36
+ this.platforms.set(platform.id, platform)
37
+ this.logger?.info?.(`[dsh-bridge] platform registered: ${platform.id} (${platform.name ?? ''})`)
38
+ return platform
39
+ }
40
+
41
+ /** 获取平台实例;不存在返回 undefined。 */
42
+ get(platformId) {
43
+ return this.platforms.get(platformId)
44
+ }
45
+
46
+ /** 所有已注册平台 id。 */
47
+ list() {
48
+ return [...this.platforms.values()]
49
+ }
50
+
51
+ /** 聚合所有平台状态:{ [platformId]: status }。 */
52
+ getStatus() {
53
+ const out = {}
54
+ for (const [id, platform] of this.platforms) {
55
+ try {
56
+ out[id] = platform.getStatus()
57
+ } catch (err) {
58
+ // 防御性读取 platform.name(getter 可能抛异常)
59
+ let name = id
60
+ try { name = platform.name ?? id } catch { /* 忽略 */ }
61
+ out[id] = { id, name, status: 'error', error: err?.message ?? String(err) }
62
+ }
63
+ }
64
+ return out
65
+ }
66
+
67
+ /** 释放所有平台。 */
68
+ dispose() {
69
+ for (const platform of this.platforms.values()) {
70
+ try { platform.dispose() } catch { /* 忽略 */ }
71
+ }
72
+ this.platforms.clear()
73
+ }
74
+ }
@@ -1,15 +1,19 @@
1
- // dsh-bridge WeChat coordinator
1
+ // dsh-bridge WeChat platform adapter
2
2
  //
3
3
  // 编排 WechatGateway(iLink 网关,注册为 ctx.wechat 服务)+ WechatConversationNode
4
- // (微信⇄DSH 会话桥)。对外暴露给 bridge-rpc 的接口:status / login / stop / setAllowFrom。
4
+ // (微信⇄DSH 会话桥)。作为 Platform 子类,可注册进 PlatformManager 统一管理。
5
+ //
6
+ // 对外暴露给 bridge-rpc 的接口(与 v1.x 完全兼容):getStatus / login / stop /
7
+ // setAllowFrom / setConfig / unbind / destroy。
5
8
  //
6
9
  // 持久化策略:凭证(token/accountId/baseUrl)与配置(allowFrom/间隔)由主插件通过回调
7
10
  // `onPersist` 保存在 $DSH_HOME/dsh-bridge/config.json。本服务不直接碰文件系统。
8
11
 
9
- import { WechatGateway } from './gateway.js'
12
+ import { Platform } from '../platform/base.js'
13
+ import { WechatGateway, gatewayConstants } from './gateway.js'
10
14
  import { WechatConversationNode } from './node.js'
11
15
 
12
- export class WechatService {
16
+ export class WechatService extends Platform {
13
17
  /**
14
18
  * @param {object} opts
15
19
  * @param {object} opts.ctx Cordis 上下文
@@ -18,17 +22,9 @@ export class WechatService {
18
22
  * @param {(patch: object) => (void|Promise<void>)} opts.onPersist 主插件保存回调
19
23
  */
20
24
  constructor({ ctx, logger, config = {}, onPersist }) {
21
- this.ctx = ctx
22
- this.logger = logger
23
- this.onPersist = onPersist ?? (() => {})
24
-
25
- // 扫码登录的流式状态(RPC 轮询读取)
26
- this.loginState = {
27
- phase: 'idle', // idle | qr | scaned | confirmed | done | error
28
- qrPayload: null, // 待渲染内容:dataURL 图片 或 二维码文本
29
- qrKind: null, // 'img' | 'text'
30
- error: null,
31
- }
25
+ super({ ctx, logger, config, onPersist })
26
+ this.id = 'wechat'
27
+ this.name = '微信'
32
28
 
33
29
  this.gateway = new WechatGateway({
34
30
  ctx,
@@ -51,18 +47,50 @@ export class WechatService {
51
47
  onFirstSender: (senderId) => this.persist({ allowFrom: [senderId] }),
52
48
  onActiveSessionChange: (sessionId) => this.persist({ activeSessionId: sessionId }), // v0.2.1:持久化活动会话
53
49
  })
50
+ this.bridge = this.node
54
51
 
55
52
  if (this.gateway.configured) {
56
- void this.gateway.start().catch((err) => {
53
+ void this.start().catch((err) => {
57
54
  this.logger.error('[dsh-bridge wechat] start failed: %s', err?.message ?? err)
58
55
  })
59
56
  }
60
57
  }
61
58
 
62
- /** 合并展示状态给浏览器 UI。 */
59
+ // ---- Platform 接口 ----
60
+
61
+ get configured() { return this.gateway.configured }
62
+ get accountId() { return this.gateway.accountId }
63
+ get baseUrl() { return this.gateway.baseUrl }
64
+
65
+ get capabilities() {
66
+ return {
67
+ supportsGroup: false, // v0.1 不处理群消息
68
+ supportsMedia: true,
69
+ supportsVoice: true,
70
+ supportsTyping: true,
71
+ maxMessageChars: this.node.config.maxMessageChars ?? gatewayConstants.MAX_MESSAGE_CHARS,
72
+ }
73
+ }
74
+
75
+ /** 消息抽象:委托 gateway(供 PlatformManager/未来多平台统一调用)。 */
76
+ async sendText(peerId, text, opts = {}) {
77
+ return this.gateway.sendText(peerId, text, opts.clientId)
78
+ }
79
+
80
+ async sendTyping(peerId, state) {
81
+ return this.gateway.sendTyping(peerId, state)
82
+ }
83
+
84
+ async sendMedia(peerId, media, opts = {}) {
85
+ return this.gateway.sendMedia({ ...media, to: peerId, ...opts })
86
+ }
87
+
88
+ /** 合并展示状态给浏览器 UI(保持 v1.x 字段结构,新增 id/name/capabilities)。 */
63
89
  getStatus() {
64
90
  const allowFrom = [...(this.node.config.allowFrom ?? [])]
65
91
  return {
92
+ id: this.id,
93
+ name: this.name,
66
94
  status: this.gateway.status,
67
95
  configured: this.gateway.configured,
68
96
  accountId: this.gateway.accountId,
@@ -71,6 +99,7 @@ export class WechatService {
71
99
  sessionId: this.node.activeSessionId,
72
100
  baseUrl: this.gateway.baseUrl,
73
101
  login: { ...this.loginState },
102
+ capabilities: { ...this.capabilities },
74
103
  // 可编辑配置(供 UI 设置面板读取)
75
104
  config: {
76
105
  digestIntervalSec: this.node.config.digestIntervalSec,
@@ -81,6 +110,42 @@ export class WechatService {
81
110
  }
82
111
  }
83
112
 
113
+ /** 可编辑配置(供通用 PlatformPanel 读取)。 */
114
+ getEditableConfig() {
115
+ return {
116
+ digestIntervalSec: this.node.config.digestIntervalSec,
117
+ approvalTimeoutSec: this.node.config.approvalTimeoutSec,
118
+ maxMessageChars: this.node.config.maxMessageChars,
119
+ sendChunkDelayMs: this.node.config.sendChunkDelayMs,
120
+ }
121
+ }
122
+
123
+ /** 启动网关轮询(凭证已配置时生效)。 */
124
+ async start() {
125
+ if (!this.gateway.configured) {
126
+ this.setStatus('idle')
127
+ return
128
+ }
129
+ await this.gateway.start()
130
+ }
131
+
132
+ /** 停止网关轮询(保留凭证与配置)。 */
133
+ async stop() {
134
+ await this.gateway.stop()
135
+ }
136
+
137
+ /** 完整关停(dispose 时调用)。 */
138
+ async dispose() {
139
+ await this.gateway.stop()
140
+ this.gateway.dispose()
141
+ super.dispose() // 清理 node (bridge) + disposers
142
+ }
143
+
144
+ /** 兼容 v1.x 的别名(主插件 ctx.effect 调用)。 */
145
+ async destroy() {
146
+ await this.dispose()
147
+ }
148
+
84
149
  /**
85
150
  * 发起扫码登录(后台执行,状态通过 getStatus/loginState 轮询读取)。
86
151
  * @param {object} [opts]
@@ -138,11 +203,6 @@ export class WechatService {
138
203
  this.loginState = { phase: 'done', qrPayload: null, qrKind: null, error: null }
139
204
  }
140
205
 
141
- /** 停止网关轮询(保留凭证与配置)。 */
142
- async stop() {
143
- await this.gateway.stop()
144
- }
145
-
146
206
  /** 解绑:停止网关并清除凭证(token/accountId),下次重启不再自动重连。 */
147
207
  async unbind() {
148
208
  await this.gateway.stop()
@@ -171,19 +231,4 @@ export class WechatService {
171
231
  sendChunkDelayMs: this.node.config.sendChunkDelayMs,
172
232
  })
173
233
  }
174
-
175
- /** 完整关停(dispose 时调用)。 */
176
- async destroy() {
177
- await this.gateway.stop()
178
- this.gateway.dispose()
179
- this.node.dispose()
180
- }
181
-
182
- async persist(patch) {
183
- try {
184
- await this.onPersist(patch)
185
- } catch (err) {
186
- this.logger.warn('[dsh-bridge wechat] persist failed: %s', err?.message ?? err)
187
- }
188
- }
189
234
  }