Μετάβαση στο περιεχόμενο

Ενσωμάτωση widget

Ενσωματώστε το γραφικό στοιχείο ChatHub στον ιστότοπό σας για να παρέχετε υποστήριξη συνομιλίας σε πραγματικό χρόνο στους πελάτες σας.

Επισκόπηση

Το γραφικό στοιχείο ChatHub είναι ένα στοιχείο JavaScript που:

  • Ενσωματώνεται σε οποιονδήποτε ιστότοπο
  • Παρέχει διεπαφή συνομιλίας σε πραγματικό χρόνο
  • Συνδέει τους πελάτες με τους φορείς εκμετάλλευσης
  • Απαιτεί διακριτικό ελέγχου ταυτότητας χειριστή
  • Φορτώνεται ως μονάδα ES

Γρήγορη εκκίνηση

1. Λάβετε το διακριτικό χειριστή

Αρχικά, αποκτήστε ένα διακριτικό χειριστή ακολουθώντας τη ροή ελέγχου ταυτότητας:

// 1. Get company token
const companyToken = await getCompanyToken(login, password);

// 2. Get organization
const organizations = await getOrganizations(companyToken);
const orgId = organizations[0].id;

// 3. Get operator
const operators = await getOperators(companyToken, orgId);
const operatorId = operators[0].id;

// 4. Generate operator token
const operatorToken = await getOperatorToken(
  companyToken,
  operatorId,
  expiresAt
);

// 5. Validate token
const isValid = await validateToken(companyToken, operatorToken);

2. Ενσωμάτωση γραφικού στοιχείου

Προσθέστε το σενάριο του γραφικού στοιχείου στο HTML σας:

<!DOCTYPE html>
<html>
<head>
    <title>Your Website</title>
</head>
<body>
    <!-- Your website content -->

    <!-- 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>

Παράμετροι σεναρίου

Ιδιότητα Αξία Απαιτείται Περιγραφή
«τύπος» «ενότητα» Ναι Τύπος μονάδας ES
id operator-chat-panel-script Ναι Μοναδικό αναγνωριστικό σεναρίου
src Διεύθυνση URL γραφικού στοιχείου Ναι Θέση σεναρίου widget
«κουπόνι» Token JWT Ναι Διακριτικό ελέγχου ταυτότητας χειριστή

Μέθοδοι ολοκλήρωσης

Στατικό HTML

Για στατικούς ιστότοπους, ενσωματώστε απευθείας σε HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Website</title>
</head>
<body>
    <h1>Welcome to My Website</h1>

    <!-- ChatHub Widget -->
    <script type="module" id="operator-chat-panel-script"
      src="https://widget.smsbat.com/operator-chat-panel/widget-script.js"
      token="YOUR_OPERATOR_TOKEN"></script>
</body>
</html>

Dynamic Injection (JavaScript)

Για εφαρμογές μιας σελίδας, εισάγετε δυναμικά:

function loadChatHubWidget(operatorToken) {
  // Check if widget already loaded
  const existing = document.getElementById('operator-chat-panel-script');
  if (existing) {
    existing.remove();
  }

  // Create script element
  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);

  // Append to body
  document.body.appendChild(script);
}

// Usage
const token = await getOperatorToken();
loadChatHubWidget(token);

Αντίδρασε

import { useEffect } from 'react';

function ChatHubWidget({ operatorToken }) {
  useEffect(() => {
    if (!operatorToken) return;

    // Load widget
    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);

    // Cleanup on unmount
    return () => {
      const existing = document.getElementById('operator-chat-panel-script');
      if (existing) {
        existing.remove();
      }
    };
  }, [operatorToken]);

  return null;
}

// Usage
function App() {
  const [token, setToken] = useState('');

  useEffect(() => {
    async function init() {
      const operatorToken = await fetchOperatorToken();
      setToken(operatorToken);
    }
    init();
  }, []);

  return (
    <div>
      <h1>My App</h1>
      <ChatHubWidget operatorToken={token} />
    </div>
  );
}

Vue.js

<template>
  <div id="app">
    <h1>My App</h1>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      operatorToken: ''
    };
  },
  async mounted() {
    // Get operator token
    this.operatorToken = await this.fetchOperatorToken();

    // Load widget
    this.loadWidget();
  },
  methods: {
    async fetchOperatorToken() {
      // Your token fetching logic
      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>

Γωνιακό

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<h1>My App</h1>'
})
export class AppComponent implements OnInit, OnDestroy {
  private operatorToken: string = '';

  async ngOnInit() {
    // Get operator token
    this.operatorToken = await this.fetchOperatorToken();

    // Load widget
    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);
  }
}

Διαχείριση διακριτικών

Δημιουργία διακριτικών από την πλευρά του διακομιστή

Μην εκθέτετε ποτέ τα διαπιστευτήρια της εταιρείας σε κώδικα από την πλευρά του πελάτη. Δημιουργήστε διακριτικά στον διακομιστή σας:

// Node.js Express example
const express = require('express');
const app = express();

app.get('/api/chathub/token', async (req, res) => {
  try {
    // Authenticate your user first
    const userId = req.session.userId;
    if (!userId) {
      return res.status(401).json({ error: 'Unauthorized' });
    }

    // Get company token (stored securely on server)
    const companyToken = process.env.CHATHUB_COMPANY_TOKEN;

    // Get operator ID for this user
    const operatorId = await getOperatorIdForUser(userId);

    // Generate operator token
    const operatorToken = await generateOperatorToken(
      companyToken,
      operatorId
    );

    res.json({ token: operatorToken });
  } catch (error) {
    res.status(500).json({ error: 'Failed to generate token' });
  }
});

