跳至主要内容

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 引導階段被讀取。

必填設定

欄位類型說明
appidstring應用唯一識別碼。生產整合統一使用小寫 appid

選填設定

欄位類型預設值說明
asyncInitbooleanfalse是否跳過自動 init()
languagestring瀏覽器語言兜底初始語言,支援值請參閱語言設定
theme'light' | 'dark' | 'system'light初始主題
launcher'default' | 'custom'defaultlauncher 渲染模式
zIndexnumber由 host-sdk 預設樣式決定widget 容器層級
sbsstring業務使用者識別碼
sbs_mmstring業務使用者簽名
ranstrstring簽名隨機串
namestring訪客名稱
nicknamestring訪客暱稱
emailstring訪客電子信箱
phonestring訪客手機號碼
refererstring當前頁面 URL來源 URL
source_titlestring當前頁面標題來源標題

示例:

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 內歸一化。

標準值語言
enEnglish
zh-cn簡體中文
zh-tw繁體中文
ja日本語
ko한국어
deDeutsch
frFrançais
ptPortuguê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",
});

sbssbs_mmranstr 是必填簽名欄位。缺失時,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");

返回:

欄位類型說明
versionstringhost-sdk 運行時版本
scriptTagVersionstring腳本版本
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");

返回 lightdarksystem

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說明
initializedhost-sdk init() 完成
iframe_loaded{ surface: string }iframe DOM load 完成,僅用於診斷
bridge_readyruntime bridge handler 已註冊,可以接受命令
runtime_mountedVue runtime 已掛載
visitor_auth_ready請見下方訪客 token 已解析或恢復
runtime_activated首次激活完成,初始會話列表已解析
destroyedwidget 已銷燬

initializedbridge_ready 不等於 WebSocket 已連線,也不等於認證已完成。需要關注連線狀態時,請監聽 connection_state_changedsession_connected

visitor_auth_ready

訪客身份已準備好,payload 可能包含可回寫的身份線索。

欄位類型說明
bsstring訪客身份線索
bs_mmstring訪客身份簽名線索
fk_idstring舊客戶頁面相容欄位
fkidstringruntime / 後端記錄欄位,語義等同於 fk_id
hasTokenboolean當前 runtime 是否已有可用訪客 token
source'initial' | 'resolved'來源
visitorUidstringruntime 內部訪客 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
hiddenlauncher 和麵板都隱藏

status 可選值:

說明
disconnected未連線或已斷開
connecting正在連線
connected已連線
reconnecting正在重連

auth_closed

認證關閉後,host-sdk 會隱藏 widget 並向宿主頁面發送該事件。

常見 payload 欄位:

欄位類型說明
messagestring關閉原因描述
reasonstring關閉原因
sourcestring來源,如 http 或 WS 來源
clearTokenboolean是否需要清理本地 token
closeAuthTypestring伺服器端關閉類型
closeCodenumber伺服器端關閉碼
terminalstring終端來源

bootstrap_failed

embedded 啟動必需流程失敗時觸發。host-sdk 會隱藏並清理當前 widget。

常見 payload 欄位:

欄位類型說明
reason'domain_not_whitelisted' | 'login_failed' | 'invalid_bootstrap' | 'timeout' | string失敗原因
sourcestring失敗來源
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");
});