开放平台 · 网页客服接入

npm · @sochatlive/cs-widget v0.1.1

cs-widget · 一行脚本接入网页客服

纯 TypeScript + Shadow DOM 构建,gzipped ~12 KB,零运行时依赖。CDN 与 npm 双形态接入;本页所有示例与下方 Live Demo 共用同一份构建产物。

CDN: https://chat.sochatlive.com/cs/widget.js npm: @sochatlive/cs-widget 目标 API 域: https://chat.sochatlive.com 鉴权: data-app-key + visitor JWT 支持: zh-CN · en-US 传输: REST + WebSocket(自动降级 8s 长轮询)
CDN(零代码)
<script async src="https://chat.sochatlive.com/cs/widget.js"
  data-app-key="sk_xxxxxxxxxxxxxxxxx"
  data-api-base="https://chat.sochatlive.com"></script>
npm(Vue / React / Next / Nuxt)
pnpm add @sochatlive/cs-widget
import SoChatCS from '@sochatlive/cs-widget'
SoChatCS.init({
  appKey: 'sk_xxxxxxxxxxxxxxxxx',
  apiBase: 'https://chat.sochatlive.com',
})

Live Demo

本页已加载真实的 https://chat.sochatlive.com/cs/widget.js 构建产物(data-mock="true",不连接后端)。点击右下角悬浮按钮立即试用,下方按钮覆盖全部命令式 API 与事件订阅。

独立窗口打开 ↗
正在加载交互面板…

提示:右下角悬浮按钮 + 聊天面板就是 widget 实际形态。本页 widget 与 /cs-demo 页面共用同一份构建产物,只是这里直接内嵌、那里作为单独发布的 demo 页。

快速开始(30 秒)

你只需要一个站点 appKey。它由平台运营在 admin 后台创建站点时下发;本页面所有示例都给了占位值,可以先用 data-mock="true" 跑通前端再申请正式参数。

A. 单 <script> 标签接入(推荐)

<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>

B. Query String(无法写 data-* 时)

<script async src="https://chat.sochatlive.com/cs/widget.js?appKey=sk_xxxxxxxxxxxxxxxxx&apiBase=https%3A%2F%2Fchat.sochatlive.com&locale=en-US"></script>

C. 命令式初始化(与 Intercom 风格一致)

<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>

脚本异步加载期间的命令会被排队,加载完成后自动重放。适合宿主页有自己的初始化时序时使用。

D. npm 安装(Vue / React / Next / Nuxt)

完整说明 ↓

ESM / CJS 双产物 + 完整 TypeScript 类型声明,与 CDN 同源同代码,体积同为 gzipped ~12 KB。SSR 框架请放在客户端 hook 中加载。

install
# 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 表,不会写入主用户表

L1 · externalUserId(推荐)

业务系统中稳定且唯一的用户标识。最高优先级,能识别同一用户跨站点跨设备的会话历史;登录后传入会自动合并已有的游客记录。

L2 · fingerprint(v0.1.1+ 自动启用)

浏览器低熵特征 hash:UA、屏幕、时区、canvas/WebGL/字体等组合算 SHA-256。用户清缓存或切隐身后仍约 60-80% 能命中同一访客。SDK 仅上传 hash,不上传原始指纹原料。

L3 · visitorId(兜底)

Widget 在 localStorage 自动生成 v_<uuid>。同浏览器内最稳定;清缓存后会换新值,由 L2 fingerprint 兜住。

可选展示字段

nickname / avatar / email / phone / extra 都是 optional;extra 是任意 JSON,可放订单号、套餐等业务上下文。

SPA 中用户登录后同步身份

// 用户登录后立即同步身份;无需 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-keystring站点接入参数,admin 创建站点时下发。
apiBasedata-api-basestring · 默认 chat.sochatlive.com后端基础地址,私有化部署时覆盖。
wsBasedata-ws-basestring · 自动推导WebSocket 地址,默认从 apiBase 推导。
externalUserIddata-external-user-idstring业务方稳定用户 ID(§9 三层识别第一层)。
nicknamedata-nicknamestring展示昵称。
avatardata-avatarstring · URL头像图片 URL。
emaildata-emailstring邮箱。
phonedata-phonestring手机号。
extradata-extraJSON string业务上下文(订单号 / 套餐等),客服侧可见。
localedata-locale"zh-CN" | "en-US"强制语言;不传走 localStorage / 浏览器探测。
themedata-theme"light" | "dark" | "auto"默认 light;auto 跟随 prefers-color-scheme。
positiondata-position"br" | "bl" | "tr" | "tl"悬浮按钮位置,默认右下 br。
defaultOpendata-default-openboolean · 默认 false页面加载完成后自动展开面板。
zIndexdata-z-indexnumber · 默认 2147483000Shadow DOM 容器层级,仅当宿主页有更高层级浮层时调整。
mockdata-mockboolean · 默认 false本地 mock 模式,不连接后端,用于演示与离线开发。
data-defer-initattribute关闭自动初始化,必须配合 SoChatCS.init() 使用。

JS API

所有方法挂在全局 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-CNen-US。Widget 与 SoChat App / Admin 共用同一个 localStorage['sochat_locale'] 键。

优先级(高 → 低)

  1. SoChatCS.init({ locale }) / setLocale()
  2. data-locale="..."
  3. src="...?locale=..."
  4. localStorage['sochat_locale'](与 SoChat App / Admin 共用)
  5. 远端站点配置(admin 后台默认值)
  6. navigator.languages:zh-* → 中文,其他 → 英文
  7. 兜底:en-US

切换语言

