开放平台 · 网页客服接入
npm · @sochatlive/cs-widget v0.1.1纯 TypeScript + Shadow DOM 构建,gzipped ~12 KB,零运行时依赖。CDN 与 npm 双形态接入;本页所有示例与下方 Live Demo 共用同一份构建产物。
data-app-key + visitor JWT 支持: zh-CN · en-US 传输: REST + WebSocket(自动降级 8s 长轮询) <script async src="https://chat.sochatlive.com/cs/widget.js" data-app-key="sk_xxxxxxxxxxxxxxxxx" data-api-base="https://chat.sochatlive.com"></script>
pnpm add @sochatlive/cs-widget
import SoChatCS from '@sochatlive/cs-widget'
SoChatCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com',
}) 本页已加载真实的 https://chat.sochatlive.com/cs/widget.js 构建产物(data-mock="true",不连接后端)。点击右下角悬浮按钮立即试用,下方按钮覆盖全部命令式 API 与事件订阅。
提示:右下角悬浮按钮 + 聊天面板就是 widget 实际形态。本页 widget 与 /cs-demo 页面共用同一份构建产物,只是这里直接内嵌、那里作为单独发布的 demo 页。
你只需要一个站点 appKey。它由平台运营在 admin 后台创建站点时下发;本页面所有示例都给了占位值,可以先用 data-mock="true" 跑通前端再申请正式参数。
<script async src="https://chat.sochatlive.com/cs/widget.js" data-app-key="sk_xxxxxxxxxxxxxxxxx" data-api-base="https://chat.sochatlive.com" data-locale="zh-CN" data-theme="auto" data-position="br"></script>
<script async src="https://chat.sochatlive.com/cs/widget.js?appKey=sk_xxxxxxxxxxxxxxxxx&apiBase=https%3A%2F%2Fchat.sochatlive.com&locale=en-US"></script>
<script async src="https://chat.sochatlive.com/cs/widget.js" data-defer-init></script>
<script>
// queue-style:异步加载期间的命令会被排队
window.SoChatCS = window.SoChatCS || function () {
(window.SoChatCS.q = window.SoChatCS.q || []).push(arguments)
}
SoChatCS('init', {
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com', // 私有化部署改成自家 API 域
externalUserId: 'u_42',
nickname: '李雷',
extra: { orderNo: 'O-2026-001' },
onReady: () => console.log('widget ready'),
onMessage: (m) => console.log('msg', m),
})
</script>脚本异步加载期间的命令会被排队,加载完成后自动重放。适合宿主页有自己的初始化时序时使用。
ESM / CJS 双产物 + 完整 TypeScript 类型声明,与 CDN 同源同代码,体积同为 gzipped ~12 KB。SSR 框架请放在客户端 hook 中加载。
# pnpm pnpm add @sochatlive/cs-widget # npm npm install @sochatlive/cs-widget # yarn yarn add @sochatlive/cs-widget
import SoChatCS from '@sochatlive/cs-widget'
await SoChatCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com', // 私有化部署改成自家 API 域
externalUserId: currentUser?.id,
nickname: currentUser?.name,
onReady: () => console.log('ready'),
onMessage: (m) => console.log(m),
})
SoChatCS.open({ prefill: '我想咨询订单' }) Widget 走 三层身份识别:externalUserId(业务方稳定 ID)> fingerprint(浏览器低熵特征 hash)> visitorId(localStorage UUID 兜底)。任一层命中即视为同一访客;后端按优先级匹配并自动合并身份记录。游客身份只进独立的 cs_visitors 表,不会写入主用户表。
业务系统中稳定且唯一的用户标识。最高优先级,能识别同一用户跨站点跨设备的会话历史;登录后传入会自动合并已有的游客记录。
浏览器低熵特征 hash:UA、屏幕、时区、canvas/WebGL/字体等组合算 SHA-256。用户清缓存或切隐身后仍约 60-80% 能命中同一访客。SDK 仅上传 hash,不上传原始指纹原料。
Widget 在 localStorage 自动生成 v_<uuid>。同浏览器内最稳定;清缓存后会换新值,由 L2 fingerprint 兜住。
nickname / avatar / email / phone / extra 都是 optional;extra 是任意 JSON,可放订单号、套餐等业务上下文。
// 用户登录后立即同步身份;无需 shutdown + init
SoChatCS.identify({
externalUserId: currentUser.id, // 业务系统中稳定唯一的 ID
nickname: currentUser.name,
avatar: currentUser.avatar,
email: currentUser.email,
extra: { orderNo: 'O-2026-001' },
})
// 用户登出
SoChatCS.identify({ externalUserId: null }) 合并优先级:SoChatCS.init() > data-* > query string > 远端站点配置。三种渠道可自由叠加。
| 字段 | data-* 属性 | 类型 / 默认 | 说明 |
|---|---|---|---|
| appKey必填 | data-app-key | string | 站点接入参数,admin 创建站点时下发。 |
| apiBase | data-api-base | string · 默认 chat.sochatlive.com | 后端基础地址,私有化部署时覆盖。 |
| wsBase | data-ws-base | string · 自动推导 | WebSocket 地址,默认从 apiBase 推导。 |
| externalUserId | data-external-user-id | string | 业务方稳定用户 ID(§9 三层识别第一层)。 |
| nickname | data-nickname | string | 展示昵称。 |
| avatar | data-avatar | string · URL | 头像图片 URL。 |
| data-email | string | 邮箱。 | |
| phone | data-phone | string | 手机号。 |
| extra | data-extra | JSON string | 业务上下文(订单号 / 套餐等),客服侧可见。 |
| locale | data-locale | "zh-CN" | "en-US" | 强制语言;不传走 localStorage / 浏览器探测。 |
| theme | data-theme | "light" | "dark" | "auto" | 默认 light;auto 跟随 prefers-color-scheme。 |
| position | data-position | "br" | "bl" | "tr" | "tl" | 悬浮按钮位置,默认右下 br。 |
| defaultOpen | data-default-open | boolean · 默认 false | 页面加载完成后自动展开面板。 |
| zIndex | data-z-index | number · 默认 2147483000 | Shadow DOM 容器层级,仅当宿主页有更高层级浮层时调整。 |
| mock | data-mock | boolean · 默认 false | 本地 mock 模式,不连接后端,用于演示与离线开发。 |
| — | data-defer-init | attribute | 关闭自动初始化,必须配合 SoChatCS.init() 使用。 |
所有方法挂在全局 window.SoChatCS。下表与 Live Demo 中的按钮组一一对应。
| 方法 | 参数 | 说明 |
|---|---|---|
| init(cfg) | InitConfig | 手动初始化(仅 data-defer-init 模式)。 |
| identify(profile) | { externalUserId?, nickname?, ... } | 同步登录态;不重连,不丢未读。 |
| open(opts?) | { prefill?: string } | 展开面板,可选预填输入框。 |
| close() | — | 收起面板。 |
| toggle() | — | 切换展开 / 收起。 |
| sendMessage(text) | string | 在面板内追加并发送一条游客消息。 |
| setLocale(locale) | "zh-CN" | "en-US" | 切换语言,自动写入 localStorage。 |
| on(event, handler) | WidgetEvent, fn | 订阅事件。 |
| off(event, handler) | WidgetEvent, fn | 取消订阅。 |
| shutdown() | — | 销毁实例并释放资源(SPA 退出登录场景)。 |
| version | — | 只读字段,当前 widget 版本。 |
SoChatCS.open() // 展开面板
SoChatCS.close() // 收起面板
SoChatCS.toggle() // 切换
SoChatCS.open({ prefill: '我想咨询订单' }) // 打开并预填
SoChatCS.sendMessage('你好') // 直接发送
SoChatCS.identify({ externalUserId: 'u_42' }) // 同步登录态
SoChatCS.setLocale('en-US') // 切换语言(持久化)
SoChatCS.shutdown() // 销毁实例(SPA 退出登录时用)SoChatCS.on('ready', () => console.log('widget 已就绪'))
SoChatCS.on('open', () => console.log('面板已展开'))
SoChatCS.on('close', () => console.log('面板已收起'))
SoChatCS.on('message', (m) => {
// m: { id, conversationId?, from: 'visitor' | 'agent' | 'system',
// text, agentName?, createdAt }
console.log(`new ${m.from} message: ${m.text}`)
})
SoChatCS.on('error', (e) => console.warn(e.message)) 事件枚举:ready · open · close · message · error · identify · shutdown。
当前支持 zh-CN 与 en-US。Widget 与 SoChat App / Admin 共用同一个 localStorage['sochat_locale'] 键。
// 跟随你的网站语言切换器:
SoChatCS.setLocale('en-US')
// → 写入 localStorage['sochat_locale'],刷新页面仍记忆
// 完全不依赖 widget JS 也行:
localStorage.setItem('sochat_locale', 'en-US') 适合已有打包流程的工程(Vue / React / Next / Nuxt / Remix / Astro 等)。SDK 提供 ESM + CJS 双产物,完整 TypeScript 类型,与 CDN 共用同一份核心代码。命名导出对 tree-shaking 更友好,未使用的 API(如 shutdown)不会进入打包产物。
@sochatlive/cs-widget npm v0.1.1
ESM + CJS 双产物 · 自带类型声明 · IIFE 自启动版本同 dist 一并发布 · 0 运行时依赖。
最新版本
v0.1.1
实时拉自 npm registry
体积(gzipped)
~12 KB
37 KB unpacked
运行时依赖
0
仅浏览器内置 API
License
MIT
商业项目可用
# pnpm pnpm add @sochatlive/cs-widget # npm npm install @sochatlive/cs-widget # yarn yarn add @sochatlive/cs-widget
import SoChatCS from '@sochatlive/cs-widget'
await SoChatCS.init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com', // 私有化部署改成自家 API 域
externalUserId: currentUser?.id,
nickname: currentUser?.name,
onReady: () => console.log('ready'),
onMessage: (m) => console.log(m),
})
SoChatCS.open({ prefill: '我想咨询订单' })import { init, identify, open, on, version } from '@sochatlive/cs-widget'
await init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com',
})
on('message', (m) => console.log(m))
console.log('widget version', version)// require('@sochatlive/cs-widget').default 是 SoChatCS 主对象
const SoChatCS = require('@sochatlive/cs-widget').default
// 也可解构命名导出
const { init, open } = require('@sochatlive/cs-widget')
init({
appKey: 'sk_xxxxxxxxxxxxxxxxx',
apiBase: 'https://chat.sochatlive.com',
}) cs-widget 需要访问 window / document / localStorage,必须仅在客户端加载,否则 SSR 阶段会报 window is not defined。下面是两个常见框架的标准写法:
<!-- components/CSWidget.vue —— 仅在客户端 onMounted 加载 -->
<script setup lang="ts">
import { onMounted } from 'vue'
onMounted(async () => {
const { default: SoChatCS } = await import('@sochatlive/cs-widget')
const cfg = useRuntimeConfig()
await SoChatCS.init({
appKey: cfg.public.sochatAppKey,
apiBase: cfg.public.sochatApiBase ?? 'https://chat.sochatlive.com',
})
})
</script>
<template><div /></template>// components/CSWidget.tsx —— App Router 客户端组件
'use client'
import { useEffect } from 'react'
export function CSWidget() {
useEffect(() => {
import('@sochatlive/cs-widget').then(({ default: SoChatCS }) =>
SoChatCS.init({
appKey: process.env.NEXT_PUBLIC_SOCHAT_APP_KEY!,
apiBase: process.env.NEXT_PUBLIC_SOCHAT_API_BASE
?? 'https://chat.sochatlive.com',
}),
)
}, [])
return null
}| 条件 | 入口 | 说明 |
|---|---|---|
| import | ./dist/index.js | ESM 入口(Vite / Webpack 5 / Rollup / 现代 bundler 自动选用) |
| require | ./dist/index.cjs | CommonJS 入口(Node 18+ / Webpack 4 / Jest 默认) |
| types | ./dist/index.d.ts / .d.cts | 完整 TypeScript 类型声明(含 InitConfig / ChatMessage / WidgetEvent 等) |
| unpkg / jsdelivr | ./dist/widget.js | IIFE 自启动产物,CDN 镜像直接 <script> 引用 |
| ./widget.js | ./dist/widget.js | 显式子路径,便于 bundler 在特殊场景直接引用 IIFE 产物 |
unpkg / jsdelivr 字段指向 dist/widget.js,可作为 CDN 等价镜像: unpkg / jsDelivr。
<script>):纯营销页 / WordPress / 不接管打包流程的网站;最快上线,无需 build。import):Vue / React / Next / Nuxt 等已有打包流程的工程;可与你的代码一起 tree-shake、压缩、code-split,并享受完整的 TypeScript 类型提示。 Widget 不使用 eval / new Function / 内联样式注入;所有 DOM 与 CSS 都在 Shadow DOM 内隔离。如果你的站点开启了 CSP,下面是最小可用配置:
Content-Security-Policy: script-src 'self' https://chat.sochatlive.com; connect-src 'self' https://chat.sochatlive.com wss://chat.sochatlive.com; img-src 'self' data: https://chat.sochatlive.com;
originAllowList,握手请求的 Origin 必须命中其中一项才能成功,避免 appKey 被误嵌到非法站点。 95% 的接入方无需关心下面这些端点;Widget 已经处理了握手、JWT 续期、消息推送与降级。如果你要做深度集成(比如 SSR 时复用游客身份),请按下表使用。
| Method | Path | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/v1/cs/handshake | appKey + Origin | 颁发 visitor JWT,30 分钟有效。 |
| POST | /api/v1/cs/identify | visitor JWT | 更新游客资料(昵称 / 邮箱 / extra 等)。 |
| POST | /api/v1/cs/conversations | visitor JWT | 游客发送消息(首条自动建会话)。 |
| GET | /api/v1/cs/conversations/current | visitor JWT | 取当前活跃会话。 |
| GET | /api/v1/cs/conversations/:id/messages | visitor JWT | 消息列表(since 增量拉取)。 |
| POST | /api/v1/cs/conversations/:id/leave | visitor JWT | 游客离开会话。 |
| WS | /ws (subprotocols: sochat-cs, jwt.<token>) | visitor JWT | 推送会话事件 / 客服消息。 |
所有 REST 请求都会自动携带 X-Locale: <当前语言>,便于服务端按访客语言返回错误码翻译。
Widget 出问题时优先看浏览器 Console;下面这张表覆盖了 99% 的现场。
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 没有出现悬浮按钮 | 没有 data-app-key;或脚本被 ad-block 拦截 | 检查 console 是否有 [SoChatCS] no appKey;尝试在 mock 模式离线验证。 |
| 按钮出现但点不开 | 宿主页 CSP 拦截 connect-src | 把 chat.sochatlive.com(含 wss)加入 CSP 白名单。 |
| 握手 403 origin denied | 当前域名不在站点 originAllowList | 让平台运营把当前域名加入 admin 后台站点白名单。 |
| 握手返回 available=false | 平台 feature.customer_service.enabled = false | 由超管在 admin 后台开启平台总开关。 |
| WebSocket failed | 浏览器 / 代理拒绝 wss | 无需处理,会自动降级到 8s 短轮询,对用户透明。 |
| 路由切换后按钮消失 | SPA 框架 unmount 节点 | widget ≥ 0.1.x 内置 MutationObserver 自愈;升级到最新版本即可。 |
| 用户切换后仍是旧昵称 | 没有调 identify | 登录态变化时务必调 SoChatCS.identify({ externalUserId, nickname })。 |
| 样式被宿主页覆盖 | Shadow DOM 隔离失效(理论上不会发生) | 提交 issue 并附最小复现页面。 |
先用 mock 模式跑通前端交互,再联系平台获取站点 appKey 进入正式联调。生产 widget URL 与本页 Demo 共用同一份 dist 产物。