Converse.js 14.0.1 嵌入集成使用指南

把 Converse.js 14.0.1 嵌入自有网站并打通自建 XMPP:三种 view_mode 形态、v14 插件机制与 api 调用面、Prosody 0.12 联调全流程,含六个实测踩坑(ESM/模块覆盖/跨源403/端点发现/WebCrypto/插件白名单)排障实录。

Converse.jsConverse.jsXMPPJavaScript即时通信自托管2026/10/25

Converse.js 14.0.1 嵌入集成使用指南

目标:把 Converse.js 14.0.1 嵌入自己的网站并与自建 XMPP 服务端打通——三种 view_mode 嵌入形态、v14 插件机制与 api.* 调用面、Prosody 0.12 联调全流程,含六个实测踩坑(ESM 加载、模块覆盖、跨源 403、端点发现阻断、WebCrypto 安全上下文、插件白名单)的完整排障实录。已在 Ubuntu 22.04(nginx/1.18.0 + Prosody 0.12.1)+ Chromium 实测通过,消息收发全链路成功。

一、环境信息

本文所有命令、页面与回显均在以下实测环境中执行:

  • 操作系统:Ubuntu 22.04(x86_64)
  • Converse.js:14.0.1(npm 包 dist 自托管,对照组用官方 CDN cdn.conversejs.org/14.0.1/dist/)
  • Web 服务器:nginx/1.18.0(托管 dist 与示例页面)
  • XMPP 服务端:Prosody 0.12.1(apt 安装,自带 mod_websocket/mod_bosh,监听 5280)
  • 演示账号:admin@localhost / q1w2e3r4
  • 验证浏览器:Chromium(无头模式,CDP 驱动采集 DOM/控制台/诊断输出)

💡 说明:嵌入 Converse.js 的本质是「宿主页面引入一个 CSS + 一个 ES Module JS,再调一次 converse.initialize()」。聊天逻辑全部运行在访客浏览器里,你的 Web 服务器只分发静态文件,消息经由 XMPP 服务器路由。

二、前置条件

  1. 一个可连的 XMPP 服务端,且开启 WebSocket 或 BOSH 传输。本文用 Prosody 0.12(搭建的完整流程见《Ubuntu 22.04 安装 Prosody IM 13.0.7 指南》,本文 5.1 给出最小可用配置);
  2. 一个你能改 HTML 的宿主页面,以及 Converse.js 14.0.1 的 dist 产物(自托管见《Ubuntu 22.04 安装 Converse.js 14 指南》,编排化部署见《Converse.js Docker Compose 部署指南》;临时验证可直接用官方 CDN);
  3. HTTPS 或 localhost 访问环境:SCRAM 密码认证依赖浏览器 WebCrypto(crypto.subtle),仅安全上下文可用,否则登录直接失败(踩坑实录见 5.1 步骤 6)。

三、依赖引入

在宿主页面 <head> 引入样式与脚本。v14 产物是原生 ES Module,JS 必须 type="module"(用传统 <script src> 会得到 converse is not defined 且页面空白,排障细节见《Ubuntu 22.04 安装 Converse.js 14 指南》9.1 节):

自托管方式(dist 挂在站点 /dist/ 子路径):

<link rel="stylesheet" href="/dist/converse.min.css">
<script type="module" src="/dist/converse.min.js"></script>

CDN 方式(带版本号锁定):

<link rel="stylesheet" href="https://cdn.conversejs.org/14.0.1/dist/converse.min.css">
<script type="module" src="https://cdn.conversejs.org/14.0.1/dist/converse.min.js"></script>

验证引入成功:浏览器控制台 typeof converse 应返回 "object",页面 DOM 中出现 <converse-root> 元素。实测自托管与 CDN 两种引入最终 DOM 状态一致:converse-root=true, controlbox=true。

四、核心 API 与配置

4.1 三种嵌入形态(view_mode)

view_mode形态典型场景
fullscreen铺满整页,自带背景独立 Web 聊天站(安装篇默认形态)
overlayed页面右下角浮出聊天窗,可拖大拖小给既有网站加客服/社区聊天入口(嵌入首选)
embedded聊天窗嵌入页面指定区域(配合 root 可挂进 shadow DOM)文档页/商品页旁路讨论区

