@zeyiy/openclaw-channel 0.3.8 → 0.3.10

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 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
- > Forked from [@openim/openclaw-channel](https://github.com/openimsdk/openclaw-channel). Licensed under AGPL-3.0-only.
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
- - Inbound and outbound text/image/file messages
13
- - `openim_send_video` is intentionally sent as a file message
14
- - Quote/reply message parsing for inbound context
15
- - Multi-account login via `channels.openim.accounts.<id>`
16
- - Group trigger policy with optional mention-only mode
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
- - Per-user session isolation (direct chat) / shared session (group chat)
19
- - Agent Portal Bridge — persistent WebSocket connection to agent-portal cloud service
20
- - Interactive setup command: `openclaw openim 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
- - npm package name: `@zeyiy/openclaw-channel`
41
- - plugin id: `openclaw-channel` (used in `plugins.entries` and `plugins.allow`)
42
- - channel id: `openim` (used in `channels.openim`)
43
- - setup command: `openclaw openim setup`
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
- ### Option 1: Interactive setup (recommended)
37
+ ### Interactive setup (recommended)
48
38
 
49
39
  ```bash
50
40
  openclaw openim setup
51
41
  ```
52
42
 
53
- ### Option 2: Edit `~/.openclaw/openclaw.json`
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": "your_token",
63
- "wsAddr": "ws://127.0.0.1:10001",
64
- "apiAddr": "http://127.0.0.1:10002",
65
- "botId": "my-bot-001",
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
- `userID` and `platformID` are optional. If omitted, they are auto-derived from JWT token claims (`UserID` and `PlatformID`).
75
-
76
- `requireMention` is optional and defaults to `true`.
64
+ ### Field reference
77
65
 
78
- `inboundWhitelist` is optional. If omitted or empty, inbound handling keeps existing behavior.
79
- If set, only these users can trigger processing:
80
- - direct messages to the account
81
- - group messages where they `@` the account
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
- `botId` and `portalWsAddr` are optional. When both are set, the plugin establishes a WebSocket connection to the agent-portal cloud service, enabling remote management of agents, files, and models.
80
+ Single-account shorthand (without `accounts` wrapper) is also supported.
84
81
 
85
- Single-account fallback (without `accounts`) is supported.
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
- - `openim_send_text`
101
- - `target`: `user:<id>` or `group:<id>`
102
- - `text`: message text
103
- - `accountId` (optional): select sending account
104
-
105
- - `openim_send_image`
106
- - `target`: `user:<id>` or `group:<id>`
107
- - `image`: local path (`file://` supported) or `http(s)` URL
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 connects to the agent-portal cloud service via WebSocket. The portal can remotely invoke the following methods:
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 the agentId bound to the current bot |
130
- | `models.list` | List available models from config |
131
- | `agents.list` | List all configured agents |
132
- | `agents.create` | Create a new agent with workspace |
133
- | `agents.files.list` | List workspace files for an agent |
134
- | `agents.files.get` | Read a single workspace file |
135
- | `agents.files.set` | Write a file to agent workspace |
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` | List installed skills/plugins status |
138
- | `skills.search` | Search ClawHub for skills (placeholder) |
139
- | `skills.detail` | Get detail for a specific skill |
140
- | `cron.list` | List configured cron jobs |
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
- The connection features automatic reconnect with exponential backoff and heartbeat keepalive.
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](https://github.com/ZeyiY/openclaw-channel/blob/main/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
- > [@openim/openclaw-channel](https://github.com/openimsdk/openclaw-channel) fork 而来,采用 AGPL-3.0-only 许可证。
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
- - `openim_send_video` 按文件消息发送(不使用 OpenIM 视频消息)
14
- - 支持引用消息解析(用于入站上下文)
15
- - 支持多账号并发(`channels.openim.accounts.<id>`)
16
- - 支持群聊仅 @ 触发
17
- - 私聊消息自动标记已读(已读回执)
10
+ - 文本 / 图片 / 文件 / 视频消息收发
11
+ - 引用消息上下文解析
12
+ - 多账号并发登录
13
+ - 群聊 @触发模式(可选)
18
14
  - 每用户独立会话(私聊)/ 同群共享会话(群聊)
19
- - Agent Portal Bridge — 与 agent-portal 云服务保持 WebSocket 长连接,支持远程管理
20
- - 提供交互式配置命令:`openclaw openim setup`
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
- - npm 包名:`@zeyiy/openclaw-channel`
41
- - 插件 id:`openclaw-channel`(用于 `plugins.entries` / `plugins.allow`)
42
- - 渠道 id:`openim`(用于 `channels.openim`)
43
- - 配置命令:`openclaw openim setup`
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
- ### 方式二:手动编辑 `~/.openclaw/openclaw.json`
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": "your_token",
63
- "wsAddr": "ws://127.0.0.1:10001",
64
- "apiAddr": "http://127.0.0.1:10002",
65
- "botId": "my-bot-001",
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
- `userID` 和 `platformID` 为可选项,未填写时会自动从 JWT token 的 `UserID` / `PlatformID` 声明解析。
75
-
76
- `requireMention` 为可选项,默认 `true`。
64
+ ### 字段说明
77
65
 
78
- `inboundWhitelist` 为可选项,不填或为空时保持当前逻辑;填了后仅处理白名单用户触发的消息:
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
- `botId` `portalWsAddr` 为可选项。同时配置后,插件会与 agent-portal 云服务建立 WebSocket 连接,支持远程管理 agent、文件和模型。
80
+ 支持单账号简写(不使用 `accounts` 包装)。
83
81
 
84
- 支持单账号兜底写法(不使用 `accounts`)。
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
- - `openim_send_text`
100
- - `target`: `user:<id>` 或 `group:<id>`
101
- - `text`: 文本内容
102
- - `accountId`(可选):指定发送账号
103
-
104
- - `openim_send_image`
105
- - `target`: `user:<id>` 或 `group:<id>`
106
- - `image`: 本地路径(支持 `file://`)或 `http(s)` URL
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` 后,插件会通过 WebSocket 连接到 agent-portal 云服务。Portal 可远程调用以下方法:
97
+ 配置 `botId` 和 `portalWsAddr` 后,插件与 agent-portal 保持 WebSocket 连接,支持远程管理:
125
98
 
126
99
  | 方法 | 说明 |
127
- |---|---|
128
- | `bot.agent.get` | 获取当前 bot 绑定的 agentId |
129
- | `models.list` | 列出配置中的可用模型 |
130
- | `agents.list` | 列出所有已配置的 agent |
131
- | `agents.create` | 创建新 agent 及工作空间 |
132
- | `agents.files.list` | 列出 agent 工作空间文件 |
133
- | `agents.files.get` | 读取单个工作空间文件 |
134
- | `agents.files.set` | 写入文件到 agent 工作空间 |
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.search` | 搜索 ClawHub 技能(占位) |
138
- | `skills.detail` | 获取特定技能详情 |
139
- | `cron.list` | 列出已配置的定时任务 |
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
- 本项目采用 `AGPL-3.0-only` 许可证。详见 [LICENSE](https://github.com/ZeyiY/openclaw-channel/blob/main/LICENSE)。
155
-
156
- 原始项目由 [openimsdk](https://github.com/openimsdk/openclaw-channel) 开发。
130
+ AGPL-3.0-only,详见 [LICENSE](./LICENSE)。
package/dist/clients.js CHANGED
@@ -3,14 +3,22 @@ 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);
9
10
  state.sdk.off(CbEvents.OnRecvOfflineNewMessages, state.handlers.onRecvOfflineNewMessages);
10
11
  }
11
12
  export function getConnectedClient(accountId) {
12
- if (accountId && clients.has(accountId)) {
13
- return clients.get(accountId) ?? null;
13
+ if (accountId) {
14
+ // Direct match by accountId key
15
+ if (clients.has(accountId))
16
+ return clients.get(accountId);
17
+ // Match by userID (ctx.agentAccountId may be the OpenIM userID, not the config key)
18
+ for (const state of clients.values()) {
19
+ if (state.config.userID === accountId)
20
+ return state;
21
+ }
14
22
  }
15
23
  if (clients.has("default"))
16
24
  return clients.get("default") ?? null;
@@ -21,6 +29,26 @@ export function connectedClientCount() {
21
29
  return clients.size;
22
30
  }
23
31
  export async function startAccountClient(api, config) {
32
+ if (clients.has(config.accountId) || loginInProgress.has(config.accountId)) {
33
+ api.logger?.debug?.(`[openim] account ${config.accountId} already connected or login in progress, skipping`);
34
+ return;
35
+ }
36
+ loginInProgress.add(config.accountId);
37
+ // Diagnostic: test raw WebSocket before SDK login
38
+ const WS = globalThis.WebSocket;
39
+ api.logger?.info?.(`[openim] WebSocket global: ${typeof WS}, constructor: ${WS?.name ?? "N/A"}`);
40
+ try {
41
+ const testWs = new WS(config.wsAddr);
42
+ await new Promise((resolve, reject) => {
43
+ const timer = setTimeout(() => { testWs.close(); reject(new Error("timeout")); }, 5000);
44
+ testWs.onopen = () => { clearTimeout(timer); testWs.close(); resolve(); };
45
+ testWs.onerror = (e) => { clearTimeout(timer); reject(e); };
46
+ });
47
+ api.logger?.info?.(`[openim] raw WebSocket test to ${config.wsAddr}: OK`);
48
+ }
49
+ catch (e) {
50
+ api.logger?.error?.(`[openim] raw WebSocket test to ${config.wsAddr}: FAILED — ${e?.message ?? e}`);
51
+ }
24
52
  const sdk = getSDK();
25
53
  const state = {
26
54
  sdk,
@@ -62,14 +90,16 @@ export async function startAccountClient(api, config) {
62
90
  platformID: config.platformID,
63
91
  });
64
92
  clients.set(config.accountId, state);
93
+ loginInProgress.delete(config.accountId);
65
94
  api.logger?.info?.(`[openim] account ${config.accountId} connected`);
66
- // Start portal bridge after successful OpenIM login
67
- startPortalBridge(api, config);
68
95
  }
69
96
  catch (e) {
70
97
  detachHandlers(state);
98
+ loginInProgress.delete(config.accountId);
71
99
  api.logger?.error?.(`[openim] account ${config.accountId} login failed: ${formatSdkError(e)}`);
72
100
  }
101
+ // Start portal bridge regardless of SDK login result — portal is an independent service
102
+ startPortalBridge(api, config);
73
103
  }
74
104
  export async function stopAccountClient(api, accountId) {
75
105
  // 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
  }
@@ -1,9 +1 @@
1
- declare class NodeFileReaderPolyfill {
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
- "use strict";
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;