chore: import clean Agent Desk snapshot (1/7)

This commit is contained in:
2026-10-16 11:55:22 +00:00
parent 24f6423407
commit 79176541c7
323 changed files with 34882 additions and 2 deletions

164
docs/embedding.zh-CN.md Normal file
View File

@@ -0,0 +1,164 @@
# 嵌入客服挂件
挂件会在任意网页上添加一个聊天入口:只需加载一个脚本,页面上显示悬浮按钮,点击后在 iframe 中打开客服聊天。访客可以同时保留多个请求并随时切换。
[English](./embedding.md) | [Русский](./embedding.ru-RU.md)
## 快速开始
将 `https://support.example.com` 替换为你的部署地址,将 `YOUR_CHANNEL_ID` 替换为控制台中的渠道 ID。
```html
<script>
window.AgentDeskConfig = {
channelId: "YOUR_CHANNEL_ID",
}
</script>
<script src="https://support.example.com/sdk/agent-desk-sdk.min.js" async></script>
```
两段标签放在 `</body>` 之前,且 `AgentDeskConfig` 必须在 SDK 脚本**之前**执行。挂件会自动挂载,按钮出现在页面角落。
标题、副标题、主题色、位置和宽度来自控制台的渠道设置,通常无需在代码中配置。
## 配置项
| 选项 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `channelId` | `string` | – | **必填**,挂件所属渠道。 |
| `baseUrl` | `string` | 脚本所在域名 | Agent Desk 服务地址。 |
| `apiBaseUrl` | `string` | `baseUrl` | API 与页面不同源时使用。 |
| `widgetBaseUrl` | `string` | 脚本所在域名 | 聊天页面的加载地址。 |
| `language` | `"zh-CN" \| "en-US"` | 服务端设置 | 界面语言,任何 `en*` 取值都会选择英文。 |
| `position` | `"left" \| "right"` | 渠道设置 | 按钮和窗口所在一侧。 |
| `themeColor` | `string` | 渠道设置 | 主题色,例如 `#0f6cbd`。 |
| `width` | `string` | `380px` | 桌面端窗口宽度。 |
| `title`、`subtitle` | `string` | 渠道设置 | 窗口标题文字。 |
| `externalId` | `string` | 浏览器本地 ID | 你自己系统中的稳定用户 ID。 |
| `signExternalId` | `(id) => {signature, issuedAt}` | – | 证明 `externalId` 真实有效,见[已识别访客](#已识别访客)。 |
| `externalName` | `string` | – | 显示名称,仅在首次创建访客时使用。 |
| `getUserToken` | `() => string \| Promise<string>` | – | 返回由你的系统签发的 JWT。 |
屏幕宽度不超过 480px 时,聊天窗口会全屏打开。
## JavaScript API
```js
AgentDeskWidget.open() // Promise<void>,打开聊天
AgentDeskWidget.close() // 最小化聊天
AgentDeskWidget.mount(cfg) // 使用显式配置挂载
AgentDeskWidget.destroy() // 移除按钮和聊天窗口
AgentDeskWidget.getChatUrl() // Promise<string>,用于独立页面或新标签页
```
从自己的按钮打开聊天:
```html
<button onclick="AgentDeskWidget.open()">联系客服</button>
```
## React 与 Next.js
只在客户端加载一次脚本。Next.js(App Router)示例:
```tsx
"use client"
import Script from "next/script"
export function SupportWidget({ channelId }: { channelId: string }) {
return (
<>
<Script id="agent-desk-config" strategy="afterInteractive">
{`window.AgentDeskConfig = ${JSON.stringify({ channelId })}`}
</Script>
<Script
src="https://support.example.com/sdk/agent-desk-sdk.min.js"
strategy="afterInteractive"
/>
</>
)
}
```
包含函数的配置(`signExternalId`、`getUserToken`)无法序列化,请手动挂载:
```tsx
useEffect(() => {
const script = document.createElement("script")
script.src = "https://support.example.com/sdk/agent-desk-sdk.min.js"
script.onload = () =>
window.AgentDeskWidget?.mount({
channelId,
externalId: user.id,
signExternalId: (id) => fetch(`/api/support/sign?id=${id}`).then((r) => r.json()),
})
document.body.appendChild(script)
return () => window.AgentDeskWidget?.destroy()
}, [channelId, user.id])
```
## 已识别访客
默认情况下访客是由浏览器本地 ID 标识的匿名游客。若要把聊天关联到你自己的用户,需要同时传入 `externalId` 和签名,防止他人冒充:
```
signature = hex(HMAC-SHA256(identitySecret, channelId + "\n" + externalId + "\n" + issuedAt))
```
`issuedAt` 为当前 Unix 时间(秒)。服务端只接受未超过 `identity.maxAgeMinutes` 的签名,因此每次打开聊天时挂件都会重新获取签名。
> [!IMPORTANT]
> 签名必须在**你的服务端**计算,身份密钥绝不能下发到浏览器。
Node.js 接口:
```js
import crypto from "node:crypto"
app.get("/api/support/sign", requireLogin, (req, res) => {
const issuedAt = Math.floor(Date.now() / 1000)
const payload = `${CHANNEL_ID}\n${req.user.id}\n${issuedAt}`
const signature = crypto
.createHmac("sha256", process.env.AGENT_DESK_IDENTITY_SECRET)
.update(payload)
.digest("hex")
res.json({ signature, issuedAt })
})
```
Go:
```go
func sign(secret, channelID, externalID string) (string, int64) {
issuedAt := time.Now().Unix()
mac := hmac.New(sha256.New, []byte(secret))
fmt.Fprintf(mac, "%s\n%s\n%d", channelID, externalID, issuedAt)
return hex.EncodeToString(mac.Sum(nil)), issuedAt
}
```
注意:接口签名的 `externalId` 应取自已登录用户,而不是查询参数。签名缺失或无效不会导致挂件失效,访客只会保持游客身份。
## 内容安全策略(CSP)与跨域
如果站点设置了 CSP,需要放行 Agent Desk 的域名:
```
script-src https://support.example.com;
frame-src https://support.example.com;
connect-src https://support.example.com;
```
挂件与 API 不同源时,请把你的站点加入部署的允许来源,否则配置请求会失败。
## 常见问题
| 现象 | 原因与解决 |
| --- | --- |
| 没有出现按钮 | 缺少 `channelId`,或 `AgentDeskConfig` 定义在 SDK 脚本之后。查看控制台是否有 `channelId is required`。 |
| 按钮出现但聊天空白 | iframe 被浏览器拦截。检查 CSP 的 `frame-src`,以及代理是否为 Agent Desk 域名设置了 `X-Frame-Options`。 |
| 访客始终显示为游客 | 签名缺失、已过期,或使用了错误的密钥或拼接顺序。请对照 `identity.maxAgeMinutes` 检查服务器时钟偏差。 |
| 语言不对 | 显式设置 `language`,否则使用服务端默认语言。 |
| 单页应用中挂件重复 | 组件卸载时调用 `AgentDeskWidget.destroy()`。 |
| 聊天显示“出了点问题” | 点击**重新加载聊天**。若仍然出现,请打开控制台并反馈 `Support chat crashed` 错误。 |