Help Center Интеграция на Widget

Интеграция на 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>

Параметри на скрипта

АтрибутСтойностЗадължителенОписание
typemoduleДаТип на ES модула
idoperator-chat-panel-scriptДаУникален идентификатор на скрипта
srcWidget URLДаМестоположение на скрипта за уиджета
tokenJWT 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:[email protected]">[email protected]</a></p>
    </div>
  `;
  document.body.appendChild(fallback);
}

Отстраняване на проблеми

Уиджетът не се зарежда

  1. Проверете дали operator token е валиден
  2. Уверете се, че токенът не е изтекъл
  3. Уверете се, че URL адресът на скрипта е правилен
  4. Проверете конзолата на браузъра за грешки
  5. Проверете мрежовата свързаност

Токенът е изтекъл

// Откриване на изтекъл токен и опресняване
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');
    }
  });
});

Следващи стъпки