Перейти к основному содержимому

Виджет на сайте

Чат на сайте организации подключается одним скриптом. Скрипт, окно чата и Web SDK раздаёт сама установка, внешний CDN не нужен. В примерах адрес установки для клиентов — https://support.example.com, ключ размещения — pk_… (его показывает Интеграции → Виджет на сайт организации).

Подключение​

<script src="https://support.example.com/widget/loader.js" data-placement="pk_…" async></script>

Скрипт (около 3 КБ) ставит кнопку чата и открывает окно в изолированном iframe. С сайтом окно общается только сообщениями с проверкой origin и источника. CSS и скрипты сайта в окно не проникают.

Тот же виджет программно:

const chat = window.ShtabHelpdesk.init({
placement: 'pk_…',
widgetOrigin: 'https://support.example.com',
})

chat.open()

init возвращает { open, close, identify, logout, setTheme, navigated, destroy }.

Домен сайта​

Размещение работает только на подтверждённых доменах: origin сайта доказывается TXT-записью DNS, запись и её значение показывает настройка размещения. Запросы с неподтверждённого origin API отклоняет.

Вошедший клиент​

Гость пишет без входа: сессия держится на cookie браузера. Клиент, вошедший в личный кабинет сайта, получает свою историю обращений по chat token: его подписывает бэкенд сайта, а виджет передаёт identify(token).

ПолеЗначение
issиздатель chat token, которого заводит администратор организации: адрес, вид подписи, секрет или ключи
audключ размещения
subпостоянный id клиента в системе сайта: по нему находятся его обращения
nameимя, которое увидит оператор (необязательно)
iat, expвремя выпуска и окончания, exp − iat не больше 600 секунд
jtiуникальная строка: токен действует один раз

Подпись — HS256 общим секретом или RS256/ES256 ключом издателя. Токен выпускает только бэкенд: секрет никогда не попадает в браузер. Выход из кабинета — logout().

Тот же sub с пространством token:<издатель> — клиент интеграционного API: обращение, начатое системой через API, клиент увидит в виджете.

Точка входа​

Скрипт сообщает окну страницу сайта (origin и путь, без query и fragment) при подключении, открытии окна и повторном init. Новое обращение берёт её в «Точку входа» для оператора. Одностраничное приложение, которое не вызывает init при переходах, вызывает chat.navigated() само.

Свои стили​

Вид окна меняют два слоя:

  1. CSS размещения (Интеграции → Виджет на сайт → Свой CSS) применяется внутри окна поверх его стилей. Разметки и @import нет, загрузить что-то извне не даст политика безопасности окна.
  2. Тема сайта — init({ theme }) и setTheme(theme): значения переменных --shtab-* и scheme: 'light' | 'dark' поверх значений CSS размещения.
ПеременнаяЧто красит
--shtab-accentосновной цвет: кнопки, ссылки, кнопка чата на сайте
--shtab-background, --shtab-textфон и текст окна
--shtab-font-familyшрифт окна
--shtab-radiusскругление окна
--shtab-header-background, --shtab-header-textшапка
--shtab-bubble-customer, --shtab-bubble-customer-textсообщения клиента
--shtab-bubble-operator, --shtab-bubble-operator-textсообщения сотрудников и бота

Классы для CSS размещения: .shtab-widget (окно), .shtab-widget__header, __title, __subtitle, __bubble (с --customer и --operator), __composer. Переменные и классы — контракт: они меняются с предупреждением за 30 дней. Тему сайта проверяет мост: только эти переменные, без url(), правил и разметки.

ShtabHelpdesk.init({
placement: 'pk_…',
widgetOrigin: 'https://support.example.com',
theme: { vars: { '--shtab-accent': '#7c3aed', '--shtab-radius': '24px' }, scheme: 'dark' },
})

Свой интерфейс: headless Web SDK​

/widget/sdk/helpdesk-sdk.js (ESM) — тот же клиент, на котором написан виджет. Сайт рисует чат сам:

import { HelpdeskClient } from 'https://support.example.com/widget/sdk/helpdesk-sdk.js'

const client = new HelpdeskClient({
apiBaseUrl: 'https://support.example.com',
realtimeUrl: 'wss://support.example.com/connection/websocket',
placementKey: 'pk_…',
})

await client.config() // окно размещения: язык, тексты, согласие
await client.refresh().catch(() => client.startGuest()) // гость: сессия по cookie или новая
// await client.identify(chatTokenFromYourBackend) // вошедший клиент
client.connect() // realtime

const { conversation } = await client.startConversation('Тема', { text: 'Здравствуйте!' })
let older = null

const stream = client.openConversation(conversation.id, {
onMessages: (messages) => render(messages), // история и realtime без потерь и дублей
onSnapshot: ({ nextCursor }) => {
older = nextCursor // курсор страницы перед загруженной
},
onTyping: ({ authorName }) => showTyping(authorName), // «печатает…» сотрудника
})

// Прокрутили к началу: страница по 50 перед загруженными, null — начало истории.
if (older) {
;({ nextCursor: older } = await client.olderHistory(conversation.id, older))
}

composer.addEventListener('input', () => stream.typing()) // «печатает…» клиента, не чаще раза в 3 с
await client.sendMessage(conversation.id, { text: 'Ещё вопрос' }) // офлайн дождётся сети
stream.close()
await client.logout()

Требования те же, что у виджета: подтверждённый домен сайта и chat token от бэкенда для вошедшего клиента.

Мобильные приложения​

В приложениях iOS и Android тот же чат открывают мобильные SDK.