@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.
- package/README.md +3 -2
- package/README.zh-CN.md +3 -2
- package/client/client.js +102 -76
- package/client/index.js +112 -89
- package/lib/bridge-rpc-constants.js +9 -0
- package/lib/bridge-rpc.js +93 -3
- package/lib/index.js +28 -10
- package/lib/platform/base.js +143 -0
- package/lib/platform/conversation-bridge.js +972 -0
- package/lib/platform/index.js +10 -0
- package/lib/platform/manager.js +74 -0
- package/lib/wechat/index.js +82 -37
- package/lib/wechat/node.js +64 -886
- package/package.json +3 -2
|
@@ -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
|
+
}
|
package/lib/wechat/index.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
// dsh-bridge WeChat
|
|
1
|
+
// dsh-bridge WeChat platform adapter
|
|
2
2
|
//
|
|
3
3
|
// 编排 WechatGateway(iLink 网关,注册为 ctx.wechat 服务)+ WechatConversationNode
|
|
4
|
-
// (微信⇄DSH
|
|
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 {
|
|
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
|
-
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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
|
}
|