15 KiB
Встраивание виджета поддержки (Интеграция)
Виджет онлайн-чата AgentDesk добавляет кнопку вызова поддержки и интерактивный диалог на любую веб-страницу. Он загружается одним скриптом, отображает плавающую кнопку в углу экрана и открывает интерфейс чата внутри изолированного iframe. Посетители могут вести несколько обращений параллельно и переключаться между ними.
Быстрый старт
Замените https://support.example.com на URL вашего развёрнутого сервера AgentDesk, а YOUR_CHANNEL_ID — на идентификатор канала из панели управления (Каналы -> ID нужного канала).
<script>
window.AgentDeskConfig = {
channelId: "YOUR_CHANNEL_ID",
}
</script>
<script src="https://support.example.com/sdk/agent-desk-sdk.min.js" async></script>
Разместите оба тега перед закрывающим тегом </body>. Блок конфигурации window.AgentDeskConfig обязательно должен выполняться до загрузки скрипта SDK.
Tip
Заголовок, подзаголовок, основной цвет темы, позиция (слева/справа) и ширина виджета по умолчанию подтягиваются из настроек канала в панели управления, поэтому их необязательно прописывать в коде.
Идентификация пользователей (Авторизация клиентов)
По умолчанию любой посетитель считается анонимным гостем с браузерным идентификатором вида guest_xxxxxxxx.
Чтобы связать диалог с реальным пользователем вашего сайта, передавать его имя и историю обращений, платформа требует криптографического подтверждения личности. Это защищает от спуфинга: посетитель не может просто указать чужой userId в браузере и прочитать чужие конфиденциальные сообщения.
Поддерживаются два способа авторизации: JWT-токен (userToken) и HMAC-подпись (externalId).
Вариант 1: JWT-токен пользователя (userToken) — Рекомендуемый
В этом варианте ваш сервер выпускает подписанный JWT-токен при авторизации пользователя на вашем сайте.
1. Настройка секрета в панели управления
- Откройте панель администратора: Каналы -> выберите нужный канал (или создайте новый Web/Chat канал).
- Найдите поле User Token Secret (секретный ключ токена пользователя) и сохраните надёжную случайную строку (например, 32+ символа).
2. Полезная нагрузка токена (JWT Claims)
JWT подписывается алгоритмом HS256 (также поддерживаются HS384 и HS512) и должен содержать следующие поля:
{
"userId": "12345", // Уникальный ID пользователя в вашей системе (string)
"name": "Иван Иванов", // Отображаемое имя пользователя (string)
"exp": 1792100000 // Время истечения срока действия (Unix timestamp в секундах)
}
3. Генерация токена на вашем бэкенде
Node.js / Express:
import jwt from "jsonwebtoken";
app.get("/api/support/user-token", requireAuth, (req, res) => {
const token = jwt.sign(
{
userId: String(req.user.id),
name: req.user.fullName || req.user.email,
},
process.env.AGENT_DESK_CHANNEL_SECRET, // User Token Secret из настроек канала
{
algorithm: "HS256",
expiresIn: "24h",
}
);
res.json({ token });
});
Python (FastAPI / Flask + PyJWT):
import time
import jwt
@app.get("/api/support/user-token")
def get_support_user_token(current_user: User = Depends(get_current_user)):
payload = {
"userId": str(current_user.id),
"name": current_user.name,
"exp": int(time.time()) + 86400, # 24 часа
}
token = jwt.encode(payload, os.environ["AGENT_DESK_CHANNEL_SECRET"], algorithm="HS256")
return {"token": token}
PHP:
use Firebase\JWT\JWT;
$payload = [
'userId' => (string)$user->id,
'name' => $user->name,
'exp' => time() + 86400,
];
$token = JWT::encode($payload, getenv('AGENT_DESK_CHANNEL_SECRET'), 'HS256');
echo json_encode(['token' => $token]);
Go:
claims := jwt.MapClaims{
"userId": fmt.Sprintf("%d", user.ID),
"name": user.Name,
"exp": time.Now().Add(24 * time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, err := token.SignedString([]byte(channelSecret))
4. Передача в виджет на фронтенде
Передайте функцию getUserToken в конфигурацию виджета. SDK будет автоматически запрашивать свежий токен при открытии окна чата:
window.AgentDeskConfig = {
channelId: "YOUR_CHANNEL_ID",
getUserToken: async () => {
const res = await fetch("/api/support/user-token");
const data = await res.json();
return data.token;
},
};
Вариант 2: HMAC-SHA256 подпись (externalId + signExternalId)
Если вы предпочитаете не выпускать полный JWT, можно передавать ID пользователя и подписывать его через HMAC:
\text{signature} = \text{hex}\Big(\text{HMAC-SHA256}\big(\text{secret},\, \text{channelId} + \text{"\textbackslash n"} + \text{externalId} + \text{"\textbackslash n"} + \text{issuedAt}\big)\Big)
secret: общий ключidentity.secretиз файлаconfig.yaml(переменная окруженияIDENTITY_HMAC_SECRET).channelId: идентификатор канала в AgentDesk.externalId: ID пользователя на вашем сайте.issuedAt: текущее время Unix в секундах.
Important
Подпись должна вычисляться исключительно на вашем бэкенде. Никогда не передавайте
identity.secretв браузер.
Пример эндпоинта подписи (Node.js):
import crypto from "node:crypto";
app.get("/api/support/sign", requireAuth, (req, res) => {
const issuedAt = Math.floor(Date.now() / 1000);
const channelId = process.env.AGENT_DESK_CHANNEL_ID;
const externalId = String(req.user.id);
const payload = `${channelId}\n${externalId}\n${issuedAt}`;
const signature = crypto
.createHmac("sha256", process.env.AGENT_DESK_IDENTITY_SECRET)
.update(payload)
.digest("hex");
res.json({ signature, issuedAt });
});
Подключение в виджете:
window.AgentDeskConfig = {
channelId: "YOUR_CHANNEL_ID",
externalId: currentUserId,
externalName: currentUserName,
signExternalId: async (id) => {
const res = await fetch(`/api/support/sign?id=${encodeURIComponent(id)}`);
return await res.json(); // { signature: "...", issuedAt: 171... }
},
};
Примеры для популярных фронтенд-фреймворков
React / Next.js (App Router)
Поскольку функции getUserToken или signExternalId нельзя передать в JSON-строке скрипта, для них используется программное монтирование через AgentDeskWidget.mount:
"use client"
import { useEffect } from "react"
declare global {
interface Window {
AgentDeskWidget?: {
mount: (config: Record<string, unknown>) => void
destroy: () => void
open: () => Promise<void>
close: () => void
}
}
}
export function SupportChatWidget({
channelId,
user,
}: {
channelId: string
user?: { id: string; name: string } | null
}) {
useEffect(() => {
const script = document.createElement("script")
script.src = "https://support.example.com/sdk/agent-desk-sdk.min.js"
script.async = true
script.onload = () => {
window.AgentDeskWidget?.mount({
channelId,
language: "ru-RU",
getUserToken: user
? async () => {
const res = await fetch("/api/support/user-token")
const data = await res.json()
return data.token
}
: undefined,
})
}
document.body.appendChild(script)
return () => {
window.AgentDeskWidget?.destroy()
script.remove()
}
}, [channelId, user?.id])
return null
}
Vue 3 / Nuxt 3
<script setup>
import { onMounted, onUnmounted } from "vue"
const props = defineProps({
channelId: { type: String, required: true },
})
onMounted(() => {
const script = document.createElement("script")
script.src = "https://support.example.com/sdk/agent-desk-sdk.min.js"
script.async = true
script.onload = () => {
window.AgentDeskWidget?.mount({
channelId: props.channelId,
language: "ru-RU",
getUserToken: async () => {
const { data } = await useFetch("/api/support/user-token")
return data.value?.token
},
})
}
document.body.appendChild(script)
})
onUnmounted(() => {
window.AgentDeskWidget?.destroy()
})
</script>
Таблица всех параметров конфигурации
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
channelId |
string |
— | Обязательный. Идентификатор канала из панели управления. |
baseUrl |
string |
домен скрипта | Базовый URL сервера AgentDesk. |
apiBaseUrl |
string |
baseUrl |
URL для запросов API (если API вынесен на отдельный домен). |
widgetBaseUrl |
string |
домен скрипта | URL, с которого загружается страница фрейма чата. |
language |
"ru-RU" | "en-US" | "zh-CN" |
язык сервера | Язык интерфейса виджета чата. |
position |
"left" | "right" |
из настроек канала | Сторона экрана, где находится кнопка и всплывающее окно. |
themeColor |
string |
из настроек канала | Основной цвет темы (HEX, например #2563eb). |
width |
string |
380px |
Ширина окна чата на экранах десктопа (например 400px). |
title |
string |
из настроек канала | Заголовок окна поддержки в шапке. |
subtitle |
string |
из настроек канала | Подзаголовок окна поддержки. |
externalId |
string |
браузерный ID | Уникальный ID вашего пользователя. |
externalName |
string |
— | Имя пользователя (используется при создании профиля). |
signExternalId |
(id) => Promise<{signature, issuedAt}> |
— | Функция вычисления HMAC-подписи для externalId. |
getUserToken |
() => Promise<string> |
— | Функция получения JWT-токена пользователя. |
JavaScript API виджета
После загрузки SDK в объекте window.AgentDeskWidget доступны методы управления:
// Открыть окно чата поддержки
await AgentDeskWidget.open();
// Свернуть окно чата
AgentDeskWidget.close();
// Смонтировать виджет с явной конфигурацией
AgentDeskWidget.mount({ channelId: "..." });
// Полностью удалить кнопку и фрейм чата из DOM
AgentDeskWidget.destroy();
// Получить прямую ссылку на чат (для открытия в новой вкладке или перехода)
const chatUrl = await AgentDeskWidget.getChatUrl();
Пример создания собственной кнопки вызова поддержки в шапке вашего сайта:
<button type="button" onclick="AgentDeskWidget.open()">
Написать в поддержку
</button>
Content Security Policy (CSP) и CORS
Если на вашем сайте настроена строгая политика безопасности Content Security Policy (CSP), добавьте домен AgentDesk в разрешённые источники:
script-src https://support.example.com;
frame-src https://support.example.com;
connect-src https://support.example.com;
Диагностика частых проблем
| Проблема | Причина и способ устранения |
|---|---|
| Пользователь отображается как «Гость...» | 1. Проверьте, что передаётся либо валидный userToken, либо externalId + signExternalId.2. Проверьте секретный ключ канала User Token Secret.3. Убедитесь, что серверное время на вашем сервере не расходится с сервером AgentDesk (срок действия подписи/токена). |
| Кнопка виджета не появляется | В консоли браузера проверьте ошибку: чаще всего не указан channelId или скрипт SDK загрузился раньше, чем объявлен window.AgentDeskConfig. |
| Кнопка есть, но внутри окна чата белый экран | Браузер блокирует загрузку iframe. Проверьте заголовки X-Frame-Options и Content-Security-Policy (frame-src) на вашем прокси-сервере. |
| Язык виджета не совпадает с сайтом | Задайте параметр language: "ru-RU" (или en-US) явно в конфигурации виджета. |
| Дублирование виджета в SPA-приложениях | При смене маршрута или размонтировании вызывайте AgentDeskWidget.destroy(). |