本文示例以 overlayed 为主线(5.1/5.2),embedded 见 5.3,fullscreen 即安装篇的形态。

4.2 面向嵌入的关键初始化参数

参数嵌入场景建议值作用
view_modeoverlayed / embedded见 4.1
singletontrue只允许一个会话窗,嵌入场景防窗口铺屏
allow_url_history_changefalse不让 Converse 用 History API 改宿主页面地址栏
locked_domain你的 XMPP 域锁定登录域,用户只输用户名
discover_connection_methodsfalse(域名不可解析时必须)关闭 XEP-0156 端点发现,见 5.1 踩坑
auto_login视场景配合凭据来源实现免登
show_controlbox_by_default嵌入时默认 false是否默认展开控制框
i18nzh_CN中文界面
whitelisted_plugins自定义插件名列表自定义插件必须列入才加载(v14 实测)

全部 153 个参数见《Converse.js 14 配置参数详解》。

4.3 v14 插件机制与 API 面

Converse.js 的核心功能本身就是一组插件,对外扩展走同一机制。v14 的两个关键事实(均实测确认):

  1. 全局 converse 对象上没有 .api——只有 initialize、plugins、env、keycodes 等少量成员;完整 API 必须在自定义插件内部经 this._converse.api 获取;
  2. 自定义插件必须列入 whitelisted_plugins 才会被加载,否则 initialize() 不会触发。

插件骨架:

converse.plugins.add('myplugin', {
  initialize() {
    const api = this._converse.api;        // v14 唯一 API 入口
    const { $msg, $iq, $pres, Strophe, dayjs } = converse.env;  // Stanza 构造工具
    api.listen.on('connected', () => console.log('connected'));
  }
});
converse.initialize({
  // ...其他配置
  whitelisted_plugins: ['myplugin']
});

常用 API 域(v14,与官方 plugin-development 文档一致):

API作用
api.listen.on(event, handler) / api.listen.off事件订阅(connected/disconnected/message 等)
api.user.jid()当前登录 JID
api.send(stanza)发送 Stanza(用 converse.env.$msg 等构造)
api.chats.open(jid)打开与某 JID 的单聊窗口
api.rooms.open(jid)打开/加入群聊
api.settings.extend/get读取/扩展运行时配置
api.promises.add / api.waitUntil生命周期等待点
api.archive.queryMAM 归档查询
api.trigger触发自定义事件
api.registerMessageInterceptor注册消息拦截器(改写/过滤收发消息)

五、集成示例

5.1 全链路示例:overlayed 浮窗 + Prosody 联调(可独立执行)

目标:在一台 Ubuntu 22.04 机器上,从 Prosody 配置到浏览器收发消息全部跑通。浏览器、nginx、Prosody 同机(故示例用 localhost 满足安全上下文);生产环境把 localhost 换成实际域名并全程 HTTPS。

步骤 1:Prosody 最小可用配置

apt 安装的 Prosody 0.12 自带 mod_websocket/mod_bosh(/usr/lib/prosody/modules/),无需另装。编辑 /etc/prosody/prosody.cfg.lua,在全局段(VirtualHost 之前)的 modules_enabled 表中启用两个传输模块,并补四条全局选项:

modules_enabled = {
  -- ...保留原有条目...
  "websocket";
  "bosh";
}

allow_registration = true        -- 允许带内注册(演示用,生产关掉)
consider_websocket_secure = true -- 反向代理终结 TLS 后仍按安全连接对待
cross_domain_websocket = true    -- 允许跨源 WebSocket(嵌入页面与 XMPP 端口不同源)
cross_domain_bosh = true

⚠️ 踩坑实录 ①(模块覆盖):Ubuntu 包默认在 prosody.cfg.lua 末尾(VirtualHost "localhost" 之后)用 Include "conf.d/*.cfg.lua" 引入附加配置。若在 conf.d 里再写 modules_enabled,会整体覆盖 localhost 域的模块表,登录时服务器回 No stream features to offer(无任何流特性可提供)。正确做法就是上面这样:传输模块写进主配置全局模块表。

