Web IMLib 快速开始
本页用 5 个步骤完成 Web 端 IMLib 接入:控制台取 AppKey / Token、导入 SDK、初始化、建立连接、收发消息与会话。示例方法名与参数来自冻结的 sdk-bindings.json。
- 控制台获取 AppKey 与 Token
在控制台创建应用并获取 AppKey;用户 Token 由业务服务先注册用户(
user.register)、再调用user.token.issue签发后下发到客户端(两步都需要服务端签名)。客户端只持有 Token,不得内置 AppSecret;Token 泄露时可调用服务端接口吊销。
- 导入 SDK
按「导入 SDK」安装依赖;包内同时提供
rongim.mjs(IMLib)与imkit.mjs(IMKit);两者都不依赖框架。 - 初始化客户端
使用 AppKey(与服务端地址)初始化;初始化幂等:同 appkey 重复调用返回同一实例,不建立连接。
- 建立连接并订阅事件
使用 Token 调用 connect,成功后触发 CONNECTED;离线消息在 hello 后自动补发。
- 收发消息与会话
注册对端用户后发送第一条文本消息、监听 MESSAGES 事件并拉取会话列表。
1. 控制台获取 AppKey 与 Token
| 凭据 | 获取方式 | 使用位置 |
|---|---|---|
| AppKey | 控制台 → 应用 → 基本信息 | 初始化客户端(options.appkey) |
| 用户 Token | 业务服务注册用户(user.register)后调用 user.token.issue 签发(需签名) | connect(token) |
| AppSecret | 控制台 → 应用 → 密钥(仅服务端保存) | 服务端签发 Token / 调用 Open API |
AppSecret 只能保存在业务服务端;客户端产物中不得出现 AppSecret。示例中的 TOKEN 表示服务端签发的用户 Token。
本页及后续示例统一使用 https://im.example.com(服务端缺省端口)。
示例使用占位地址 https://im.example.com;本地联调请替换为你实际启动的服务端地址与端口(客户端与服务端必须指向同一地址)。
本地从零跑通:创建应用 → 注册用户 → 签发 Token
下面三步不依赖任何 SDK/包管理器,只需要服务端已启动(默认 https://im.example.com)与 Node.js 18+。第 1 步返回的 appSecret 填到第 2 步脚本里,第 2 步返回的 token 就是第 4 步 connect(token) 需要的值。
# 1) 创建应用(已有应用可跳过;返回 appkey 与 appSecret)
curl -sS -X POST "https://im.example.com/api/admin/apps" \
-H "X-Admin-Secret: dev-admin" -H "content-type: application/json" \
--data-binary '{"appkey":"demo","name":"Demo"}'PowerShell/cmd 直接传 -d 的 JSON 时引号可能被吞掉(服务端报 “Failed to parse the request body as JSON”)。把请求体写进 app.json 后用 --data-binary "@app.json",即可稳定复现。
// 2) 注册用户并签发 Token(Node.js 18+;把 APP_SECRET 换成上一步返回的 appSecret)
import { createHash, randomBytes } from 'node:crypto';
const BASE = 'https://im.example.com';
const APP_KEY = 'demo';
const APP_SECRET = process.env.IM_APP_SECRET; // 仅服务端持有
function signedHeaders() {
const nonce = randomBytes(16).toString('hex');
const timestamp = Date.now();
const signature = createHash('sha1').update(`${APP_SECRET}${nonce}${timestamp}`).digest('hex');
return { 'App-Key': APP_KEY, Nonce: nonce, Timestamp: String(timestamp), Signature: signature, 'content-type': 'application/json' };
}
async function call(path, body) {
const res = await fetch(`${BASE}/im/v2${path}`, { method: 'POST', headers: signedHeaders(), body: JSON.stringify(body) });
return res.json();
}
console.log(await call('/user/register', { userId: 'alice', name: 'Alice' }));
const issued = await call('/user/token/issue', { userId: 'alice' });
console.log(issued); // { code: 0, data: { token, expireAt } }控制台「API 调试器」可以直接用 AppKey/AppSecret 发签名请求;也可以用 OpenAPI 与 Postman 导出物导入调试器。
2. 导入 SDK
# 方式 A(免构建,推荐):从文档服务器整目录下载 SDK(rongim.mjs 依赖 src/*.mjs,
# 只下载单个 rongim.mjs 会报 ERR_MODULE_NOT_FOUND: ./src/shared.mjs)
curl -sS -O https://im.example.com/sdk/web/scripts/fetch-dist.mjs
node fetch-dist.mjs --base https://im.example.com/sdk/web --out ./vendor/im-web
# 下载 + 校验完成后直接导入(Node / 浏览器 / 打包器均适用)
# node -e "import('./vendor/im-web/rongim.mjs').then(m => console.log(Object.keys(m).length, 'exports'))"
# 方式 B(直接引用托管路径,浏览器内一行完成)
# <script type="module">
# import { createClient, Events, ConversationType } from '/sdk/web/rongim.mjs';
# </script>
# 方式 C(npm 包,当前未发布到 npm registry;发布后可用)
npm install @ni-paas/im-web当前制品是「入口 + src/ 模块」结构:只下载单文件会在 import 时报 ERR_MODULE_NOT_FOUND …/src/shared.mjs。请用 sdk/web/scripts/fetch-dist.mjs 整目录下载并校验,或直接 import 托管路径。
3. 初始化
import { createClient } from '@ni-paas/im-web';
const client = createClient({
appkey: 'YOUR_APP_KEY',
serverUrl: 'https://im.example.com',
});4. 建立连接
import { Events } from '@ni-paas/im-web';
client.addEventListener(Events.CONNECTED, (payload) => console.log('已连接', payload.userId));
client.addEventListener(Events.DISCONNECT, (payload) => console.warn('连接断开', payload.reason));
const res = await client.connect(TOKEN);
if (res.code !== 0) throw new Error(res.msg);5. 收发消息与会话
收发验证需要两个已注册用户:先注册对端(例如 user2)并签发它的 Token,再用对端账号建连接收。示例中的 user2 是“已存在的另一个用户”,不是任意字符串。
client.addEventListener(Events.MESSAGES, ({ messages }) => {
for (const message of messages) console.log(message.conversationType, message.content);
});
const sent = await client.sendTextMessage(ConversationType.PRIVATE, 'user2', '你好,NI PaaS');
if (sent.code !== 0) throw new Error(sent.msg);
const conversations = await client.getConversationList();
await client.clearUnreadCount(ConversationType.PRIVATE, 'user2');单聊发送给不存在的用户时,服务端返回 1004(用户不存在),当前连接可能因此被断开,随后发送调用返回 3001(连接已中断)。
这不是网络故障:注册对端用户(user.register)并重连后即可正常发送。排查时先确认两个 userId 都已注册且属于同一个 appkey。
如果脚本报 Unexpected token '<' 或看到 HTML,通常是服务端地址或端口不对(示例使用占位地址;本地联调要替换为实际端口)。
先用浏览器或 curl 访问服务端根路径确认可达,再检查 serverUrl 是否与启动参数一致。
同一进程内对同一个 appkey 初始化只会得到一个客户端实例(初始化幂等):把两个账号塞进同一个实例会互相覆盖登录态,典型报错是 “cannot send to self”。
实测:同一进程内建两个客户端实例(不同 appkey 或先 destroy 再 create)可以互发;同 origin 的两个浏览器标签共享 deviceId,第二个客户端可能收不到投递。
因此本机验证“收发”请开两个进程,或开两个浏览器 profile(各自独立 localStorage/deviceId),每个进程/Profile 只登录一个用户。