主题
Web 从零接入晞晗IM
包名:
@xihansoftware/xhim-web当前等级:Source Alpha。已经具备浏览器真实 HTTP/WSS 通讯、登录续期、断线重连、 增量同步、单聊、文本/自定义消息、历史消息、已读、Presence 和 Typing;正式 npm 发布、主流浏览器/弱网矩阵和附件 UI 完成后才能升级为 Stable。
本包运行在普通浏览器页面中,不需要 Electron、Node-API、C++ 编译环境或 Docker。 网页开发者只安装 npm 包、填写 XHIM Server 地址,并把当前业务用户的凭证获取 函数交给 SDK。
1. Web 与 Electron 的区别
| 使用场景 | 应安装 |
|---|---|
| Chrome、Safari、Edge、Firefox 中打开的网站 | @xihansoftware/xhim-web |
| Electron 桌面应用的 Main/Preload/Renderer | @xihansoftware/xhim-electron |
Web SDK 使用浏览器原生 fetch、WebSocket 和 Web Crypto,通过 Protobuf 与 XHIM Server 通讯。Electron SDK 则通过预编译 Node-API 加载 C++ Core;两个包 不能互换。
2. 服务端团队先提供三项信息
text
Server URL 例如 https://im.example.com
App ID 例如 xhim-demo
测试用户 例如 alice 和 bob服务端需要把网页来源加入 XHIM_ALLOWED_ORIGINS。例如开发和生产同时允许:
dotenv
XHIM_ALLOWED_ORIGINS=localhost:5173,chat.example.com生产环境必须使用 HTTPS/WSS,且只填写经过审核的精确主机;不要配置 *。反向 代理应允许 Authorization、Content-Type 和 Sec-WebSocket-Protocol, 并对后者做日志脱敏。
3. 安装 SDK
bash
npm install @xihansoftware/xhim-web支持 Vite、Webpack、Rspack 等现代 ESM 工具链。SDK 不依赖 DOM 框架,可在 React、Vue、Svelte 或原生 TypeScript 项目中使用。
4. Development 环境:只填地址和用户 ID
XHIM Server 开启 Development Easy Login 时,前端可以直接使用开发凭证提供器:
ts
import {
XHIMWebClient,
createXHIMDevelopmentCredentialProvider
} from '@xihansoftware/xhim-web'
const server = 'http://localhost:8080'
const appId = 'xhim-demo'
const currentUserId = 'alice'
const client = await XHIMWebClient.connect({
server,
appId,
credentialProvider: createXHIMDevelopmentCredentialProvider({
server,
appId,
userId: currentUserId,
deviceId: `web-${currentUserId}`
})
})
client.on('stateChanged', ({ current }) => {
console.log('XHIM 状态:', current)
})
client.on('sync', (batch) => {
console.log('收到增量事件:', batch.events)
})Development Easy Login 在非开发模式会返回 404,因此不会被误用为公网生产 登录接口。
5. 生产环境:接入购买方已有登录态
生产网页不能保存 Admin Key、Endpoint 私钥,也不能自己伪造 User ID。购买方的 业务后端应根据当前网页登录态换取短期 XHIM 访问凭证:
ts
import { XHIMWebClient } from '@xihansoftware/xhim-web'
const client = await XHIMWebClient.connect({
server: 'https://im.example.com',
appId: 'your-app',
credentialProvider: async ({ forceRefresh }) => {
const response = await fetch('/api/im/credential', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ forceRefresh })
})
if (!response.ok) {
throw new Error('当前账号无法登录 IM')
}
const credential = await response.json()
return {
accessToken: credential.accessToken,
expiresAtMs: credential.expiresAtMs
}
}
})SDK 只在内存中持有访问凭证;遇到即将过期或 HTTP 401 时自动再次调用 credentialProvider。业务代码不要把访问凭证写入 localStorage、 sessionStorage、URL 或错误日志。
6. 发送第一条消息
ts
const conversation = await client.getOrCreateDirectConversation('bob')
const sent = await client.sendText(
conversation.conversationId,
'你好,晞晗IM'
)
console.log('服务端消息 ID:', sent.serverMessageId)取消普通查询请求:
ts
const controller = new AbortController()
const historyPromise = client.getMessageHistory(
conversation.conversationId,
{
limit: 50,
signal: controller.signal
}
)
controller.abort()7. 自定义消息
业务消息使用稳定的 contentType + contentVersion + payload + fallbackText 协议。旧网页即使不认识该消息,也能展示 fallbackText:
ts
const payload = new TextEncoder().encode(
JSON.stringify({
orderId: 'order-1001',
amount: 88
})
)
await client.sendCustomMessage(conversation.conversationId, {
contentType: 'com.example.order-card',
contentVersion: 1,
payload,
fallbackText: '[订单卡片] ¥88'
})8. Presence、Typing 与已读
ts
await client.publishPresence('online')
await client.publishTyping(conversation.conversationId, true)
client.on('typing', (event) => {
console.log('输入状态:', event)
})
await client.markConversationRead(
conversation.conversationId,
'42'
)serverSequence 使用十进制字符串,避免 JavaScript number 超过安全整数范围。
9. 公开 API
| API / 事件 | 用途 |
|---|---|
XHIMWebClient.connect(config) | 鉴权、能力协商并建立实时连接 |
client.disconnect() | 停止重连并释放当前客户端 |
client.on(name, listener) | 订阅事件,返回取消订阅函数 |
client.getOrCreateDirectConversation(peerUserId) | 获取或创建单聊 |
client.sendText(conversationId, text) | 发送文本消息 |
client.sendCustomMessage(conversationId, message) | 发送可版本化业务消息 |
client.getMessageHistory(conversationId, options) | 分页查询历史 |
client.markConversationRead(conversationId, sequence) | 上报已读位置 |
client.publishPresence(status, ttlMs) | 发布在线状态租约 |
client.publishTyping(conversationId, isTyping, ttlMs) | 发布输入状态租约 |
client.sync(options) | 主动拉取增量事件 |
stateChanged | 连接状态变化 |
sync | 消息/会话等增量事件批次 |
presence / typing | 临时状态变化 |
sessionRevoked | 当前设备会话被撤销 |
error | 异步连接或实时处理错误 |
所有 Promise API 都接受文档中列出的强类型参数;网络错误以 XHIMWebError 返回稳定 code、retryable、operationId 和 traceId。
10. React/Vue 中的生命周期
每个已登录业务账号只创建一个 XHIMWebClient,放在账号级 Service/Store 中。 组件挂载时订阅事件,卸载时只取消自己的订阅;退出账号时才调用 client.disconnect()。
ts
const unsubscribe = client.on('sync', () => {
messageStore.reloadCurrentConversation()
})
// 组件卸载
unsubscribe()
// 退出业务账号
client.disconnect()11. 浏览器与安全要求
- 目标为仍在安全支持期内的 Chrome、Edge、Firefox 和 Safari。
- 必须具备
fetch、WebSocket、AbortController、crypto.subtle、TextEncoder和BigInt。 - 正式环境只使用 HTTPS/WSS,并配置限制来源的 CSP
connect-src。 - 不在查询参数、URL、页面 HTML 或可持久存储中放 XHIM Token。
- 反向代理、WAF 和 APM 必须对
Authorization与Sec-WebSocket-Protocol做脱敏。 - 当前 Web SDK 是在线优先的 Web Lite Runtime;不要宣称具备原生端 SQLCipher 离线数据库能力。
12. 运行仓库中的原生网页示例
bash
cd platforms/web/examples/vanilla
npm install
npm run dev打开两个无痕窗口,分别填入 alice 和 bob,即可验证双向通讯。示例只用于 Development Server;生产项目请替换为第 5 节的业务凭证提供器。
13. 维护者校验
bash
cd platforms/web
npm ci
npm test
npm audit
npm pack --dry-run协议绑定由仓库中的 canonical .proto 生成。普通构建会校验协议指纹,防止 服务端协议更新后继续发布过期的 Web 类型。