async function generateOperatorToken(companyToken, operatorId) {
  const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours

  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();

    // Refresh token 1 hour before expiration
    this.refreshInterval = setInterval(
      () => this.checkAndRefresh(),
      60 * 60 * 1000 // Check every hour
    );
  }

  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() {
    // Remove old widget
    const existing = document.getElementById('operator-chat-panel-script');
    if (existing) {
      existing.remove();
    }

    // Load new widget with fresh token
    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);
    }
  }
}

// Usage
const widgetManager = new WidgetTokenManager();
await widgetManager.initialize();

Πολλαπλοί Οργανισμοί

Φόρτωση διαφορετικών γραφικών στοιχείων για διαφορετικούς οργανισμούς:

function loadWidgetForOrganization(organizationId) {
  return new Promise((resolve, reject) => {
    // Get operator for this organization
    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('Failed to load widget'));

        document.body.appendChild(script);
      })
      .catch(reject);
  });
}

// Usage
await loadWidgetForOrganization('sales');
await loadWidgetForOrganization('support');

Βέλτιστες πρακτικές

Ασφάλεια

  • ✅ Δημιουργία διακριτικών από την πλευρά του διακομιστή
  • ✅ Μην εκθέτετε ποτέ τα διαπιστευτήρια της εταιρείας στον κωδικό πελάτη
  • ✅ Χρησιμοποιήστε HTTPS για όλα τα αιτήματα API
  • ✅ Εφαρμογή λήξης διακριτικού
  • ✅ Επικυρώστε τα κουπόνια πριν από τη χρήση
  • ❌ Μην αποθηκεύετε διακριτικά στο localStorage χωρίς κρυπτογράφηση
  • ❌ Μην δεσμεύετε διακριτικά στον έλεγχο έκδοσης

Απόδοση

  • ✅ Φόρτωση γραφικού στοιχείου ασύγχρονα
  • ✅ Χρησιμοποιήστε μονάδες ES (σύγχρονα προγράμματα περιήγησης)
  • ✅ Εφαρμογή προσωρινής αποθήκευσης διακριτικών
  • ✅ Χειριστείτε τα λάθη με χάρη
  • ❌ Μην αποκλείετε τη φόρτωση σελίδας

Εμπειρία χρήστη

  • ✅ Εμφάνιση κατάστασης φόρτωσης κατά την προετοιμασία του γραφικού στοιχείου
  • ✅ Χειριστείτε σφάλματα δικτύου
  • ✅ Παρέχετε εναλλακτική μέθοδο επικοινωνίας
  • ✅ Δοκιμή σε διαφορετικά προγράμματα περιήγησης και συσκευές

Χειρισμός σφαλμάτων

async function loadWidgetSafely(operatorToken) {
  try {
    // Validate token first
    const isValid = await validateToken(operatorToken);

    if (!isValid) {
      console.error('Invalid operator token');
      showFallbackContact();
      return;
    }

    // Load widget
    await loadWidget(operatorToken);

  } catch (error) {
    console.error('Failed to load chat widget:', error);
    showFallbackContact();
  }
}

function showFallbackContact() {
  // Show alternative contact method
  const fallback = document.createElement('div');
  fallback.innerHTML = `
    <div class="chat-fallback">
      <p>Chat is temporarily unavailable.</p>
      <p>Contact us: <a href="mailto:support@example.com">support@example.com</a></p>
    </div>
  `;
  document.body.appendChild(fallback);
}

Αντιμετώπιση προβλημάτων

Το γραφικό στοιχείο δεν φορτώνεται

  1. Ελέγξτε ότι το διακριτικό χειριστή είναι έγκυρο
  2. Βεβαιωθείτε ότι το διακριτικό δεν έχει λήξει
  3. Βεβαιωθείτε ότι η διεύθυνση URL του σεναρίου είναι σωστή
  4. Ελέγξτε την κονσόλα του προγράμματος περιήγησης για σφάλματα
  5. Επαληθεύστε τη συνδεσιμότητα δικτύου

Token έληξε

// Detect expired token and refresh
window.addEventListener('error', async (event) => {
  if (event.message.includes('token expired')) {
    console.log('Token expired, refreshing...');
    await refreshWidgetToken();
  }
});

Πολλαπλές παρουσίες γραφικών στοιχείων

Βεβαιωθείτε ότι φορτώνει μόνο ένα γραφικό στοιχείο κάθε φορά:

function loadWidgetOnce(token) {
  // Remove any existing widgets
  const existingScripts = document.querySelectorAll(
    'script[id^="operator-chat-panel-script"]'
  );

  existingScripts.forEach(script => script.remove());

  // Load new widget
  loadWidget(token);
}

Ζητήματα πολλαπλής προέλευσης

Βεβαιωθείτε ότι ο τομέας σας είναι στη λίστα επιτρεπόμενων. Επικοινωνήστε με την υποστήριξη εάν αντιμετωπίσετε σφάλματα CORS.

Δοκιμή

Τοπική Ανάπτυξη

// Use test token for development
const isDevelopment = process.env.NODE_ENV === 'development';
const token = isDevelopment
  ? 'test-token-for-development'
  : await getProductionToken();

loadWidget(token);

Έλεγχος ενσωμάτωσης

describe('ChatHub Widget', () => {
  it('should load widget with valid token', async () => {
    const token = await getTestToken();
    loadWidget(token);

    await waitFor(() => {
      const widget = document.getElementById('operator-chat-panel-script');
      expect(widget).toBeTruthy();
    });
  });

  it('should handle invalid token', async () => {
    const invalidToken = 'invalid-token';

    try {
      await loadWidget(invalidToken);
    } catch (error) {
      expect(error.message).toContain('Invalid token');
    }
  });
});

Επόμενα βήματα