Интеграция на Widget
Интегрирайте чат уиджет (приставка) ChatHub във вашия уебсайт, за да осигурите поддръжка чрез чат в реално време за вашите клиенти.
Преглед
Уиджетът ChatHub е JavaScript компонент, който:
- Се вгражда във всеки уебсайт
- Предоставя интерфейс за чат в реално време
- Свързва клиенти с оператори
- Изисква токен за удостоверяване на оператор
- Се зарежда като ES модул
Бърз старт
1. Вземете Operator Token
Първо, вземете токен на оператор, следвайки работния процес на автентикация:
// 1. Вземане на company token
const companyToken = await getCompanyToken(login, password);
// 2. Вземане на организация
const organizations = await getOrganizations(companyToken);
const orgId = organizations[0].id;
// 3. Вземане на оператор
const operators = await getOperators(companyToken, orgId);
const operatorId = operators[0].id;
// 4. Генериране на operator token
const operatorToken = await getOperatorToken(
companyToken,
operatorId,
expiresAt
);
// 5. Валидиране на токена
const isValid = await validateToken(companyToken, operatorToken);
2. Вградете уиджета
Добавете скрипта за уиджета към вашия HTML:
<!DOCTYPE html>
<html>
<head>
<title>Вашият Уебсайт</title>
</head>
<body>
<!-- Съдържание на вашия уебсайт -->
<!-- ChatHub Widget -->
<script type="module" id="operator-chat-panel-script"
src="https://widget.smsbat.com/operator-chat-panel/widget-script.js"
token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."></script>
</body>
</html>
Параметри на скрипта
| Атрибут | Стойност | Задължителен | Описание |
|---|---|---|---|
type |
module |
Да | Тип на ES модула |
id |
operator-chat-panel-script |
Да | Уникален идентификатор на скрипта |
src |
Widget URL | Да | Местоположение на скрипта за уиджета |
token |
JWT token | Да | Токен за удостоверяване на оператор |
Методи за интеграция
Статичен HTML
За статични уебсайтове вградете директно в HTML:
<!DOCTYPE html>
<html lang="bg">
<head>
<meta charset="UTF-8">
<title>Моят Уебсайт</title>
</head>
<body>
<h1>Добре дошли в Моя Уебсайт</h1>
<!-- ChatHub Widget -->
<script type="module" id="operator-chat-panel-script"
src="https://widget.smsbat.com/operator-chat-panel/widget-script.js"
token="ВАШИЯТ_OPERATOR_TOKEN"></script>
</body>
</html>
Динамично инжектиране (JavaScript)
За приложения с една страница (Single-page applications), инжектирайте динамично:
function loadChatHubWidget(operatorToken) {
// Проверете дали уиджетът вече е зареден
const existing = document.getElementById('operator-chat-panel-script');
if (existing) {
existing.remove();
}
// Създаване на елемент script
const script = document.createElement('script');
script.type = 'module';
script.id = 'operator-chat-panel-script';
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', operatorToken);
// Добавяне към тялото (body)
document.body.appendChild(script);
}
// Употреба
const token = await getOperatorToken();
loadChatHubWidget(token);
React
import { useEffect } from 'react';
function ChatHubWidget({ operatorToken }) {
useEffect(() => {
if (!operatorToken) return;
// Зареждане на уиджета
const script = document.createElement('script');
script.type = 'module';
script.id = 'operator-chat-panel-script';
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', operatorToken);
document.body.appendChild(script);
// Почистване при демонтиране (unmount)
return () => {
const existing = document.getElementById('operator-chat-panel-script');
if (existing) {
existing.remove();
}
};
}, [operatorToken]);
return null;
}
// Употреба
function App() {
const [token, setToken] = useState('');
useEffect(() => {
async function init() {
const operatorToken = await fetchOperatorToken();
setToken(operatorToken);
}
init();
}, []);
return (
<div>
<h1>Моето Приложение</h1>
<ChatHubWidget operatorToken={token} />
</div>
);
}
Vue.js
<template>
<div id="app">
<h1>Моето Приложение</h1>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
operatorToken: ''
};
},
async mounted() {
// Вземане на operator token
this.operatorToken = await this.fetchOperatorToken();
// Зареждане на уиджет
this.loadWidget();
},
methods: {
async fetchOperatorToken() {
// Вашата логика за извличане на токен
const response = await fetch('/api/chathub/token');
return response.text();
},
loadWidget() {
if (!this.operatorToken) return;
const script = document.createElement('script');
script.type = 'module';
script.id = 'operator-chat-panel-script';
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', this.operatorToken);
document.body.appendChild(script);
}
},
beforeUnmount() {
const script = document.getElementById('operator-chat-panel-script');
if (script) {
script.remove();
}
}
};
</script>
Angular
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-root',
template: '<h1>Моето Приложение</h1>'
})
export class AppComponent implements OnInit, OnDestroy {
private operatorToken: string = '';
async ngOnInit() {
// Вземане на operator token
this.operatorToken = await this.fetchOperatorToken();
// Зареждане на уиджета
this.loadWidget();
}
ngOnDestroy() {
const script = document.getElementById('operator-chat-panel-script');
if (script) {
script.remove();
}
}
private async fetchOperatorToken(): Promise<string> {
const response = await fetch('/api/chathub/token');
return response.text();
}
private loadWidget() {
if (!this.operatorToken) return;
const script = document.createElement('script');
script.type = 'module';
script.id = 'operator-chat-panel-script';
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', this.operatorToken);
document.body.appendChild(script);
}
}
Управление на токени
server-side Генериране на токени
Никога не излагайте идентификационни данни на компанията в кода на клиента. Генерирайте токени на вашия сървър:
// Node.js Express пример
const express = require('express');
const app = express();
app.get('/api/chathub/token', async (req, res) => {
try {
// Първо удостоверете вашия потребител
const userId = req.session.userId;
if (!userId) {
return res.status(401).json({ error: 'Неупълномощен' });
}
// Вземете токена на компанията (съхранява се сигурно на сървъра)
const companyToken = process.env.CHATHUB_COMPANY_TOKEN;
// Вземете ID на оператор за този потребител
const operatorId = await getOperatorIdForUser(userId);
// Генерирайте operator token
const operatorToken = await generateOperatorToken(
companyToken,
operatorId
);
res.json({ token: operatorToken });
} catch (error) {
res.status(500).json({ error: 'Неуспешно генериране на токен' });
}
});
async function generateOperatorToken(companyToken, operatorId) {
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 часа
const response = await fetch(
'https://chatapi.smsbat.com/api/operator/get-token',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${companyToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: operatorId,
expiresAt: expiresAt.toISOString()
})
}
);
return response.text();
}
Опресняване на токен
Приложете автоматично опресняване на токена:
class WidgetTokenManager {
constructor() {
this.token = null;
this.expiresAt = null;
this.refreshInterval = null;
}
async initialize() {
await this.refreshToken();
// Опресняване на токена 1 час преди изтичането му
this.refreshInterval = setInterval(
() => this.checkAndRefresh(),
60 * 60 * 1000 // Проверка на всеки час
);
}
async refreshToken() {
const response = await fetch('/api/chathub/token');
const data = await response.json();
this.token = data.token;
this.expiresAt = new Date(data.expiresAt);
this.reloadWidget();
}
async checkAndRefresh() {
const oneHour = 60 * 60 * 1000;
const timeUntilExpiry = this.expiresAt - Date.now();
if (timeUntilExpiry < oneHour) {
await this.refreshToken();
}
}
reloadWidget() {
// Премахване на стария уиджет
const existing = document.getElementById('operator-chat-panel-script');
if (existing) {
existing.remove();
}
// Зареждане на новия уиджет със свеж токен
const script = document.createElement('script');
script.type = 'module';
script.id = 'operator-chat-panel-script';
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', this.token);
document.body.appendChild(script);
}
destroy() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
}
// Употреба
const widgetManager = new WidgetTokenManager();
await widgetManager.initialize();
Множество организации
Зареждайте различни уиджети за различни организации:
function loadWidgetForOrganization(organizationId) {
return new Promise((resolve, reject) => {
// Вземане на оператор за тази организация
fetch(`/api/chathub/token?org=${organizationId}`)
.then(response => response.json())
.then(data => {
const script = document.createElement('script');
script.type = 'module';
script.id = `operator-chat-panel-script-${organizationId}`;
script.src = 'https://widget.smsbat.com/operator-chat-panel/widget-script.js';
script.setAttribute('token', data.token);
script.onload = () => resolve();
script.onerror = () => reject(new Error('Неуспешно зареждане на уиджет'));
document.body.appendChild(script);
})
.catch(reject);
});
}
// Употреба
await loadWidgetForOrganization('sales');
await loadWidgetForOrganization('support');
Добри практики
Сигурност
- ✅ Генериране на токени сървърно
- ✅ Никога не излагайте идентификационните данни на компанията в клиентския код
- ✅ Използвайте HTTPS за всички API заявки
- ✅ Внедрете изтичане на токените
- ✅ Валидирайте токените преди употреба
- ❌ Не съхранявайте токени в localStorage без криптиране
- ❌ Не добавяйте токени в системата за контрол на версиите (Git)
Производителност
- ✅ Зареждайте уиджета асинхронно
- ✅ Използвайте ES модули (модерни браузъри)
- ✅ Внедрете кеширане на токени
- ✅ Обработвайте грешки грациозно
- ❌ Не блокирайте зареждането на страницата
Потребителско изживяване (UX)
- ✅ Показвайте състояние на зареждане, докато уиджетът инициализира
- ✅ Обработвайте мрежови грешки
- ✅ Предоставете резервен метод (fallback) за контакт
- ✅ Тествайте на различни браузъри и устройства
Обработка на грешки
async function loadWidgetSafely(operatorToken) {
try {
// Валидирайте токена първо
const isValid = await validateToken(operatorToken);
if (!isValid) {
console.error('Невалиден operator token');
showFallbackContact();
return;
}
// Зареждане на уиджета
await loadWidget(operatorToken);
} catch (error) {
console.error('Неуспешно зареждане на чат уиджет:', error);
showFallbackContact();
}
}
function showFallbackContact() {
// Показване на алтернативен метод за контакт
const fallback = document.createElement('div');
fallback.innerHTML = `
<div class="chat-fallback">
<p>Чатът е временно недостъпен.</p>
<p>Свържете се с нас: <a href="mailto:support@example.com">support@example.com</a></p>
</div>
`;
document.body.appendChild(fallback);
}
Отстраняване на проблеми
Уиджетът не се зарежда
- Проверете дали operator token е валиден
- Уверете се, че токенът не е изтекъл
- Уверете се, че URL адресът на скрипта е правилен
- Проверете конзолата на браузъра за грешки
- Проверете мрежовата свързаност
Токенът е изтекъл
// Откриване на изтекъл токен и опресняване
window.addEventListener('error', async (event) => {
if (event.message.includes('token expired')) {
console.log('Токенът е изтекъл, опресняване...');
await refreshWidgetToken();
}
});
Множество инстанции на уиджета
Уверете се, че се зарежда само един уиджет в даден момент:
function loadWidgetOnce(token) {
// Премахване на всякакви съществуващи уиджети
const existingScripts = document.querySelectorAll(
'script[id^="operator-chat-panel-script"]'
);
existingScripts.forEach(script => script.remove());
// Зареждане на нов уиджет
loadWidget(token);
}
Проблеми с различни източници (Cross-Origin)
Уверете се, че вашият домейн е разрешен (whitelisted). Свържете се с поддръжката, ако срещнете CORS грешки.
Тестване
Локално разработване
// Използвайте тестов токен за разработка
const isDevelopment = process.env.NODE_ENV === 'development';
const token = isDevelopment
? 'test-token-for-development'
: await getProductionToken();
loadWidget(token);
Интеграционно тестване
describe('ChatHub Widget', () => {
it('трябва да зареди уиджета с валиден токен', async () => {
const token = await getTestToken();
loadWidget(token);
await waitFor(() => {
const widget = document.getElementById('operator-chat-panel-script');
expect(widget).toBeTruthy();
});
});
it('трябва да обработи невалиден токен', async () => {
const invalidToken = 'invalid-token';
try {
await loadWidget(invalidToken);
} catch (error) {
expect(error.message).toContain('Invalid token');
}
});
});
Следващи стъпки
- Автентикация - Управление на токени
- Оператори - Настройка на оператори
- Организации - Конфигуриране на организации