JavaScript API
简介
... Widget JavaScript API 用于让宿主页面控制访客端小组件,例如打开/收起窗口、监听初始化和未读数变化、切换语言或主题、业务用户登录等。
安装 SDK
安装代码
<!-- Start of ... code -->
<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};
window.__twt = {
appid: "以项目APPID为准",
};
(function (n, t) {
var e = {
init: function () {
var n = t.createElement("script");
n.async = !0;
n.type = "text/javascript";
n.src = ".../install/core.js?version=v1.2";
t.head.appendChild(n);
},
};
e.init();
})(window, document);
</script>
<!-- End of ... code -->
配置选项
所有配置都写入 window.__twt,并在 core.js 引导阶段被读取。
必填配置
| 字段 | 类型 | 说明 |
|---|---|---|
appid | string | 应用唯一标识。生产接入统一使用小写 appid |
可选配置
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
asyncInit | boolean | false | 是否跳过自动 init() |
language | string | 浏览器语言兜底 | 初始语言,支持值见语言配置 |
theme | 'light' | 'dark' | 'system' | light | 初始主题 |
launcher | 'default' | 'custom' | default | launcher 渲染模式 |
zIndex | number | 由 host-sdk 默认样式决定 | widget 容器层级 |
sbs | string | 无 | 业务用户标识 |
sbs_mm | string | 无 | 业务用户签名 |
ranstr | string | 无 | 签名随机串 |
name | string | 无 | 访客名称 |
nickname | string | 无 | 访客昵称 |
email | string | 无 | 访客邮箱 |
phone | string | 无 | 访客手机号 |
referer | string | 当前页面 URL | 来源 URL |
source_title | string | 当前页面标题 | 来源标题 |
示例:
window.__twt = {
appid: "YOUR_APPID",
language: "zh-cn",
theme: "dark",
launcher: "custom",
zIndex: 9999,
};
语言配置
window.__twt.language、和 window.TWTChatWidget.setLanguage(locale) 使用同一套 runtime locale。接入方推荐直接传下表中的标准值;大小写和常见地区变体会在 SDK / runtime 内归一化。
| 标准值 | 语言 |
|---|---|
en | English |
zh-cn | 简体中文 |
zh-tw | 繁體中文 |
ja | 日本語 |
ko | 한국어 |
de | Deutsch |
fr | Français |
pt | Português |
ru | Русский |
未显式配置语言时,SDK 会按 navigator.languages / navigator.language 选择第一个可支持语言;没有匹配项时回退到 en。不支持的显式值不会作为 runtime locale 保留,接入方应避免传入上表以外的语言值。
初始化时机
默认情况下,core.js 加载后会自动 init() 并创建 iframe。
如果需要延迟创建 widget iframe,可以设置 asyncInit: true,再在合适时机手动初始化:
<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};
window.__twt = {
appid: "YOUR_APPID",
asyncInit: true,
};
window.TWTChatWidget.on("initialized", function () {
console.log("Widget initialized");
});
(function (d) {
var sdk = d.createElement("script");
sdk.async = true;
sdk.src = ".../install/core.js?version=v1.2";
sdk.onload = function () {
document
.querySelector("#contact-us")
.addEventListener("click", function () {
window.TWTChatWidget.init();
});
};
d.head.appendChild(sdk);
})(document);
</script>
init() 是幂等的,重复调用不会重复创建实例。使用 asyncInit 时,必须等 core.js 加载完成后再调用 init()。get() 是同步读取,必须在 init() 已完成后使用,否则会抛出错误。
入口约定
window.TWTChatWidget
window.TWTChatWidget 是统一的对外 API 入口。宿主页面调用方法、注册事件、读取状态时,都应该使用它:
window.TWTChatWidget.on("initialized", function () {
// SDK 小组件加载完,自动打开小组件
window.TWTChatWidget.call("maximize");
});
核心函数
| 函数 | 说明 | 初始化前是否可用 |
|---|---|---|
on(event, handler) | 注册事件监听 | 最小安装 stub 支持排队 |
once(event, handler) | 注册一次性事件监听 | 取决于安装 stub 是否提供 |
off(event, handler?) | 移除事件监听 | 取决于安装 stub 是否提供 |
call(method, payload?) | 调用 Widget 方法 | 建议 initialized 后调用 |
get(getter) | 同步读取状态 | 只能初始化后调用 |
init() | 手动初始化 | core.js 加载后可用 |
login(...) | 业务用户登录便捷方法 | 初始化后调用 |
setLanguage(locale) | call('set_language', locale) 便捷方法 | 初始化后调用 |
scriptTagVersion() | 返回当前 SDK 脚本版本 | core.js 加载后可用 |
scriptTagVersion() 返回的值与 get('sdk_info').scriptTagVersion 等价,都是安装片段的 snippet 版本号;区别仅在于 scriptTagVersion() 在 core.js 加载后即可调用,而 get('sdk_info') 必须等 init() 完成。
Methods
maximize
展开聊天面板。
window.TWTChatWidget.call("maximize");
minimize
收起聊天面板,仅保留内置 launcher。
window.TWTChatWidget.call("minimize");
hide
隐藏整个 widget。iframe 仍保持挂载。
window.TWTChatWidget.call("hide");
show
从隐藏态恢复到最小化态。
window.TWTChatWidget.call("show");
toggle
在展开态和最小化态之间切换。
window.TWTChatWidget.call("toggle");
destroy
销毁 host 侧 iframe、监听器和营销卡片 surface。
window.TWTChatWidget.call("destroy");
destroy 是 host-side API,不要求 runtime 先确认销毁。
login
用新的业务用户身份重载 iframe。
推荐使用便捷方法:
window.TWTChatWidget.login(
"session_token_123", // sbs,业务用户标识
"signature_abc", // sbs_mm,签名
"random_xyz", // ranstr,签名随机串
"张三", // name,可选
"小张", // nickname,可选
"zhangsan@example.com", // email,可选
"13800138000", // phone,可选
);
也可以直接调用 method:
window.TWTChatWidget.call("login", {
sbs: "session_token_123",
sbs_mm: "signature_abc",
ranstr: "random_xyz",
name: "张三",
nickname: "小张",
email: "zhangsan@example.com",
phone: "13800138000",
});
sbs、sbs_mm、ranstr 是必填签名字段。缺失时,runtime 会拒绝本次登录命令。
set_language
切换 runtime 语言。
window.TWTChatWidget.call("set_language", "en");
便捷写法:
window.TWTChatWidget.setLanguage("en");
支持的语言值与初始化配置一致,见语言配置。set_language 会先把输入归一化成 runtime locale,再同步 launcher 和 panel;如果 runtime 当前会话拒绝语言切换,panel 文案不会更新。切换到不支持的语言时不会新增语言包,宿主页应只传支持列表中的值。
set_theme
切换主题。
window.TWTChatWidget.call("set_theme", "dark");
可选值:
| 值 | 说明 |
|---|---|
light | 浅色主题 |
dark | 深色主题 |
system | 跟随系统 |
Getters
get() 是同步方法,不会排队等待 runtime。请在 initialized 之后调用。
state
读取当前可见性。
const state = window.TWTChatWidget.get("state");
console.log(state.visibility);
返回:
| 字段 | 类型 | 说明 |
|---|---|---|
visibility | 'maximized' | 'minimized' | 'hidden' | 当前可见性 |
sdk_info
读取 SDK 版本和运行时信息。
const sdkInfo = window.TWTChatWidget.get("sdk_info");
返回:
| 字段 | 类型 | 说明 |
|---|---|---|
version | string | host-sdk 运行时版本 |
scriptTagVersion | string | 脚本版本 |
compatibilityMode | 'off' | 'livechat' | 兼容模式,当前返回 off |
transport | 'ws' | 'http-polling' | 'mixed' | 当前返回 mixed,表示 host bridge + runtime WS 的组合形态 |
unread_count
读取当前缓存的未读数。
const count = window.TWTChatWidget.get("unread_count");
未收到 runtime 的 unread_count_changed 前,默认返回 0。
theme
读取当前 SDK 侧主题。
const theme = window.TWTChatWidget.get("theme");
返回 light、dark 或 system。
Events
事件通过 TWTChatWidget.on(event, handler) 监听。
window.TWTChatWidget.on("visibility_changed", function (payload) {
console.log(payload.visibility);
});
需要解绑时保留同一个 handler 引用:
function handleVisibilityChanged(payload) {
console.log(payload.visibility);
}
window.TWTChatWidget.on("visibility_changed", handleVisibilityChanged);
window.TWTChatWidget.off("visibility_changed", handleVisibilityChanged);
生命周期事件
| 事件 | Payload | 说明 |
|---|---|---|
initialized | 无 | host-sdk init() 完成 |
iframe_loaded | { surface: string } | iframe DOM load 完成,仅用于诊断 |
bridge_ready | 无 | runtime bridge handler 已注册,可以接受命令 |
runtime_mounted | 无 | Vue runtime 已挂载 |
visitor_auth_ready | 见下方 | 访客 token 已解析或恢复 |
runtime_activated | 无 | 首次激活完成,初始会话列表已解析 |
destroyed | 无 | widget 已销毁 |
initialized 和 bridge_ready 不等于 WebSocket 已连接,也不等于认证已完成。需要关注连接状态时,请监听 connection_state_changed 或 session_connected。
visitor_auth_ready
访客身份已准备好,payload 可能包含可回写的身份线索。
| 字段 | 类型 | 说明 |
|---|---|---|
bs | string | 访客身份线索 |
bs_mm | string | 访客身份签名线索 |
fk_id | string | 旧客户页兼容字段 |
fkid | string | runtime / 后端记录字段,语义等同于 fk_id |
hasToken | boolean | 当前 runtime 是否已有可用访客 token |
source | 'initial' | 'resolved' | 来源 |
visitorUid | string | runtime 内部访客 uid,不等同于 fk_id |
host-sdk 会基于该事件把 ITP 身份写回当前存储策略。接入方如需同步到自己的业务存储,也可以自行监听:
window.TWTChatWidget.on("visitor_auth_ready", function (payload) {
if (payload.bs && payload.bs_mm) {
localStorage.setItem(
"twt_visitor_identity",
JSON.stringify({
bs: payload.bs,
bs_mm: payload.bs_mm,
fk_id: payload.fk_id || payload.fkid,
}),
);
}
});
状态事件
| 事件 | Payload | 说明 |
|---|---|---|
visibility_changed | { visibility } | 可见性变化 |
connection_state_changed | { status } | 连接状态变化 |
session_connected | { status: 'connected' } | WebSocket 已连接 |
session_disconnected | { status } | WebSocket 断开或重连中 |
auth_closed | 见下方 | 访客认证已被服务端关闭 |
bootstrap_failed | 见下方 | embedded 启动必需链路失败 |
unread_count_changed | { count: number } | 未读数变化 |
visibility 可选值:
| 值 | 说明 |
|---|---|
maximized | 面板展开 |
minimized | 仅显示 launcher |
hidden | launcher 和面板都隐藏 |
status 可选值:
| 值 | 说明 |
|---|---|
disconnected | 未连接或已断开 |
connecting | 正在连接 |
connected | 已连接 |
reconnecting | 正在重连 |
auth_closed
认证关闭后,host-sdk 会隐藏 widget 并向宿主页面派发该事件。
常见 payload 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
message | string | 关闭原因描述 |
reason | string | 关闭原因 |
source | string | 来源,如 http 或 WS 来源 |
clearToken | boolean | 是否需要清理本地 token |
closeAuthType | string | 服务端关闭类型 |
closeCode | number | 服务端关闭码 |
terminal | string | 终端来源 |
bootstrap_failed
embedded 启动必需链路失败时触发。host-sdk 会隐藏并清理当前 widget。
常见 payload 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
reason | 'domain_not_whitelisted' | 'login_failed' | 'invalid_bootstrap' | 'timeout' | string | 失败原因 |
source | string | 失败来源 |
mode | 'embedded' | 当前运行模式 |
该事件只表示嵌入启动失败,不用于普通会话内 HTTP 404。
常用场景
以下示例默认页面已经按上文完成 SDK 安装。
监听初始化后打开窗口
window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.call("maximize");
});
自定义入口按钮
<button id="open-chat">联系客服</button>
<button id="close-chat">收起客服</button>
<script>
window.__twt = {
appid: "YOUR_APPID",
launcher: "custom",
};
document.querySelector("#open-chat").addEventListener("click", function () {
window.TWTChatWidget.call("maximize");
});
document.querySelector("#close-chat").addEventListener("click", function () {
window.TWTChatWidget.call("minimize");
});
</script>
<script
src=".../install/core.js?version=v1.2"
async
></script>
launcher: 'custom' 会关闭内置 launcher iframe。此时宿主页面需要自己提供入口按钮。
内置 launcher 悬停展示自定义咨询浮层
- 配置使用
window.__twt - 就绪事件使用
window.TWTChatWidget.on("initialized", ...) - 打开聊天使用
window.TWTChatWidget.call("maximize") - 内置 launcher 元素使用
iframe[data-twt-widget-surface="launcher"]
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<title>... Widget Demo</title>
<style>
#my-custom-popup {
position: fixed;
right: 20px;
bottom: 100px;
z-index: 2147483647;
width: 280px;
padding: 20px;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
pointer-events: none;
background: rgba(255, 255, 255, 0.85);
border: 1px solid rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
opacity: 0;
transform: translateY(15px);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
#my-custom-popup.show {
pointer-events: auto;
opacity: 1;
transform: translateY(0);
}
#my-custom-popup .btn-consult {
display: block;
padding: 10px;
font-size: 14px;
font-weight: 500;
color: #fff;
text-align: center;
text-decoration: none;
margin-bottom: 8px;
background: #2563eb;
border-radius: 8px;
transition:
background 0.2s,
transform 0.1s;
}
#my-custom-popup .btn-consult:hover {
background: #1d4ed8;
}
#my-custom-popup .btn-consult:active {
transform: scale(0.98);
}
</style>
</head>
<body>
<div id="my-custom-popup">
<div
style="font-size: 16px; font-weight: 600; color: #1e293b; margin-bottom: 8px"
>
您好,欢迎咨询!
</div>
<div
style="font-size: 13px; color: #64748b; line-height: 1.5; margin-bottom: 16px"
>
我们随时为您提供贴心服务。点击下方按钮即可发起实时在线沟通。
</div>
<div style="display: flex; flex-direction: column">
<a href="javascript:void(0)" class="btn-consult" id="btn-start-chat"
>立即咨询</a
>
<div style="font-size: 11px; text-align: center; color: #94a3b8">
服务时间: 9:00 - 18:00
</div>
</div>
</div>
<!-- Start of ... code -->
<script>
window.TWTChatWidget = window.TWTChatWidget || {
_q: [],
on: function () {
this._q.push(["on", Array.prototype.slice.call(arguments)]);
},
};
window.__twt = {
appid: "以项目APPID为准",
launcher: "default",
};
(function (n, t) {
var e = {
init: function () {
var n = t.createElement("script");
n.async = !0;
n.type = "text/javascript";
n.src = ".../install/core.js?version=v1.2";
t.head.appendChild(n);
},
};
e.init();
})(window, document);
(function () {
var hoverTimer = null;
var isHovered = false;
var popup = document.getElementById("my-custom-popup");
function showPopup() {
if (popup) {
popup.classList.add("show");
}
}
function hidePopup() {
if (popup) {
popup.classList.remove("show");
}
}
function bindPopupHover(launcherFrame) {
if (!launcherFrame || !popup) {
return;
}
function onEnter() {
isHovered = true;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
showPopup();
}
function onLeave() {
isHovered = false;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
hoverTimer = setTimeout(function () {
if (!isHovered) {
hidePopup();
}
}, 200);
}
launcherFrame.addEventListener("mouseenter", onEnter);
launcherFrame.addEventListener("mouseleave", onLeave);
popup.addEventListener("mouseenter", function () {
isHovered = true;
if (hoverTimer) {
clearTimeout(hoverTimer);
}
});
popup.addEventListener("mouseleave", onLeave);
var startChatButton = document.getElementById("btn-start-chat");
if (startChatButton) {
startChatButton.addEventListener("click", function () {
hidePopup();
window.TWTChatWidget.call("maximize");
});
}
}
window.TWTChatWidget.on("initialized", function () {
var launcherFrame = document.querySelector(
'iframe[data-twt-widget-surface="launcher"]',
);
bindPopupHover(launcherFrame);
});
})();
</script>
<!-- End of ... code -->
</body>
</html>
如果配置了 launcher: "custom",host-sdk 不会创建内置 launcher iframe,上面的 hover 绑定目标也就不存在;这种情况下应改用宿主页面自己的按钮或入口元素。
未读数同步页面标题
var originalTitle = document.title;
window.TWTChatWidget.on("unread_count_changed", function (payload) {
document.title =
payload.count > 0
? "(" + payload.count + ") " + originalTitle
: originalTitle;
});
连接状态观测
window.TWTChatWidget.on("connection_state_changed", function (payload) {
console.log("connection status:", payload.status);
});
window.TWTChatWidget.on("session_connected", function () {
console.log("session connected");
});
window.TWTChatWidget.on("session_disconnected", function (payload) {
console.log("session disconnected:", payload.status);
});
切换主题
window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.call("set_theme", "dark");
});
切换语言
window.TWTChatWidget.on("initialized", function () {
window.TWTChatWidget.setLanguage("en");
});