npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

libredesk-sdk-js

v0.2.2

Published

Libredesk 客户侧 SDK

Downloads

91

Readme

libredesk-sdk-js

libredesk-sdk-js 是 Libredesk LiveChat 的客户侧 TypeScript SDK。它封装了客户 Widget 所需的 HTTP API 和 WebSocket:读取公开配置、匿名访客建会话、已验证客户登录、会话与消息管理、附件、CSAT 评价、输入状态、页面访问上报和实时消息接收。

SDK 只面向网站访客/客户的 LiveChat 收件箱,不包含管理员、坐席或其他服务端管理 API。

目录

运行要求

  • Bun 或支持标准 fetch、FormData、Blob 的现代浏览器运行时。
  • 实时功能还需要运行时提供标准 WebSocket。在无全局 WebSocket 的非浏览器运行时,可通过 webSocketFactory 注入适配实现。
  • Libredesk 服务端需要启用一个 LiveChat 收件箱。

安装

# Bun
bun add libredesk-sdk-js

# PNPM
pnpm add libredesk-sdk-js

# NPM
npm install libredesk-sdk-js
import { ApiError, LiveChatClient } from 'libredesk-sdk-js';
import type { ChatMessage, LiveChatSettings } from 'libredesk-sdk-js';

SDK 使用 ESM 和具名导出。浏览器使用时应由应用构建工具打包;不要把收件箱 secret、JWT 签名密钥或后台管理员凭据放入前端产物。

服务端准备

在创建客户端前,确认 Libredesk 中的 LiveChat 收件箱已启用,并准备以下两个公开信息:

| 配置 | 来源与用途 | | --- | --- | | baseURL | Libredesk 服务根地址,例如 https://support.example.com;不包含 /api 或 /widget/ws。 | | inboxID | LiveChat 收件箱的 UUID。SDK 会将它写入 X-Libredesk-Inbox-ID。 |

