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-tw",
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-TW">
<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");
});