Skip to content

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 使用浏览器原生 fetchWebSocket 和 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,且只填写经过审核的精确主机;不要配置 *。反向 代理应允许 AuthorizationContent-TypeSec-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。业务代码不要把访问凭证写入 localStoragesessionStorage、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 返回稳定 coderetryableoperationIdtraceId

10. React/Vue 中的生命周期

每个已登录业务账号只创建一个 XHIMWebClient,放在账号级 Service/Store 中。 组件挂载时订阅事件,卸载时只取消自己的订阅;退出账号时才调用 client.disconnect()

ts
const unsubscribe = client.on('sync', () => {
  messageStore.reloadCurrentConversation()
})

// 组件卸载
unsubscribe()

// 退出业务账号
client.disconnect()

11. 浏览器与安全要求

  • 目标为仍在安全支持期内的 Chrome、Edge、Firefox 和 Safari。
  • 必须具备 fetchWebSocketAbortControllercrypto.subtleTextEncoderBigInt
  • 正式环境只使用 HTTPS/WSS,并配置限制来源的 CSP connect-src
  • 不在查询参数、URL、页面 HTML 或可持久存储中放 XHIM Token。
  • 反向代理、WAF 和 APM 必须对 AuthorizationSec-WebSocket-Protocol 做脱敏。
  • 当前 Web SDK 是在线优先的 Web Lite Runtime;不要宣称具备原生端 SQLCipher 离线数据库能力。

12. 运行仓库中的原生网页示例

bash
cd platforms/web/examples/vanilla
npm install
npm run dev

打开两个无痕窗口,分别填入 alicebob,即可验证双向通讯。示例只用于 Development Server;生产项目请替换为第 5 节的业务凭证提供器。

13. 维护者校验

bash
cd platforms/web
npm ci
npm test
npm audit
npm pack --dry-run

协议绑定由仓库中的 canonical .proto 生成。普通构建会校验协议指纹,防止 服务端协议更新后继续发布过期的 Web 类型。

XHIM 客户端 SDK 与服务端文档