SDK 调用的是服务端公开的 /api/v1/client/chat/* 路由;服务端会校验收件箱、启用状态和 IP 限制。Client API 已启用跨域访问;受信任域名仅用于服务端托管 Widget 页面的嵌入限制。

已验证客户还需要由宿主业务后端生成 JWT。服务端使用该收件箱的 secret 校验 JWT,前端只能接收已签发的 JWT,绝不能获得 secret。按照官方 LiveChat 配置文档,JWT 使用 HS256 签名,完整 payload 如下:

{
  "external_user_id": "crm-user-42",
  "email": "[email protected]",
  "first_name": "张三",
  "last_name": "",
  "phone_number": "15911111111",
  "phone_number_country_code": "CN",
  "exp": 1780000000,
  "contact_custom_attributes": {
    "plan": "premium",
    "company": "EEDATEK"
  }
}

| 字段名称 | 是否一定需要 | 字段描述 | | --- | --- | --- | | external_user_id | 是 | 宿主系统中稳定且唯一的用户 ID。Libredesk 使用它识别和匹配回访用户。 | | email | 是 | 用户的邮箱地址。 | | first_name | 是 | 用户名字。 | | last_name | 否 | 用户姓氏。 | | phone_number | 否 | 用户电话号码。 | | phone_number_country_code | 否 | 电话号码所属国家或地区的 ISO 3166-1 alpha-2 代码,例如 CN、US。 | | exp | 是 | JWT 的过期时间,使用秒级 Unix 时间戳。建议由后端签发短期 token。 | | contact_custom_attributes | 否 | 对象形式的联系人自定义属性;身份验证成功时写入联系人记录。 |

external_user_id、email、first_name 和 exp 必填。JWT 必须使用该收件箱 secret 以 HS256 签名;secret 只能保存在宿主业务后端。

服务端签发 JWT

Node.js

const jwt = require('jsonwebtoken');

const token = jwt.sign({
  external_user_id: 'your_app_user_123',
  email: '[email protected]',
  first_name: 'John',
  exp: Math.floor(Date.now() / 1000) + 3600,
}, SECRET, { algorithm: 'HS256' });

Go

import (
    "time"
    "github.com/golang-jwt/jwt/v5"
)

claims := jwt.MapClaims{
    "external_user_id": "your_app_user_123",
    "email":            "[email protected]",
    "first_name":       "John",
    "exp":              time.Now().Add(time.Hour).Unix(),
}
token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
    SignedString([]byte(SECRET))

快速开始

下例实现了一个匿名访客的最小闭环:读取配置、创建会话、保存 token、连接实时通道、接收坐席消息、发送后续消息,并在页面卸载时断开连接。

import { ApiError, LiveChatClient } from 'libredesk-sdk-js';

const inboxID = '4d2e63c4-91c1-46e7-b502-725dca2f1b2b';

const client = new LiveChatClient({
  baseURL: 'https://support.example.com',
  inboxID,
  sessionToken: localStorage.getItem('libredesk_session_token'),
  visitorToken: localStorage.getItem('libredesk_visitor_token'),
  onSessionChange(token) {
    if (token) {
      localStorage.setItem('libredesk_session_token', token);
    } else {
      localStorage.removeItem('libredesk_session_token');
    }
  },
  onVisitorTokenChange(token) {
    if (token) {
      localStorage.setItem('libredesk_visitor_token', token);
    } else {
      localStorage.removeItem('libredesk_visitor_token');
    }
  }
});

const settings = await client.getWidgetSettings();
console.log(settings.brand_name);

const created = await client.initConversation({
  message: '你好,我想了解产品价格。',
  formData: {
    name: '张三',
    email: '[email protected]',
    phone_number: '15911111111'
  }
});

const conversationUUID = created.conversation.uuid;

const stopNewMessage = client.on('new_message', (message) => {
  if (message.conversation_uuid === conversationUUID) {
    renderMessage(message);
  }
});

client.connectWebSocket();
await client.sendMessage(conversationUUID, { message: '请问是否有企业版?' });

window.addEventListener('pagehide', () => {
  stopNewMessage();
  client.disconnectWebSocket();
});

function renderMessage(message: { author: { first_name: string }; text_content: string }) {
  console.log(`${message.author.first_name}: ${message.text_content}`);
}

initConversation() 在无 session 时创建匿名访客并返回 session_token。SDK 会同时更新 sessionToken 与 visitorToken,并调用两个持久化回调;之后的 REST 与 WebSocket 请求会自动携带会话信息。

客户端配置与会话持久化

const client = new LiveChatClient({
  baseURL: 'https://support.example.com/',
  inboxID: '4d2e63c4-91c1-46e7-b502-725dca2f1b2b',
  sessionToken: null,
  visitorToken: null,
  timeout: 10_000,
  onSessionChange: (token, user) => {
    // token 为 null 表示会话已被清除;user 可能为 null。
  },
  onVisitorTokenChange: (token) => {
    // token 为 null 表示访客 token 已失效或已被服务端合并。
  },
  webSocketFactory: undefined
});

| 选项 | 必填 | 说明 | | --- | --- | --- | | baseURL | 是 | Libredesk 服务根地址。末尾 / 会被自动移除。HTTP 使用此地址,WebSocket 自动转换为 ws(s)://.../widget/ws。 | | inboxID | 是 | LiveChat 收件箱 UUID。 | | sessionToken | 否 | 已持久化的 session token。 | | visitorToken | 否 | 匿名会话 token;客户完成身份验证后用于将匿名记录合并到联系人。 | | timeout | 否 | REST 超时毫秒数,默认 10000。上传附件固定使用 30000。 | | onSessionChange | 否 | SDK 更新或清空 session token 时调用,适合持久化 token 和用户摘要。 | | onVisitorTokenChange | 否 | SDK 更新或清空 visitor token 时调用。 | | webSocketFactory | 否 | 自定义 WebSocket 创建函数,供测试或非浏览器运行时使用。 |

可通过只读属性读取内存状态:client.sessionToken、client.visitorToken、client.sessionUser。也可在恢复状态或登出时手动设置:

client.setSessionToken(savedSessionToken, savedUser);
client.setVisitorToken(savedVisitorToken);

// 应用自己的登出流程中清除本地与 SDK 状态。
client.setSessionToken(null);
client.setVisitorToken(null);
client.disconnectWebSocket();

初始构造参数不会触发持久化回调。setSessionToken() 会触发 onSessionChange,setVisitorToken() 会触发 onVisitorTokenChange。

身份与会话

匿名访客

匿名访客无需先认证,直接调用 initConversation()。服务端会创建访客、会话和第一条消息;返回的 token 必须被持久化,否则刷新后无法读取之前的会话。

const result = await client.initConversation({
  message: '我需要帮助。',
  formData: {
    name: '王五',
    email: '[email protected]',
    phone_number: '15922222222'
  }
});

console.log(result.conversation.uuid);
console.log(result.messages);

formData 的 key 应匹配收件箱配置的预聊天表单字段;其中的联系人属性和会话属性由服务端根据配置保存。message 必填,最大长度为 10000 个字符。服务端的会话策略可能阻止重复创建会话或向已关闭会话回复,此时会以 ApiError 返回。

已验证客户

宿主应用用户登录后,请向自己的后端请求短期 JWT,再以它交换 Libredesk Widget session:

const jwt = await fetch('/api/customer-service/jwt').then((response) => response.text());
const session = await client.exchangeJWTForSession(jwt);

console.log(session.user.user_id, session.user.is_visitor); // is_visitor 为 false
console.log(client.sessionToken); // 已更新为已验证客户 token

exchangeJWTForSession() 会自动更新 sessionToken 和 sessionUser,并触发 onSessionChange。若当前用户先以匿名访客身份发起过会话,SDK 会保留原有 visitorToken;后续任意需要认证的 Client 请求会自动携带 X-Libredesk-Visitor-Token,服务端完成访客记录合并后会返回清理标记,SDK 随即清空并回调 onVisitorTokenChange(null)。

身份从访客切换为已验证客户后,建议重新建立 WebSocket,使实时连接也使用新 session:

client.disconnectWebSocket();
await client.exchangeJWTForSession(jwt);
client.connectWebSocket();

恢复当前用户

已持久化 session 后可通过 getAuthMe() 验证 token 并恢复用户元数据:

try {
  const user = await client.getAuthMe();
  console.log(user.first_name, user.is_visitor);
} catch (error) {
  // 401 时 SDK 会自动清空当前 session;按错误处理章节清理界面状态。
}

HTTP 用例

SDK 将服务端标准 envelope 响应解包,所有方法直接返回 data 字段,而非 { status, data, message } 外层对象。除 CSAT 外,Widget 请求会自动添加收件箱相关的请求头;有 sessionToken 时自动添加 Authorization: Bearer <token>。

读取公开配置

轻量 launcher 配置适合页面首次渲染聊天入口:

const launcher = await client.getLauncherSettings();

applyLauncher({
  position: launcher.launcher.position,
  color: launcher.colors.primary,
  logoURL: launcher.launcher.logo_url
});

完整 Widget 配置用于构建聊天界面:

const settings = await client.getWidgetSettings();

if (settings.features.file_upload) {
  enableAttachmentButton();
}

if (settings.prechat_form.enabled) {
  renderPrechatForm(settings.prechat_form.fields);
}

LiveChatSettings 包含品牌、语言、颜色、首页背景、功能开关、预聊天表单、会话策略、办公时间、公告/外部链接卡片等公开配置。请以服务端返回值为准,而不是在前端硬编码开关。

创建会话和发送首条消息

const { conversation, messages, session_token, user } = await client.initConversation({
  message: '我的订单还没有发货。',
  formData: {
    email: '[email protected]',
    order_number: 'ORD-1001'
  }
});

renderConversation(conversation, messages);

if (session_token && user) {
  console.log(`已创建访客 ${user.user_id}`);
}

已有有效 session 时,此方法仍会创建新会话,但响应不会新增 session_token。是否允许创建多个会话取决于服务端收件箱的客户/访客策略。

会话列表、详情与已读状态

const conversations = await client.listConversations();
for (const conversation of conversations) {
  console.log(conversation.uuid, conversation.status, conversation.unread_message_count);
}

const detail = await client.getConversation(conversations[0].uuid);
renderConversation(detail.conversation, detail.messages);

await client.updateConversationLastSeen(detail.conversation.uuid);

getConversation() 和 initConversation() 均返回 { conversation, messages, business_hours_id?, working_hours_utc_offset? }。会话摘要包含状态、最后一条消息、未读数和处理人;消息包含 author、text_content、attachments、status 等字段。

在以下时机调用 updateConversationLastSeen():用户进入某个会话、收到并展示坐席消息、或将聊天窗口重新置于前台。该操作只更新服务端已读时间,不返回消息列表。

在已有会话中发送文本

const message = await client.sendMessage(conversationUUID, {
  message: '我补充一下,订单号是 ORD-1001。'
});

appendMessage(message);

请求中的 message 为必填文本。成功后返回服务端已保存的 ChatMessage;不要仅依赖 WebSocket 回显来更新自己的已发送消息。

上传附件

uploadFile() 会以 multipart/form-data 上传一个文件并创建附件消息。浏览器中可直接传递 File,因为 File 是 Blob:

const input = document.querySelector<HTMLInputElement>('#attachment')!;
const file = input.files?.[0];

if (file) {
  const message = await client.uploadFile(conversationUUID, file);
  appendMessage(message);
}

SDK 自动提交 conversation_uuid 和一个名为 files 的文件字段,并将超时提高为 30 秒。是否可上传、允许的 MIME 类型和大小限制由服务端收件箱配置决定;界面应同时检查 settings.features.file_upload 并处理服务器拒绝。

提交 CSAT 评价

当消息元数据提供 CSAT UUID 时,可提交评分与文字反馈:

const accepted = await client.submitCSATResponse('csat-uuid', {
  rating: 5,
  feedback: '响应很及时。'
});

if (accepted) {
  showThankYou();
}

rating 可为 0 至 5;0 表示只提交文字反馈。评分和反馈都可省略,但应至少传递用户实际填写的内容。CSAT 是公开链接式接口,SDK 不会附加收件箱或 session 相关的请求头。

HTTP 方法总览

| 方法 | 服务端路由 | session | 返回值 | | --- | --- | --- | --- | | getLauncherSettings() | GET /api/v1/client/chat/settings/launcher | 不需要 | LauncherSettings | | getWidgetSettings() | GET /api/v1/client/chat/settings | 不需要 | LiveChatSettings | | exchangeJWTForSession(jwt) | POST /api/v1/client/chat/auth/exchange | 不需要 | SessionResponse | | getAuthMe() | GET /api/v1/client/chat/auth/me | 需要 | SessionUser | | initConversation(request) | POST /api/v1/client/chat/conversations/init | 可选 | InitConversationResponse | | listConversations() | GET /api/v1/client/chat/conversations | 需要 | ChatConversation[] | | getConversation(uuid) | GET /api/v1/client/chat/conversations/{uuid} | 需要 | ConversationResponse | | sendMessage(uuid, request) | POST /api/v1/client/chat/conversations/{uuid}/message | 需要 | ChatMessage | | uploadFile(conversationUUID, file) | POST /api/v1/client/media/upload | 需要 | ChatMessage | | updateConversationLastSeen(uuid) | POST /api/v1/client/chat/conversations/{uuid}/update-last-seen | 需要 | boolean | | submitCSATResponse(csatUuid, request) | POST /api/v1/csat/{uuid}/response | 不需要 | boolean |

WebSocket 实时通信

连接、关闭与自动行为

先获取有效 sessionToken,再连接:

client.connectWebSocket();

// 组件卸载、用户主动关闭聊天或应用登出时调用。
client.disconnectWebSocket(1000, 'chat closed');

connectWebSocket() 会连接到 ${baseURL}/widget/ws,并在打开后自动发送 join(包含 session token 与收件箱 UUID)。没有 session token 时会同步抛出错误。重复调用不会创建重复的已打开连接。

SDK 每 10 秒自动发送一次 ping,若连续 30 秒未收到 pong 会关闭连接并重连。非手动关闭时,重连延迟按 1s、2s、4s、8s、16s、20s 递增,并在 20 秒封顶;重连次数没有固定上限。disconnectWebSocket() 会停止心跳和自动重连。

独立使用 WebSocket 客户端

通常应通过 LiveChatClient 使用实时功能,使 HTTP session 与 WebSocket 共享同一份状态。只有当应用自行管理 session 或只需要实时协议时,才直接创建 LiveChatWebSocketClient:

import { LiveChatWebSocketClient } from 'libredesk-sdk-js';

let sessionToken: string | null = readSavedSessionToken();

const websocket = new LiveChatWebSocketClient({
  baseURL: 'https://support.example.com',
  inboxID: '4d2e63c4-91c1-46e7-b502-725dca2f1b2b',
  getSessionToken: () => sessionToken
});

websocket.on('new_message', (message) => appendMessage(message));
websocket.connect();

// 登录、登出或 token 刷新后,更新宿主状态并用新 token 重建连接。
sessionToken = newSessionToken;
websocket.disconnect();
websocket.connect();

该类提供 socket 只读属性、connect()、disconnect()、sendTyping()、sendPageVisit()、sendPing()、on() 与 off()。事件、心跳和重连行为与 LiveChatClient 的代理方法完全一致;直接使用时,调用方负责保证 getSessionToken() 始终返回当前有效 token。

监听事件

on() 返回取消监听函数;也可以使用 off() 移除原始处理函数。建议在界面销毁时取消所有监听。

const unsubscribe = client.on('new_message', (message) => {
  appendMessage(message);
  client.updateConversationLastSeen(message.conversation_uuid).catch(reportError);
});

client.on('typing', ({ conversation_uuid, is_typing }) => {
  updateAgentTyping(conversation_uuid, is_typing);
});

client.on('conversation_update', (patch) => {
  updateConversation(patch);
});

client.on('error', (event) => {
  if (event.kind === 'server') {
    console.error(event.data.message);
  } else {
    console.error('WebSocket 运行时错误', event.event);
  }
});

// 取消监听 new_message 事件
unsubscribe();

| 事件 | 载荷 | 时机 | | --- | --- | --- | | open | 原始 open 事件 | socket 已打开,随后 SDK 自动发送 join。 | | joined | { message?: string } | 服务端确认已加入 inbox。 | | new_message | ChatMessage | 收到新消息。 | | typing | WidgetTypingData | 坐席输入状态变更,包含 conversation_uuid 与 is_typing。 | | conversation_update | Partial<ChatConversation> | 当前会话的状态、处理人等局部字段更新。 | | pong | undefined | 收到心跳响应。 | | raw_message | WidgetMessage | 每条可解析的服务端原始协议消息。 | | error | LiveChatErrorEvent | socket 异常,或服务端发送 error 协议消息。 | | close | 原始 close 事件 | socket 关闭;非手动关闭时会排程重连。 | | reconnecting | { attempt: number; delayMs: number } | 即将按指数退避重连。 | | reconnected | { attempt: number } | 重连 socket 打开后。 |

上报输入状态、页面访问和心跳

const conversationUUID = 'conversation-uuid';

messageInput.addEventListener('input', () => {
  client.sendTyping(conversationUUID, true);
});

messageInput.addEventListener('blur', () => {
  client.sendTyping(conversationUUID, false);
});

client.sendPageVisit(location.href, document.title);

// SDK 内部已维护心跳;只有在有明确业务需要时才手动调用。
client.sendPing();

这些方法仅在 WebSocket 已打开时发送,未连接时会静默忽略。服务端会限制高频 typing、page_visit 和 ping 帧;应按状态变化或页面路由变化触发,不能在每次渲染时无节制调用。页面访问 URL 必须是 http 或 https 地址。

错误处理

服务端返回非 2xx 状态时,SDK 抛出 ApiError。其中包含 HTTP 状态、服务端错误类型、错误数据和原始 Response:

import { ApiError, ERROR_TYPE, LiveChatClient } from 'libredesk-sdk-js';

try {
  await client.sendMessage(conversationUUID, { message: text });
} catch (error) {
  if (error instanceof ApiError) {
    console.error(error.status, error.errorType, error.message, error.data);

    if (error.errorType === ERROR_TYPE.UNAUTHORIZED || error.status === 401) {
      // SDK 已清空失效的 sessionToken;同步清理当前聊天界面即可。
      showSignInOrNewConversation();
    } else if (error.errorType === ERROR_TYPE.RATE_LIMIT) {
      showRetryLater();
    } else if (error.errorType === ERROR_TYPE.INPUT) {
      showValidationMessage(error.message);
    }
  } else {
    // 网络失败、超时、JSON 解析之外的运行时异常。
    reportError(error);
  }
}

当某个使用当前 token 的请求收到 401,SDK 会自动执行 setSessionToken(null) 并触发 onSessionChange(null, null)。若响应带有 X-Libredesk-Clear-Visitor,SDK 也会清空访客 token。应用仍应自行清理会话列表、当前会话、界面缓存和本地用户状态。

可使用的标准错误常量为:

import { ERROR_TYPE } from 'libredesk-sdk-js';

ERROR_TYPE.GENERAL;
ERROR_TYPE.PERMISSION;
ERROR_TYPE.INPUT;
ERROR_TYPE.DATA;
ERROR_TYPE.NETWORK;
ERROR_TYPE.NOT_FOUND;
ERROR_TYPE.CONFLICT;
ERROR_TYPE.UNAUTHORIZED;
ERROR_TYPE.RATE_LIMIT;

默认 REST 超时为 10 秒,附件上传为 30 秒。对用户可重试的网络错误,请由宿主应用提供重试按钮,并避免对创建会话、发送消息等非幂等操作进行无提示自动重放。

数据模型与常量

常用类型可直接导入;公开导出还包括所有配置、会话、消息、办公时间、附件和 WebSocket 协议类型。

import type {
  Attachment,
  ChatConversation,
  ChatMessage,
  ConversationResponse,
  InitConversationRequest,
  LiveChatClientOptions,
  LiveChatSettings,
  SessionResponse,
  SessionUser
} from 'libredesk-sdk-js';

import {
  ATTACHMENT_DISPOSITION,
  BUSINESS_HOURS_DAY,
  CHAT_USER_TYPE,
  MESSAGE_STATUS,
  USER_AVAILABILITY_STATUS,
  WIDGET_MESSAGE_TYPE
} from 'libredesk-sdk-js';

| 常量 | 示例值 | 用途 | | --- | --- | --- | | MESSAGE_STATUS | pending、sent、failed、received | 判断消息持久化状态。 | | CHAT_USER_TYPE | agent、contact、visitor | 判断消息作者或处理人类型。 | | USER_AVAILABILITY_STATUS | online、away、offline | 展示坐席在线状态。 | | ATTACHMENT_DISPOSITION | inline、attachment | 展示或下载附件。 | | BUSINESS_HOURS_DAY | Monday 至 Sunday | 读取服务端营业时间表的 key。 | | WIDGET_MESSAGE_TYPE | join、typing、ping、new_message 等 | 自定义协议适配或使用 raw_message 时使用。 |

时间字段均以服务端字符串返回,应由宿主应用按用户语言和时区格式化。会话状态为服务端字符串,UI 不应假设只有固定的少数枚举值。

安全与集成建议

  • 将 sessionToken 和 visitorToken 视为用户会话凭据;使用 HTTPS,在可信的浏览器存储策略下保存,并在登出时清除。
  • JWT 仅由宿主后端生成,使用收件箱 secret 签名并设置短有效期;不要让浏览器参与签名。
  • 使用 getWidgetSettings() 的 features、预聊天表单和会话策略驱动界面,避免绕过服务端限制。
  • WebSocket 重连后应调用 listConversations(),并在当前会话打开时调用 getConversation(),以补齐断线期间可能遗漏的消息。
  • new_message 是实时增量,getConversation() 是权威快照;对同一 message.uuid 做去重,避免重连同步时重复渲染。
  • 在组件卸载、聊天窗口关闭或身份切换时调用 disconnectWebSocket() 并取消 on() 返回的监听函数,防止重复连接与内存泄漏。
  • 不要把 baseURL 误设为管理后台页面地址或 API 前缀。应传入 Libredesk 服务根地址,例如 https://support.example.com。