重启并注册用户:

systemctl restart prosody
prosodyctl register admin localhost q1w2e3r4

步骤 2:验证 WebSocket 握手

curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  -H "Sec-WebSocket-Protocol: xmpp" \
  http://127.0.0.1:5280/xmpp-websocket

实测回显(返回 101 后连接挂起,即为成功;Ctrl+C 结束):

HTTP/1.1 101 

⚠️ 踩坑实录 ②(跨源 403):cross_domain_websocket 默认只允许同源握手。嵌入页面(如 http://localhost:80)与 WebSocket 端点(http://localhost:5280)端口不同即跨源,不配 cross_domain_websocket = true 时握手直接 403(对应 mod_websocket.lua 的 check_origin 校验)。

步骤 3:宿主页面

保存为 /var/www/conversejs/demo.html(dist 已按安装篇挂在 /dist/):

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>站内嵌入 Converse.js</title>
  <link rel="stylesheet" href="/dist/converse.min.css">
  <script type="module" src="/dist/converse.min.js"></script>
</head>
<body>
  <h1>我的网站页面</h1>
  <p>右下角是嵌入的聊天窗。</p>
  <script type="module">
    converse.initialize({
      websocket_url: "ws://localhost:5280/xmpp-websocket",
      view_mode: "overlayed",
      authentication: "login",
      discover_connection_methods: false,   // 关键:JID 域 localhost 不可解析,关端点发现
      i18n: "zh_CN",
      singleton: true,
      allow_url_history_change: false
    });
  </script>
</body>
</html>

步骤 4:登录

浏览器访问 http://localhost/demo.html,右下角浮窗展开登录框,输入 admin@localhost / q1w2e3r4 登录。

⚠️ 踩坑实录 ③(端点发现阻断登录):discover_connection_methods 默认 true,登录前会先请求 https://localhost/.well-known/host-meta(XEP-0156)自动发现端点。localhost 上没有这个 HTTPS 服务,浏览器直接抛 TypeError: Failed to fetch 并阻断登录流程。置 false 后登录立通。生产环境若域名可解析且正确部署了 host-meta,可保持默认。

步骤 5:确认登录成功

浏览器控制台实测回显:

SASL authentication succeeded.

Prosody 服务端日志同步确认:

Authenticated as admin@localhost

步骤 6:为什么强调 localhost / HTTPS

⚠️ 踩坑实录 ④(WebCrypto 安全上下文):同一页面若改用 http://<局域网IP>/demo.html 访问(非 localhost),提交登录后控制台报 Cannot read properties of undefined (reading 'deriveBits'),SASL 失败——SCRAM 认证的密钥派生走 crypto.subtle,该 API 只在安全上下文(HTTPS、localhost、127.0.0.1)暴露。生产环境必须全站 HTTPS(页面 + wss:// 端点);实验环境用 localhost 访问即满足条件。

5.2 插件示例:事件订阅 + 编程收发消息

在 5.1 页面基础上加自定义插件(务必加 whitelisted_plugins),登录后自动给自己发一条消息并监听回执:

<script type="module">
  converse.plugins.add('e1test', {
    initialize() {
      const api = this._converse.api;
      console.log('DIAG: plugin e1test initialized');

      api.listen.on('connected', () => {
        console.log('EVENT connected, jid=' + api.user.jid());
        // 登录成功后:给自己发一条消息
        const { $msg } = converse.env;
        api.send($msg({ to: 'admin@localhost', type: 'chat' })
          .c('body').t('Hello Converse E1'));
        console.log('message sent via api.send');
        // 打开与自己的会话窗
        api.chats.open('admin@localhost').then(c => console.log('api.chats.open ok, chat id=' + c.get('jid')));
      });

      api.listen.on('message', (m) => {
        console.log('EVENT message: from=' + m.get('from') + ' body=' + m.get('body'));
      });
    }
  });

  converse.initialize({
    websocket_url: "ws://localhost:5280/xmpp-websocket",
    view_mode: "overlayed",
    discover_connection_methods: false,
    i18n: "zh_CN",
    whitelisted_plugins: ['e1test']
  });
</script>

登录 admin@localhost / q1w2e3r4 后,浏览器控制台实测回显(逐行照抄):

DIAG: plugin e1test initialized
EVENT connected, jid=admin@localhost/converse.js-32168925
message sent via api.send
api.chats.open ok, chat id=admin@localhost
EVENT message: from=admin@localhost/converse.js-32168925 body=Hello Converse E1

JID 尾部的 converse.js-32168925 是 Converse.js 自动生成的资源名;给自己发消息立即被服务端路由回来,证明「连接 → 认证 → 发送 → 接收」全链路打通。

⚠️ 踩坑实录 ⑤(插件白名单):v14 实测,去掉 whitelisted_plugins: ['e1test'] 后插件的 initialize() 完全不触发(控制台无 DIAG: plugin e1test initialized)。这是与老版本行为最容易踩的差异之一。

踩坑实录 ⑥(ESM 加载):本页两个 <script> 都必须 type="module";写成传统 script 时 typeof converse === 'undefined'、<converse-root> 不进 DOM,页面空白(与安装篇 9.1 同一坑,嵌入场景同样高发)。

CDN 对照组:同一份插件页面仅把引入换成 CDN 两行,实测同样连通——诊断输出 CDN OK, connected as admin@localhost/converse.js-102415756。

💡 说明:Prosody 0.12 的 MAM 对 Converse 发出的 max_items 归档查询回 bad-request(控制台可见一条告警噪音),不影响实时消息收发;需要历史归档时在服务端启用并按其文档配置 MAM 模块即可。CDN 引入时 curve25519_compiled.wasm 的 MIME 告警同理不影响功能(自托管修复法见安装篇 9.3)。

5.3 embedded 模式:嵌入页面指定区域

embedded 模式把聊天窗嵌入页面流中的指定区域而非浮窗,官方用法要点:置 view_mode: 'embedded',并为聊天窗准备挂载节点(在 Web Component 场景用 root 指向 shadow-root):

converse.initialize({
  websocket_url: "wss://im.example.com/xmpp-websocket",
  view_mode: "embedded",
  discover_connection_methods: false,
  i18n: "zh_CN"
});

宿主侧需自行用 CSS 约束挂载区域的高度(聊天窗不自带页面级布局),适合「文档页右侧讨论栏」这类形态。若只需要右下角入口,overlayed 更省事。

六、注意事项

  1. HTTPS 是生产硬前提:SCRAM/WebCrypto 安全上下文限制(5.1 步骤 6),页面与 WebSocket 端点都要 TLS(wss://);
  2. 六个踩坑汇总:ESM 加载(type="module")、Prosody conf.d 模块覆盖(传输模块写全局段)、跨源 403(cross_domain_websocket=true)、端点发现阻断(discover_connection_methods=false)、WebCrypto 安全上下文、插件白名单(whitelisted_plugins)——均为实测复现,按 5.1/5.2 对应步骤规避;
  3. OMEMO 端到端加密需对端客户端同样支持,且首次使用要处理设备信任;嵌入客服这类「站点对访客」场景一般不开;
  4. 免登集成:把聊天接入已有账号体系时用 authentication: 'prebind'(后端先与 XMPP 建立会话再交前端)或 credentials_url 换发凭据,避免用户二次输入密码;
  5. 嵌入不是隔离:Converse.js 与宿主页面同域运行,宿主页面脚本可读聊天 DOM;不要把嵌入当作多租户隔离手段;
  6. 升级锁定版本号:自托管锁定 dist 版本、CDN 锁定 URL 版本段,跨大版本升级先核对官方 CHANGES(v13→v14 的 ESM 切换即Breaking Change)。

七、参考链接

本专栏其他篇目:《Converse.js 介绍与选型指南》、《Ubuntu 22.04 安装 Converse.js 14 指南》、《Converse.js Docker Compose 部署指南》、《Converse.js 14 配置参数详解》。