@zeyiy/openclaw-channel 0.3.8 → 0.3.9
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 +68 -95
- package/README.zh-CN.md +68 -94
- package/dist/clients.js +25 -2
- package/dist/config.js +0 -37
- package/dist/polyfills.d.ts +1 -9
- package/dist/polyfills.js +12 -1
- package/dist/portal.js +284 -23
- package/dist/setup.js +3 -3
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -2
- package/dist/network.d.ts +0 -9
- package/dist/network.js +0 -61
- package/dist/paths.d.ts +0 -22
- package/dist/paths.js +0 -243
package/README.md
CHANGED
|
@@ -1,56 +1,46 @@
|
|
|
1
1
|
# @zeyiy/openclaw-channel
|
|
2
2
|
|
|
3
|
-
OpenIM channel plugin for OpenClaw Gateway.
|
|
3
|
+
OpenIM channel plugin for [OpenClaw](https://openclaw.ai) Gateway.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Chinese documentation: [README.zh-CN.md](https://github.com/ZeyiY/openclaw-channel/blob/main/README.zh-CN.md)
|
|
5
|
+
Connects OpenClaw agents to the [OpenIM](https://www.openim.io/) messaging platform, enabling AI-powered conversations in direct chats and group chats.
|
|
8
6
|
|
|
9
7
|
## Features
|
|
10
8
|
|
|
11
9
|
- Direct chat and group chat support
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
10
|
+
- Text / image / file / video message send & receive
|
|
11
|
+
- Quote/reply message context parsing
|
|
12
|
+
- Multi-account concurrent login
|
|
13
|
+
- Group trigger with optional mention-only mode
|
|
14
|
+
- Per-user session isolation (direct) / shared session (group)
|
|
17
15
|
- Auto read-receipt for direct messages
|
|
18
|
-
-
|
|
19
|
-
- Agent Portal Bridge — persistent WebSocket
|
|
20
|
-
- Interactive setup
|
|
16
|
+
- Inbound sender whitelist (optional)
|
|
17
|
+
- **Agent Portal Bridge** — persistent WebSocket to agent-portal for remote agent management
|
|
18
|
+
- Interactive setup: `openclaw openim setup`
|
|
21
19
|
|
|
22
20
|
## Installation
|
|
23
21
|
|
|
24
|
-
Install from npm:
|
|
25
|
-
|
|
26
22
|
```bash
|
|
27
23
|
openclaw plugins install @zeyiy/openclaw-channel
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
Or install from local path:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
openclaw plugins install /path/to/openclaw-channel
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Repository: https://github.com/ZeyiY/openclaw-channel
|
|
37
|
-
|
|
38
26
|
## Identity Mapping
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| Item | Value |
|
|
29
|
+
|------|-------|
|
|
30
|
+
| npm package | `@zeyiy/openclaw-channel` |
|
|
31
|
+
| plugin id | `openclaw-channel` |
|
|
32
|
+
| channel id | `openim` |
|
|
33
|
+
| setup command | `openclaw openim setup` |
|
|
44
34
|
|
|
45
35
|
## Configuration
|
|
46
36
|
|
|
47
|
-
###
|
|
37
|
+
### Interactive setup (recommended)
|
|
48
38
|
|
|
49
39
|
```bash
|
|
50
40
|
openclaw openim setup
|
|
51
41
|
```
|
|
52
42
|
|
|
53
|
-
###
|
|
43
|
+
### Manual — edit `~/.openclaw/openclaw.json`
|
|
54
44
|
|
|
55
45
|
```json
|
|
56
46
|
{
|
|
@@ -59,11 +49,11 @@ openclaw openim setup
|
|
|
59
49
|
"accounts": {
|
|
60
50
|
"default": {
|
|
61
51
|
"enabled": true,
|
|
62
|
-
"token": "
|
|
63
|
-
"wsAddr": "
|
|
64
|
-
"apiAddr": "
|
|
65
|
-
"botId": "
|
|
66
|
-
"portalWsAddr": "wss://portal.example.com/ws"
|
|
52
|
+
"token": "your_jwt_token",
|
|
53
|
+
"wsAddr": "wss://your-openim-server/msg_gateway",
|
|
54
|
+
"apiAddr": "https://your-openim-server/api",
|
|
55
|
+
"botId": "your-bot-id",
|
|
56
|
+
"portalWsAddr": "wss://agent-portal.example.com/ws/workspace"
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
}
|
|
@@ -71,87 +61,70 @@ openclaw openim setup
|
|
|
71
61
|
}
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`requireMention` is optional and defaults to `true`.
|
|
64
|
+
### Field reference
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
| Field | Required | Description |
|
|
67
|
+
|-------|----------|-------------|
|
|
68
|
+
| `token` | Yes | OpenIM JWT token |
|
|
69
|
+
| `wsAddr` | Yes | OpenIM WebSocket endpoint |
|
|
70
|
+
| `apiAddr` | Yes | OpenIM REST API endpoint |
|
|
71
|
+
| `userID` | No | Auto-derived from JWT `UserID` claim |
|
|
72
|
+
| `platformID` | No | Auto-derived from JWT `PlatformID` claim |
|
|
73
|
+
| `enabled` | No | Default `true` |
|
|
74
|
+
| `requireMention` | No | Require @mention in groups, default `true` |
|
|
75
|
+
| `inboundWhitelist` | No | Only process messages from listed user IDs |
|
|
76
|
+
| `botId` | No | Bot ID for agent-portal connection |
|
|
77
|
+
| `portalWsAddr` | No | Agent-portal WebSocket endpoint |
|
|
78
|
+
| `historyLimit` | No | Chat history context limit, default `20` |
|
|
82
79
|
|
|
83
|
-
|
|
80
|
+
Single-account shorthand (without `accounts` wrapper) is also supported.
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Environment fallback is supported for the `default` account:
|
|
88
|
-
|
|
89
|
-
- `OPENIM_TOKEN`
|
|
90
|
-
- `OPENIM_WS_ADDR`
|
|
91
|
-
- `OPENIM_API_ADDR`
|
|
92
|
-
|
|
93
|
-
Optional env overrides:
|
|
94
|
-
|
|
95
|
-
- `OPENIM_USER_ID`
|
|
96
|
-
- `OPENIM_PLATFORM_ID`
|
|
82
|
+
> **Note:** Environment variable fallback has been removed. All configuration is via `openclaw.json`.
|
|
97
83
|
|
|
98
84
|
## Agent Tools
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- `accountId` (optional): select sending account
|
|
109
|
-
|
|
110
|
-
- `openim_send_video`
|
|
111
|
-
- `target`: `user:<id>` or `group:<id>`
|
|
112
|
-
- `video`: local path (`file://` supported) or `http(s)` URL
|
|
113
|
-
- behavior: sent as a file message (not OpenIM video message)
|
|
114
|
-
- `name` (optional): override filename for URL input
|
|
115
|
-
- `accountId` (optional): select sending account
|
|
116
|
-
|
|
117
|
-
- `openim_send_file`
|
|
118
|
-
- `target`: `user:<id>` or `group:<id>`
|
|
119
|
-
- `file`: local path (`file://` supported) or `http(s)` URL
|
|
120
|
-
- `name` (optional): override filename for URL input
|
|
121
|
-
- `accountId` (optional): select sending account
|
|
86
|
+
| Tool | Parameters | Description |
|
|
87
|
+
|------|-----------|-------------|
|
|
88
|
+
| `openim_send_text` | `target`, `text`, `accountId?` | Send text message |
|
|
89
|
+
| `openim_send_image` | `target`, `image`, `accountId?` | Send image (local path or URL) |
|
|
90
|
+
| `openim_send_file` | `target`, `file`, `name?`, `accountId?` | Send file (local path or URL) |
|
|
91
|
+
| `openim_send_video` | `target`, `video`, `name?`, `accountId?` | Send video as file message |
|
|
92
|
+
|
|
93
|
+
`target` format: `user:<id>` or `group:<id>`
|
|
122
94
|
|
|
123
95
|
## Agent Portal Bridge
|
|
124
96
|
|
|
125
|
-
When `botId` and `portalWsAddr` are configured, the plugin
|
|
97
|
+
When `botId` and `portalWsAddr` are configured, the plugin maintains a WebSocket connection to agent-portal, enabling remote management:
|
|
126
98
|
|
|
127
99
|
| Method | Description |
|
|
128
|
-
|
|
129
|
-
| `bot.agent.get` | Resolve
|
|
130
|
-
| `models.list` | List available models
|
|
131
|
-
| `agents.list` | List
|
|
132
|
-
| `agents.create` | Create
|
|
133
|
-
| `agents.files.list` | List workspace files
|
|
134
|
-
| `agents.files.get` | Read
|
|
135
|
-
| `agents.files.set` | Write
|
|
100
|
+
|--------|-------------|
|
|
101
|
+
| `bot.agent.get` | Resolve bot's bound agent |
|
|
102
|
+
| `models.list` | List available models |
|
|
103
|
+
| `agents.list` | List configured agents |
|
|
104
|
+
| `agents.create` | Create new agent + workspace |
|
|
105
|
+
| `agents.files.list` | List agent workspace files |
|
|
106
|
+
| `agents.files.get` | Read workspace file |
|
|
107
|
+
| `agents.files.set` | Write workspace file |
|
|
136
108
|
| `tools.catalog` | List available tools |
|
|
137
|
-
| `skills.status` |
|
|
138
|
-
| `skills.
|
|
139
|
-
| `skills.
|
|
140
|
-
| `
|
|
109
|
+
| `skills.status` | Skill/plugin status |
|
|
110
|
+
| `agent.skills.status` | Per-agent skill status with whitelist |
|
|
111
|
+
| `agent.skills.set` | Enable/disable skill for agent |
|
|
112
|
+
| `agent.model.set` | Switch agent model |
|
|
113
|
+
| `skills.search` | Search ClawHub registry |
|
|
114
|
+
| `skills.install` | Install skill from ClawHub or URL |
|
|
115
|
+
| `skills.set` | Enable/disable skill globally |
|
|
116
|
+
| `cron.list` | List cron jobs |
|
|
141
117
|
|
|
142
|
-
|
|
118
|
+
Features auto-reconnect with exponential backoff and heartbeat keepalive.
|
|
143
119
|
|
|
144
120
|
## Development
|
|
145
121
|
|
|
146
122
|
```bash
|
|
123
|
+
pnpm install
|
|
147
124
|
pnpm run build
|
|
148
|
-
pnpm run test:connect
|
|
125
|
+
pnpm run test:connect # configure .env first
|
|
149
126
|
```
|
|
150
127
|
|
|
151
|
-
For `test:connect`, configure `.env` first (see `.env.example`).
|
|
152
|
-
|
|
153
128
|
## License
|
|
154
129
|
|
|
155
|
-
AGPL-3.0-only. See [LICENSE](
|
|
156
|
-
|
|
157
|
-
Originally developed by [openimsdk](https://github.com/openimsdk/openclaw-channel).
|
|
130
|
+
AGPL-3.0-only. See [LICENSE](./LICENSE).
|
package/README.zh-CN.md
CHANGED
|
@@ -1,56 +1,46 @@
|
|
|
1
1
|
# @zeyiy/openclaw-channel
|
|
2
2
|
|
|
3
|
-
OpenClaw Gateway 的 OpenIM 渠道插件。
|
|
3
|
+
[OpenClaw](https://openclaw.ai) Gateway 的 OpenIM 渠道插件。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
English documentation: [README.md](https://github.com/ZeyiY/openclaw-channel/blob/main/README.md)
|
|
5
|
+
将 OpenClaw 智能体接入 [OpenIM](https://www.openim.io/) 即时通讯平台,支持私聊和群聊中的 AI 对话。
|
|
8
6
|
|
|
9
7
|
## 功能
|
|
10
8
|
|
|
11
9
|
- 支持私聊与群聊
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
- 支持群聊仅 @ 触发
|
|
17
|
-
- 私聊消息自动标记已读(已读回执)
|
|
10
|
+
- 文本 / 图片 / 文件 / 视频消息收发
|
|
11
|
+
- 引用消息上下文解析
|
|
12
|
+
- 多账号并发登录
|
|
13
|
+
- 群聊 @触发模式(可选)
|
|
18
14
|
- 每用户独立会话(私聊)/ 同群共享会话(群聊)
|
|
19
|
-
-
|
|
20
|
-
-
|
|
15
|
+
- 私聊自动已读回执
|
|
16
|
+
- 入站发送者白名单(可选)
|
|
17
|
+
- **Agent Portal Bridge** — 与 agent-portal 云服务保持 WebSocket 长连接,支持远程管理
|
|
18
|
+
- 交互式配置命令:`openclaw openim setup`
|
|
21
19
|
|
|
22
20
|
## 安装
|
|
23
21
|
|
|
24
|
-
从 npm 安装:
|
|
25
|
-
|
|
26
22
|
```bash
|
|
27
23
|
openclaw plugins install @zeyiy/openclaw-channel
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
本地路径安装:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
openclaw plugins install /path/to/openclaw-channel
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
仓库地址:https://github.com/ZeyiY/openclaw-channel
|
|
37
|
-
|
|
38
26
|
## 标识说明
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
28
|
+
| 项目 | 值 |
|
|
29
|
+
|------|-----|
|
|
30
|
+
| npm 包名 | `@zeyiy/openclaw-channel` |
|
|
31
|
+
| 插件 ID | `openclaw-channel` |
|
|
32
|
+
| 渠道 ID | `openim` |
|
|
33
|
+
| 配置命令 | `openclaw openim setup` |
|
|
44
34
|
|
|
45
35
|
## 配置
|
|
46
36
|
|
|
47
|
-
###
|
|
37
|
+
### 交互式配置(推荐)
|
|
48
38
|
|
|
49
39
|
```bash
|
|
50
40
|
openclaw openim setup
|
|
51
41
|
```
|
|
52
42
|
|
|
53
|
-
###
|
|
43
|
+
### 手动编辑 `~/.openclaw/openclaw.json`
|
|
54
44
|
|
|
55
45
|
```json
|
|
56
46
|
{
|
|
@@ -59,11 +49,11 @@ openclaw openim setup
|
|
|
59
49
|
"accounts": {
|
|
60
50
|
"default": {
|
|
61
51
|
"enabled": true,
|
|
62
|
-
"token": "
|
|
63
|
-
"wsAddr": "
|
|
64
|
-
"apiAddr": "
|
|
65
|
-
"botId": "
|
|
66
|
-
"portalWsAddr": "wss://portal.example.com/ws"
|
|
52
|
+
"token": "your_jwt_token",
|
|
53
|
+
"wsAddr": "wss://your-openim-server/msg_gateway",
|
|
54
|
+
"apiAddr": "https://your-openim-server/api",
|
|
55
|
+
"botId": "your-bot-id",
|
|
56
|
+
"portalWsAddr": "wss://agent-portal.example.com/ws/workspace"
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
}
|
|
@@ -71,86 +61,70 @@ openclaw openim setup
|
|
|
71
61
|
}
|
|
72
62
|
```
|
|
73
63
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`requireMention` 为可选项,默认 `true`。
|
|
64
|
+
### 字段说明
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
66
|
+
| 字段 | 必填 | 说明 |
|
|
67
|
+
|------|------|------|
|
|
68
|
+
| `token` | 是 | OpenIM JWT token |
|
|
69
|
+
| `wsAddr` | 是 | OpenIM WebSocket 端点 |
|
|
70
|
+
| `apiAddr` | 是 | OpenIM REST API 端点 |
|
|
71
|
+
| `userID` | 否 | 自动从 JWT `UserID` 声明解析 |
|
|
72
|
+
| `platformID` | 否 | 自动从 JWT `PlatformID` 声明解析 |
|
|
73
|
+
| `enabled` | 否 | 默认 `true` |
|
|
74
|
+
| `requireMention` | 否 | 群聊需 @触发,默认 `true` |
|
|
75
|
+
| `inboundWhitelist` | 否 | 仅处理指定用户 ID 的消息 |
|
|
76
|
+
| `botId` | 否 | 用于 agent-portal 连接的 Bot ID |
|
|
77
|
+
| `portalWsAddr` | 否 | Agent-portal WebSocket 端点 |
|
|
78
|
+
| `historyLimit` | 否 | 聊天历史上下文条数,默认 `20` |
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
支持单账号简写(不使用 `accounts` 包装)。
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`default` 账号支持环境变量兜底:
|
|
87
|
-
|
|
88
|
-
- `OPENIM_TOKEN`
|
|
89
|
-
- `OPENIM_WS_ADDR`
|
|
90
|
-
- `OPENIM_API_ADDR`
|
|
91
|
-
|
|
92
|
-
可选环境变量覆盖项:
|
|
93
|
-
|
|
94
|
-
- `OPENIM_USER_ID`
|
|
95
|
-
- `OPENIM_PLATFORM_ID`
|
|
82
|
+
> **注意:** 已移除环境变量配置方式,所有配置通过 `openclaw.json` 完成。
|
|
96
83
|
|
|
97
84
|
## Agent 工具
|
|
98
85
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `accountId`(可选):指定发送账号
|
|
108
|
-
|
|
109
|
-
- `openim_send_video`
|
|
110
|
-
- `target`: `user:<id>` 或 `group:<id>`
|
|
111
|
-
- `video`: 本地路径(支持 `file://`)或 `http(s)` URL
|
|
112
|
-
- 行为:按文件消息发送(不是视频消息)
|
|
113
|
-
- `name`(可选):URL 输入时覆盖文件名
|
|
114
|
-
- `accountId`(可选):指定发送账号
|
|
115
|
-
|
|
116
|
-
- `openim_send_file`
|
|
117
|
-
- `target`: `user:<id>` 或 `group:<id>`
|
|
118
|
-
- `file`: 本地路径(支持 `file://`)或 `http(s)` URL
|
|
119
|
-
- `name`(可选):URL 输入时覆盖文件名
|
|
120
|
-
- `accountId`(可选):指定发送账号
|
|
86
|
+
| 工具 | 参数 | 说明 |
|
|
87
|
+
|------|------|------|
|
|
88
|
+
| `openim_send_text` | `target`, `text`, `accountId?` | 发送文本消息 |
|
|
89
|
+
| `openim_send_image` | `target`, `image`, `accountId?` | 发送图片(本地路径或 URL) |
|
|
90
|
+
| `openim_send_file` | `target`, `file`, `name?`, `accountId?` | 发送文件(本地路径或 URL) |
|
|
91
|
+
| `openim_send_video` | `target`, `video`, `name?`, `accountId?` | 发送视频(按文件消息发送) |
|
|
92
|
+
|
|
93
|
+
`target` 格式:`user:<id>` 或 `group:<id>`
|
|
121
94
|
|
|
122
95
|
## Agent Portal Bridge
|
|
123
96
|
|
|
124
|
-
配置 `botId` 和 `portalWsAddr`
|
|
97
|
+
配置 `botId` 和 `portalWsAddr` 后,插件与 agent-portal 保持 WebSocket 连接,支持远程管理:
|
|
125
98
|
|
|
126
99
|
| 方法 | 说明 |
|
|
127
|
-
|
|
128
|
-
| `bot.agent.get` |
|
|
129
|
-
| `models.list` |
|
|
130
|
-
| `agents.list` |
|
|
131
|
-
| `agents.create` |
|
|
132
|
-
| `agents.files.list` |
|
|
133
|
-
| `agents.files.get` |
|
|
134
|
-
| `agents.files.set` |
|
|
100
|
+
|------|------|
|
|
101
|
+
| `bot.agent.get` | 获取 bot 绑定的 agent |
|
|
102
|
+
| `models.list` | 列出可用模型 |
|
|
103
|
+
| `agents.list` | 列出已配置的 agent |
|
|
104
|
+
| `agents.create` | 创建 agent + 工作空间 |
|
|
105
|
+
| `agents.files.list` | 列出工作空间文件 |
|
|
106
|
+
| `agents.files.get` | 读取工作空间文件 |
|
|
107
|
+
| `agents.files.set` | 写入工作空间文件 |
|
|
135
108
|
| `tools.catalog` | 列出可用工具 |
|
|
136
|
-
| `skills.status` |
|
|
137
|
-
| `skills.
|
|
138
|
-
| `skills.
|
|
139
|
-
| `
|
|
109
|
+
| `skills.status` | 技能/插件状态 |
|
|
110
|
+
| `agent.skills.status` | Agent 级技能状态(含白名单) |
|
|
111
|
+
| `agent.skills.set` | 启用/禁用 agent 技能 |
|
|
112
|
+
| `agent.model.set` | 切换 agent 模型 |
|
|
113
|
+
| `skills.search` | 搜索 ClawHub 技能 |
|
|
114
|
+
| `skills.install` | 从 ClawHub 或 URL 安装技能 |
|
|
115
|
+
| `skills.set` | 全局启用/禁用技能 |
|
|
116
|
+
| `cron.list` | 列出定时任务 |
|
|
140
117
|
|
|
141
|
-
|
|
118
|
+
支持指数退避自动重连和心跳保活。
|
|
142
119
|
|
|
143
120
|
## 开发
|
|
144
121
|
|
|
145
122
|
```bash
|
|
123
|
+
pnpm install
|
|
146
124
|
pnpm run build
|
|
147
|
-
pnpm run test:connect
|
|
125
|
+
pnpm run test:connect # 先配置 .env
|
|
148
126
|
```
|
|
149
127
|
|
|
150
|
-
运行 `test:connect` 前请先配置 `.env`(参考 `.env.example`)。
|
|
151
|
-
|
|
152
128
|
## 许可证
|
|
153
129
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
原始项目由 [openimsdk](https://github.com/openimsdk/openclaw-channel) 开发。
|
|
130
|
+
AGPL-3.0-only,详见 [LICENSE](./LICENSE)。
|
package/dist/clients.js
CHANGED
|
@@ -3,6 +3,7 @@ import { processInboundMessage } from "./inbound";
|
|
|
3
3
|
import { startPortalBridge, stopPortalBridge, stopAllPortalBridges } from "./portal";
|
|
4
4
|
import { formatSdkError } from "./utils";
|
|
5
5
|
const clients = new Map();
|
|
6
|
+
const loginInProgress = new Set();
|
|
6
7
|
function detachHandlers(state) {
|
|
7
8
|
state.sdk.off(CbEvents.OnRecvNewMessage, state.handlers.onRecvNewMessage);
|
|
8
9
|
state.sdk.off(CbEvents.OnRecvNewMessages, state.handlers.onRecvNewMessages);
|
|
@@ -21,6 +22,26 @@ export function connectedClientCount() {
|
|
|
21
22
|
return clients.size;
|
|
22
23
|
}
|
|
23
24
|
export async function startAccountClient(api, config) {
|
|
25
|
+
if (clients.has(config.accountId) || loginInProgress.has(config.accountId)) {
|
|
26
|
+
api.logger?.debug?.(`[openim] account ${config.accountId} already connected or login in progress, skipping`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
loginInProgress.add(config.accountId);
|
|
30
|
+
// Diagnostic: test raw WebSocket before SDK login
|
|
31
|
+
const WS = globalThis.WebSocket;
|
|
32
|
+
api.logger?.info?.(`[openim] WebSocket global: ${typeof WS}, constructor: ${WS?.name ?? "N/A"}`);
|
|
33
|
+
try {
|
|
34
|
+
const testWs = new WS(config.wsAddr);
|
|
35
|
+
await new Promise((resolve, reject) => {
|
|
36
|
+
const timer = setTimeout(() => { testWs.close(); reject(new Error("timeout")); }, 5000);
|
|
37
|
+
testWs.onopen = () => { clearTimeout(timer); testWs.close(); resolve(); };
|
|
38
|
+
testWs.onerror = (e) => { clearTimeout(timer); reject(e); };
|
|
39
|
+
});
|
|
40
|
+
api.logger?.info?.(`[openim] raw WebSocket test to ${config.wsAddr}: OK`);
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
api.logger?.error?.(`[openim] raw WebSocket test to ${config.wsAddr}: FAILED — ${e?.message ?? e}`);
|
|
44
|
+
}
|
|
24
45
|
const sdk = getSDK();
|
|
25
46
|
const state = {
|
|
26
47
|
sdk,
|
|
@@ -62,14 +83,16 @@ export async function startAccountClient(api, config) {
|
|
|
62
83
|
platformID: config.platformID,
|
|
63
84
|
});
|
|
64
85
|
clients.set(config.accountId, state);
|
|
86
|
+
loginInProgress.delete(config.accountId);
|
|
65
87
|
api.logger?.info?.(`[openim] account ${config.accountId} connected`);
|
|
66
|
-
// Start portal bridge after successful OpenIM login
|
|
67
|
-
startPortalBridge(api, config);
|
|
68
88
|
}
|
|
69
89
|
catch (e) {
|
|
70
90
|
detachHandlers(state);
|
|
91
|
+
loginInProgress.delete(config.accountId);
|
|
71
92
|
api.logger?.error?.(`[openim] account ${config.accountId} login failed: ${formatSdkError(e)}`);
|
|
72
93
|
}
|
|
94
|
+
// Start portal bridge regardless of SDK login result — portal is an independent service
|
|
95
|
+
startPortalBridge(api, config);
|
|
73
96
|
}
|
|
74
97
|
export async function stopAccountClient(api, accountId) {
|
|
75
98
|
// Stop portal bridge before disconnecting OpenIM
|
package/dist/config.js
CHANGED
|
@@ -41,32 +41,6 @@ function extractAccountHintsFromToken(token) {
|
|
|
41
41
|
...(Number.isFinite(platformID) ? { platformID } : {}),
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function envDefaultAccount() {
|
|
45
|
-
const token = String(process.env.OPENIM_TOKEN ?? "").trim();
|
|
46
|
-
const wsAddr = String(process.env.OPENIM_WS_ADDR ?? "").trim();
|
|
47
|
-
const apiAddr = String(process.env.OPENIM_API_ADDR ?? "").trim();
|
|
48
|
-
if (!token || !wsAddr || !apiAddr)
|
|
49
|
-
return null;
|
|
50
|
-
const hints = extractAccountHintsFromToken(token);
|
|
51
|
-
const userID = String(process.env.OPENIM_USER_ID ?? hints.userID ?? "").trim();
|
|
52
|
-
const platformID = toFiniteNumber(process.env.OPENIM_PLATFORM_ID ?? hints.platformID, 5);
|
|
53
|
-
if (!userID)
|
|
54
|
-
return null;
|
|
55
|
-
const botId = String(process.env.OPENIM_BOT_ID ?? "").trim() || undefined;
|
|
56
|
-
const portalWsAddr = String(process.env.OPENIM_PORTAL_WS_ADDR ?? "").trim() || undefined;
|
|
57
|
-
return {
|
|
58
|
-
userID,
|
|
59
|
-
token,
|
|
60
|
-
wsAddr,
|
|
61
|
-
apiAddr,
|
|
62
|
-
platformID,
|
|
63
|
-
enabled: true,
|
|
64
|
-
requireMention: true,
|
|
65
|
-
historyLimit: 20,
|
|
66
|
-
botId,
|
|
67
|
-
portalWsAddr,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
44
|
function normalizeInboundWhitelist(raw) {
|
|
71
45
|
const values = Array.isArray(raw)
|
|
72
46
|
? raw
|
|
@@ -120,8 +94,6 @@ export function listAccountIds(apiOrCfg) {
|
|
|
120
94
|
}
|
|
121
95
|
if (ch?.userID || ch?.token || ch?.wsAddr || ch?.apiAddr)
|
|
122
96
|
return ["default"];
|
|
123
|
-
if (envDefaultAccount())
|
|
124
|
-
return ["default"];
|
|
125
97
|
return [];
|
|
126
98
|
}
|
|
127
99
|
export function getOpenIMAccountConfig(apiOrCfg, accountId = "default") {
|
|
@@ -136,10 +108,6 @@ export function getOpenIMAccountConfig(apiOrCfg, accountId = "default") {
|
|
|
136
108
|
if (normalized)
|
|
137
109
|
return normalized;
|
|
138
110
|
}
|
|
139
|
-
const env = envDefaultAccount();
|
|
140
|
-
if (env) {
|
|
141
|
-
return normalizeAccount("default", env);
|
|
142
|
-
}
|
|
143
111
|
}
|
|
144
112
|
return null;
|
|
145
113
|
}
|
|
@@ -162,10 +130,5 @@ export function resolveAccountConfig(apiOrCfg, accountId) {
|
|
|
162
130
|
if (id === "default" && (ch?.userID || ch?.token || ch?.wsAddr || ch?.apiAddr)) {
|
|
163
131
|
return { accountId: id, ...ch };
|
|
164
132
|
}
|
|
165
|
-
if (id === "default") {
|
|
166
|
-
const env = envDefaultAccount();
|
|
167
|
-
if (env)
|
|
168
|
-
return { accountId: id, ...env };
|
|
169
|
-
}
|
|
170
133
|
return { accountId: id };
|
|
171
134
|
}
|
package/dist/polyfills.d.ts
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
result: ArrayBuffer | null;
|
|
3
|
-
error: Error | null;
|
|
4
|
-
onload: ((ev: {
|
|
5
|
-
target: NodeFileReaderPolyfill;
|
|
6
|
-
}) => void) | null;
|
|
7
|
-
onerror: ((err: unknown) => void) | null;
|
|
8
|
-
readAsArrayBuffer(blob: Blob): void;
|
|
9
|
-
}
|
|
1
|
+
export {};
|
package/dist/polyfills.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import WsWebSocket from "ws";
|
|
2
|
+
// Force ws library over native WebSocket — OpenIM WASM SDK is incompatible
|
|
3
|
+
// with Node.js native WebSocket (undici-based), causing instant "network error".
|
|
4
|
+
globalThis.WebSocket = WsWebSocket;
|
|
5
|
+
// ws library sends Buffer for binary messages; OpenIM SDK calls .arrayBuffer() expecting a Blob.
|
|
6
|
+
// Polyfill Buffer.prototype.arrayBuffer so it returns Promise<ArrayBuffer>.
|
|
7
|
+
if (typeof Buffer !== "undefined" && !Buffer.prototype.arrayBuffer) {
|
|
8
|
+
Buffer.prototype.arrayBuffer = function () {
|
|
9
|
+
const ab = this.buffer.slice(this.byteOffset, this.byteOffset + this.byteLength);
|
|
10
|
+
return Promise.resolve(ab);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
2
13
|
class NodeFileReaderPolyfill {
|
|
3
14
|
result = null;
|
|
4
15
|
error = null;
|