// 跟随你的网站语言切换器:
SoChatCS.setLocale('en-US')
// → 写入 localStorage['sochat_locale'],刷新页面仍记忆

// 完全不依赖 widget JS 也行:
localStorage.setItem('sochat_locale', 'en-US')

npm 安装与 ESM/TypeScript 用法

适合已有打包流程的工程(Vue / React / Next / Nuxt / Remix / Astro 等)。SDK 提供 ESM + CJS 双产物,完整 TypeScript 类型,与 CDN 共用同一份核心代码。命名导出对 tree-shaking 更友好,未使用的 API(如 shutdown)不会进入打包产物。

TypeScript · Browser已发布
浏览器 ES2018+

@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

ESM / TypeScript(默认导出)

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: '我想咨询订单' })

命名导出(tree-shake 更友好)

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)

CommonJS

// 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',
})

SSR 框架(Next.js / Nuxt)

cs-widget 需要访问 window / document / localStorage必须仅在客户端加载,否则 SSR 阶段会报 window is not defined。下面是两个常见框架的标准写法:

Nuxt 3

<!-- 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>

Next.js(App Router)

// 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
}

导出表(Exports map)

条件入口说明
import./dist/index.jsESM 入口(Vite / Webpack 5 / Rollup / 现代 bundler 自动选用)
require./dist/index.cjsCommonJS 入口(Node 18+ / Webpack 4 / Jest 默认)
types./dist/index.d.ts / .d.cts完整 TypeScript 类型声明(含 InitConfig / ChatMessage / WidgetEvent 等)
unpkg / jsdelivr./dist/widget.jsIIFE 自启动产物,CDN 镜像直接 <script> 引用
./widget.js./dist/widget.js显式子路径,便于 bundler 在特殊场景直接引用 IIFE 产物

unpkg / jsdelivr 字段指向 dist/widget.js,可作为 CDN 等价镜像: unpkg / jsDelivr

CDN vs npm:什么时候选哪种?
  • CDN(<script>:纯营销页 / WordPress / 不接管打包流程的网站;最快上线,无需 build。
  • npm(import:Vue / React / Next / Nuxt 等已有打包流程的工程;可与你的代码一起 tree-shake、压缩、code-split,并享受完整的 TypeScript 类型提示。
  • 两种方式可以混用,例如生产走 CDN、开发用 npm 拿类型;但同一页面只应启用其中一种避免重复 init。

CSP / 安全

Widget 不使用 eval / new Function / 内联样式注入;所有 DOM 与 CSS 都在 Shadow DOM 内隔离。如果你的站点开启了 CSP,下面是最小可用配置:

Content-Security-Policy 推荐

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;
站点白名单:admin 后台为每个站点维护 originAllowList,握手请求的 Origin 必须命中其中一项才能成功,避免 appKey 被误嵌到非法站点。

REST 端点(仅供深度集成)

95% 的接入方无需关心下面这些端点;Widget 已经处理了握手、JWT 续期、消息推送与降级。如果你要做深度集成(比如 SSR 时复用游客身份),请按下表使用。

MethodPath鉴权说明
POST/api/v1/cs/handshakeappKey + Origin颁发 visitor JWT,30 分钟有效。
POST/api/v1/cs/identifyvisitor JWT更新游客资料(昵称 / 邮箱 / extra 等)。
POST/api/v1/cs/conversationsvisitor JWT游客发送消息(首条自动建会话)。
GET/api/v1/cs/conversations/currentvisitor JWT取当前活跃会话。
GET/api/v1/cs/conversations/:id/messagesvisitor JWT消息列表(since 增量拉取)。
POST/api/v1/cs/conversations/:id/leavevisitor 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 模式还能正常对话?
mock 模式下消息不会发到后端,所有响应都在本地模拟。它专门用于前端 UI / 调用方式 / 事件订阅的离线验证,1.4 秒后会收到一条 [mock] 自动回声。
可以同时嵌入到多个二级域名吗?
可以。每个站点 = 一份 appKey + 一份 originAllowList。把所有需要嵌入的二级域名加进白名单即可。
客服会看到什么信息?
Widget 上传的 externalUserId / nickname / avatar / email / phone / extra 字段,外加浏览器型号、当前页 URL、Referer、IP 与时区,便于客服快速理解上下文。
visitor JWT 会过期吗?
会,默认 30 分钟。Widget 在到期前自动调 handshake 续期;切窗 / 刷新后通过 externalUserId / fingerprint / visitorId 三层身份识别拿回同一访客(即便清了缓存,fingerprint 也能 60-80% 命中)。
能同时嵌入到 SPA 与多页面应用吗?
可以。Widget 通过 MutationObserver 自愈:SPA 路由切换误删容器时下一帧自动重挂。多页面则每次 fresh 加载。
是否需要在我后端做什么?
客服 Widget 完全前端就能跑;如果你想做"游客 → 自家会员体系"打通,可以在自家后端用 externalUserId 反查获取 SoChat 这一侧的 csVisitorId(通过 admin REST API),但绝大多数场景都不需要。
可以自定义按钮样式吗?
当前 0.x 阶段开放主题与位置参数,更多自定义(按钮 SVG、品牌色)将通过站点配置下发。如需提前接入,请联系平台。
widget URL 会做版本管理吗?
会。/cs/widget.js 始终是最新稳定版,5 分钟 CDN 缓存;版本目录 /cs-static/widget-<hash>.js 走长缓存,便于精确回滚。

下一步

先用 mock 模式跑通前端交互,再联系平台获取站点 appKey 进入正式联调。生产 widget URL 与本页 Demo 共用同一份 dist 产物。