769 lines
26 KiB
HTML
769 lines
26 KiB
HTML
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
||
|
|
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>ReplyPier Widget Test</title>
|
||
|
|
<style>
|
||
|
|
* {
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
|
|
max-width: 800px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 20px;
|
||
|
|
line-height: 1.6;
|
||
|
|
background: #ffffff;
|
||
|
|
color: #333;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
color: #333;
|
||
|
|
border-bottom: 2px solid #f34611;
|
||
|
|
padding-bottom: 10px;
|
||
|
|
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||
|
|
margin: 0 0 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
h2 {
|
||
|
|
color: #444;
|
||
|
|
font-size: clamp(1.25rem, 3vw, 1.5rem);
|
||
|
|
margin: 20px 0 15px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content {
|
||
|
|
background: #f9f9f9;
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 8px;
|
||
|
|
margin: 20px 0;
|
||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
code {
|
||
|
|
background: #f5f5f5;
|
||
|
|
padding: 2px 6px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-family: 'Courier New', Courier, monospace;
|
||
|
|
font-size: 0.9em;
|
||
|
|
word-break: break-word;
|
||
|
|
}
|
||
|
|
|
||
|
|
pre {
|
||
|
|
overflow-x: auto;
|
||
|
|
background: #f5f5f5;
|
||
|
|
padding: 15px;
|
||
|
|
border-radius: 4px;
|
||
|
|
margin: 15px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pre code {
|
||
|
|
background: none;
|
||
|
|
padding: 0;
|
||
|
|
font-size: 0.85em;
|
||
|
|
white-space: pre;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-section {
|
||
|
|
margin: 40px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-controls {
|
||
|
|
display: flex;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 10px;
|
||
|
|
align-items: center;
|
||
|
|
margin-top: 15px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input {
|
||
|
|
flex: 1;
|
||
|
|
min-width: 200px;
|
||
|
|
padding: 12px;
|
||
|
|
border-radius: 4px;
|
||
|
|
border: 1px solid #ccc;
|
||
|
|
font-size: 16px;
|
||
|
|
transition: border-color 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input:focus {
|
||
|
|
outline: none;
|
||
|
|
border-color: #f34611;
|
||
|
|
box-shadow: 0 0 0 2px rgba(243, 70, 17, 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button {
|
||
|
|
background: #f34611;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
padding: 12px 20px;
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
font-size: 16px;
|
||
|
|
font-weight: 500;
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
white-space: nowrap;
|
||
|
|
min-height: 44px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button:hover {
|
||
|
|
background: #d93d0c;
|
||
|
|
transform: translateY(-1px);
|
||
|
|
box-shadow: 0 2px 8px rgba(243, 70, 17, 0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button:active {
|
||
|
|
transform: translateY(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button.clear-button {
|
||
|
|
background: #dc3545;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button.clear-button:hover {
|
||
|
|
background: #c82333;
|
||
|
|
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button.auth-button {
|
||
|
|
background: #28a745;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button.auth-button:hover {
|
||
|
|
background: #218838;
|
||
|
|
box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section {
|
||
|
|
margin-top: 30px;
|
||
|
|
padding-top: 20px;
|
||
|
|
border-top: 1px solid #ddd;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section h3 {
|
||
|
|
font-size: 1.1rem;
|
||
|
|
color: #555;
|
||
|
|
margin: 0 0 10px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section p {
|
||
|
|
font-size: 0.9rem;
|
||
|
|
color: #666;
|
||
|
|
margin: 0 0 15px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status {
|
||
|
|
font-size: 0.9rem;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border-radius: 4px;
|
||
|
|
display: inline-block;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.success {
|
||
|
|
background: #d4edda;
|
||
|
|
color: #155724;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.error {
|
||
|
|
background: #f8d7da;
|
||
|
|
color: #721c24;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.loading {
|
||
|
|
background: #fff3cd;
|
||
|
|
color: #856404;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Responsive styles */
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
body {
|
||
|
|
padding: 15px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content {
|
||
|
|
padding: 15px;
|
||
|
|
margin: 15px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-section {
|
||
|
|
margin: 30px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-controls {
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: stretch;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input {
|
||
|
|
min-width: 100%;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button {
|
||
|
|
width: 100%;
|
||
|
|
margin: 5px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
pre {
|
||
|
|
padding: 10px;
|
||
|
|
font-size: 0.8em;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 480px) {
|
||
|
|
body {
|
||
|
|
padding: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content {
|
||
|
|
padding: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.demo-button {
|
||
|
|
padding: 14px 16px;
|
||
|
|
font-size: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input {
|
||
|
|
padding: 14px 12px;
|
||
|
|
font-size: 16px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Dark mode support */
|
||
|
|
@media (prefers-color-scheme: dark) {
|
||
|
|
body {
|
||
|
|
background: #1a1a1a;
|
||
|
|
color: #e0e0e0;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1, h2 {
|
||
|
|
color: #f0f0f0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content {
|
||
|
|
background: #2d2d2d;
|
||
|
|
color: #e0e0e0;
|
||
|
|
}
|
||
|
|
|
||
|
|
code, pre {
|
||
|
|
background: #333;
|
||
|
|
color: #e0e0e0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input {
|
||
|
|
background: #333;
|
||
|
|
border-color: #555;
|
||
|
|
color: #e0e0e0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.widget-input:focus {
|
||
|
|
border-color: #f34611;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section {
|
||
|
|
border-top-color: #444;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section h3 {
|
||
|
|
color: #ccc;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-section p {
|
||
|
|
color: #aaa;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.success {
|
||
|
|
background: #1e4620;
|
||
|
|
color: #8fdf94;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.error {
|
||
|
|
background: #4a1c1c;
|
||
|
|
color: #f5a5a5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.auth-status.loading {
|
||
|
|
background: #4a3c00;
|
||
|
|
color: #ffd966;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Accessibility improvements */
|
||
|
|
@media (prefers-reduced-motion: reduce) {
|
||
|
|
* {
|
||
|
|
animation-duration: 0.01ms !important;
|
||
|
|
animation-iteration-count: 1 !important;
|
||
|
|
transition-duration: 0.01ms !important;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
|
||
|
|
<body>
|
||
|
|
<h1>ReplyPier Widget Test Page</h1>
|
||
|
|
|
||
|
|
<div class="content">
|
||
|
|
<h2>About ReplyPier</h2>
|
||
|
|
<p>
|
||
|
|
This is a test page demonstrating the ReplyPier chat widget integration.
|
||
|
|
The widget provides real-time chat functionality with AI-powered responses
|
||
|
|
and seamless human handoff capabilities.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="demo-section">
|
||
|
|
<h2>Widget Controls</h2>
|
||
|
|
<p>Enter your widget ID and click initialize to load the chat widget.</p>
|
||
|
|
<div class="widget-controls">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
id="widgetIdInput"
|
||
|
|
placeholder="Enter Widget ID"
|
||
|
|
value=""
|
||
|
|
class="widget-input"
|
||
|
|
>
|
||
|
|
<button class="demo-button" id="widgetButton" onclick="initializeWidget()">
|
||
|
|
Initialize Widget
|
||
|
|
</button>
|
||
|
|
<button class="demo-button clear-button" id="clearButton" onclick="clearWidget()">
|
||
|
|
Clear Widget
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- API Key Authentication Section -->
|
||
|
|
<div class="auth-section">
|
||
|
|
<h3>Token Authentication (Optional)</h3>
|
||
|
|
<p>For agents with "Require Token Auth" enabled, provide an API key to authenticate.</p>
|
||
|
|
<div class="widget-controls">
|
||
|
|
<input
|
||
|
|
type="password"
|
||
|
|
id="apiKeyInput"
|
||
|
|
placeholder="Enter API Key (from Widget Apps)"
|
||
|
|
class="widget-input"
|
||
|
|
>
|
||
|
|
<input
|
||
|
|
type="email"
|
||
|
|
id="customerEmailInput"
|
||
|
|
placeholder="Customer Email (optional)"
|
||
|
|
class="widget-input"
|
||
|
|
>
|
||
|
|
</div>
|
||
|
|
<div class="widget-controls" style="margin-top: 10px;">
|
||
|
|
<button class="demo-button auth-button" id="authButton" onclick="authenticateAndInitialize()">
|
||
|
|
Authenticate & Initialize
|
||
|
|
</button>
|
||
|
|
<span id="authStatus" class="auth-status"></span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="content">
|
||
|
|
<h2>Integration Code</h2>
|
||
|
|
<p>To add the ReplyPier widget to your website, add the following code before the closing
|
||
|
|
<code></body></code> tag:
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<h3 style="margin-top: 20px; font-size: 1rem;">Basic (No Authentication)</h3>
|
||
|
|
<pre><code>
|
||
|
|
<script>
|
||
|
|
window.replypierId = 'YOUR_WIDGET_ID';
|
||
|
|
// Required for local testing — without it the widget uses the API URL baked
|
||
|
|
// into the build (the cloud API for a production build).
|
||
|
|
window.replypierBaseUrl = 'http://localhost:8000/api/v1';
|
||
|
|
</script>
|
||
|
|
<script src="http://localhost:5173/webclient/replypier.min.js"></script>
|
||
|
|
</code></pre>
|
||
|
|
|
||
|
|
<h3 style="margin-top: 20px; font-size: 1rem;">With Token Authentication</h3>
|
||
|
|
<p style="font-size: 0.9em; color: #666;">For agents with "Require Token Auth" enabled, generate a token server-side:</p>
|
||
|
|
<pre><code>
|
||
|
|
// Server-side: Generate token using your API key
|
||
|
|
const response = await fetch('http://localhost:8000/api/v1/generate-token', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': 'Bearer YOUR_API_KEY'
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
widget_id: 'YOUR_WIDGET_ID',
|
||
|
|
customer_email: 'user@example.com', // optional
|
||
|
|
ttl_seconds: 3600
|
||
|
|
})
|
||
|
|
});
|
||
|
|
const { data } = await response.json();
|
||
|
|
// Pass data.token to frontend
|
||
|
|
|
||
|
|
// Client-side: Initialize widget with token
|
||
|
|
<script>
|
||
|
|
window.replypierId = 'YOUR_WIDGET_ID';
|
||
|
|
window.replypierToken = 'TOKEN_FROM_SERVER';
|
||
|
|
window.replypierBaseUrl = 'http://localhost:8000/api/v1';
|
||
|
|
</script>
|
||
|
|
<script src="http://localhost:5173/webclient/replypier.min.js"></script>
|
||
|
|
</code></pre>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- ReplyPier Widget Integration -->
|
||
|
|
<script>
|
||
|
|
// Constants for localStorage keys
|
||
|
|
const WIDGET_ID_KEY = 'replypier_widget_id';
|
||
|
|
const WIDGET_INITIALIZED_KEY = 'replypier_initialized';
|
||
|
|
|
||
|
|
// Where the widget talks to. WITHOUT this the loader falls back to the URL
|
||
|
|
// baked in at build time — which for a :prod build is the cloud API, so a
|
||
|
|
// local test would silently chat with api.replypier.site.
|
||
|
|
// Override in the console: localStorage.setItem('replypier_api_base', '...')
|
||
|
|
const CM_API_BASE = localStorage.getItem('replypier_api_base') || 'http://localhost:8000/api/v1';
|
||
|
|
// The loader is served by the FRONTEND (vite in dev, the dashboard origin in
|
||
|
|
// production) — never copied into this folder, where it would go stale.
|
||
|
|
// The backend does not serve it. Rebuild after loader changes:
|
||
|
|
// cd ../frontend && npm run build:webclient:prod
|
||
|
|
// Follow the page's own scheme rather than hardcoding http://: served over
|
||
|
|
// https this page would otherwise pull executable code over an unencrypted
|
||
|
|
// connection, which the browser blocks as mixed content anyway.
|
||
|
|
const CM_LOADER_URL = localStorage.getItem('replypier_loader_url')
|
||
|
|
|| `${window.location.protocol}//localhost:5173/webclient/replypier.min.js`;
|
||
|
|
|
||
|
|
// Load saved widget ID and initialize on page load
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
loadSavedWidgetId();
|
||
|
|
autoInitializeWidget();
|
||
|
|
});
|
||
|
|
|
||
|
|
function loadSavedWidgetId() {
|
||
|
|
const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
|
||
|
|
const widgetInput = document.getElementById('widgetIdInput');
|
||
|
|
const widgetButton = document.getElementById('widgetButton');
|
||
|
|
|
||
|
|
if (savedWidgetId) {
|
||
|
|
widgetInput.value = savedWidgetId;
|
||
|
|
widgetButton.textContent = 'Re-initialize Widget';
|
||
|
|
updateButtonStates(true);
|
||
|
|
} else {
|
||
|
|
// Set default widget ID if none saved
|
||
|
|
widgetInput.value = 'd63483f1-3954-4a94-a3f7-bb5039dda633';
|
||
|
|
updateButtonStates(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function autoInitializeWidget() {
|
||
|
|
const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
|
||
|
|
const wasInitialized = localStorage.getItem(WIDGET_INITIALIZED_KEY) === 'true';
|
||
|
|
const hasApiKey = localStorage.getItem('replypier_api_key');
|
||
|
|
|
||
|
|
// Don't auto-initialize if we have an API key saved (requires token auth)
|
||
|
|
if (hasApiKey) {
|
||
|
|
console.log('Skipping auto-initialize: API key detected, use Authenticate & Initialize button');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (savedWidgetId && wasInitialized) {
|
||
|
|
console.log('Auto-initializing widget with saved ID:', savedWidgetId);
|
||
|
|
performWidgetInitialization(savedWidgetId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function initializeWidget() {
|
||
|
|
const widgetId = document.getElementById('widgetIdInput').value;
|
||
|
|
if (!widgetId) {
|
||
|
|
alert('Please enter a widget ID');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Save widget ID to localStorage
|
||
|
|
localStorage.setItem(WIDGET_ID_KEY, widgetId);
|
||
|
|
localStorage.setItem(WIDGET_INITIALIZED_KEY, 'true');
|
||
|
|
|
||
|
|
performWidgetInitialization(widgetId);
|
||
|
|
updateButtonStates(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
function performWidgetInitialization(widgetId) {
|
||
|
|
// Set the widget ID
|
||
|
|
window.replypierId = widgetId;
|
||
|
|
window.replypierBaseUrl = CM_API_BASE;
|
||
|
|
|
||
|
|
// Remove existing script if any
|
||
|
|
const existingScript = document.getElementById('replypier-script');
|
||
|
|
if (existingScript) {
|
||
|
|
existingScript.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait a moment for cleanup
|
||
|
|
setTimeout(() => {
|
||
|
|
// Create and add new script
|
||
|
|
const script = document.createElement('script');
|
||
|
|
script.id = 'replypier-script';
|
||
|
|
script.src = CM_LOADER_URL;
|
||
|
|
script.onload = function() {
|
||
|
|
// Initialize the widget once script loads
|
||
|
|
if (window.ReplyPier) {
|
||
|
|
window.ReplyPier.init({ id: widgetId });
|
||
|
|
console.log('Widget initialized with ID:', widgetId);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.body.appendChild(script);
|
||
|
|
}, 100);
|
||
|
|
}
|
||
|
|
|
||
|
|
function clearWidget() {
|
||
|
|
// Remove widget script first to prevent it from recreating storage items
|
||
|
|
const existingScript = document.getElementById('replypier-script');
|
||
|
|
if (existingScript) {
|
||
|
|
existingScript.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reset window properties to disable widget functionality
|
||
|
|
if (window.replypierId) {
|
||
|
|
delete window.replypierId;
|
||
|
|
}
|
||
|
|
if (window.ReplyPier) {
|
||
|
|
// Call destroy method if it exists
|
||
|
|
if (typeof window.ReplyPier.destroy === 'function') {
|
||
|
|
window.ReplyPier.destroy();
|
||
|
|
}
|
||
|
|
delete window.ReplyPier;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Function to clear storage items with specific patterns
|
||
|
|
function clearStorageItems(storage, storageType) {
|
||
|
|
const keysToRemove = [];
|
||
|
|
|
||
|
|
// Collect all keys that match our patterns
|
||
|
|
for (let i = 0; i < storage.length; i++) {
|
||
|
|
const key = storage.key(i);
|
||
|
|
if (key && (
|
||
|
|
key.includes('replypier') ||
|
||
|
|
key.includes('widget') ||
|
||
|
|
key === 'rpid' ||
|
||
|
|
key.includes('chat') ||
|
||
|
|
key.startsWith('cm_') ||
|
||
|
|
key.includes('conversation') ||
|
||
|
|
key.includes('session')
|
||
|
|
)) {
|
||
|
|
keysToRemove.push(key);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove all collected keys
|
||
|
|
keysToRemove.forEach(key => {
|
||
|
|
storage.removeItem(key);
|
||
|
|
console.log(`Removed ${storageType} key:`, key);
|
||
|
|
});
|
||
|
|
|
||
|
|
return keysToRemove.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear localStorage
|
||
|
|
const localKeysRemoved = clearStorageItems(localStorage, 'localStorage');
|
||
|
|
|
||
|
|
// Clear sessionStorage
|
||
|
|
const sessionKeysRemoved = clearStorageItems(sessionStorage, 'sessionStorage');
|
||
|
|
|
||
|
|
// Also remove our specific keys (backup)
|
||
|
|
localStorage.removeItem(WIDGET_ID_KEY);
|
||
|
|
localStorage.removeItem(WIDGET_INITIALIZED_KEY);
|
||
|
|
localStorage.removeItem('rpid');
|
||
|
|
sessionStorage.removeItem('rpid');
|
||
|
|
|
||
|
|
// Clear any widget-related cookies
|
||
|
|
let cookiesCleared = 0;
|
||
|
|
document.cookie.split(";").forEach(function(c) {
|
||
|
|
const cookie = c.trim();
|
||
|
|
if (cookie.includes('replypier') ||
|
||
|
|
cookie.includes('widget') ||
|
||
|
|
cookie.includes('rpid') ||
|
||
|
|
cookie.includes('chat')) {
|
||
|
|
const eqPos = cookie.indexOf("=");
|
||
|
|
const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
|
||
|
|
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
|
||
|
|
document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + window.location.hostname;
|
||
|
|
cookiesCleared++;
|
||
|
|
console.log('Removed cookie:', name);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Clear input field
|
||
|
|
document.getElementById('widgetIdInput').value = '';
|
||
|
|
|
||
|
|
// Force a small delay and check again for any recreated items
|
||
|
|
setTimeout(() => {
|
||
|
|
localStorage.removeItem('rpid');
|
||
|
|
sessionStorage.removeItem('rpid');
|
||
|
|
console.log('Final cleanup: Removed any remaining rpid keys');
|
||
|
|
}, 100);
|
||
|
|
|
||
|
|
updateButtonStates(false);
|
||
|
|
console.log(`Widget cleared completely:
|
||
|
|
- LocalStorage keys removed: ${localKeysRemoved}
|
||
|
|
- SessionStorage keys removed: ${sessionKeysRemoved}
|
||
|
|
- Cookies cleared: ${cookiesCleared}
|
||
|
|
- Widget script removed and window properties cleaned`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateButtonStates(isInitialized) {
|
||
|
|
const widgetButton = document.getElementById('widgetButton');
|
||
|
|
const clearButton = document.getElementById('clearButton');
|
||
|
|
|
||
|
|
if (isInitialized) {
|
||
|
|
widgetButton.textContent = 'Re-initialize Widget';
|
||
|
|
clearButton.style.display = 'inline-block';
|
||
|
|
} else {
|
||
|
|
widgetButton.textContent = 'Initialize Widget';
|
||
|
|
clearButton.style.display = 'none';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update button state when input changes
|
||
|
|
document.addEventListener('DOMContentLoaded', function() {
|
||
|
|
const widgetInput = document.getElementById('widgetIdInput');
|
||
|
|
widgetInput.addEventListener('input', function() {
|
||
|
|
const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
|
||
|
|
const currentValue = this.value;
|
||
|
|
const widgetButton = document.getElementById('widgetButton');
|
||
|
|
|
||
|
|
if (savedWidgetId && currentValue === savedWidgetId) {
|
||
|
|
widgetButton.textContent = 'Re-initialize Widget';
|
||
|
|
} else {
|
||
|
|
widgetButton.textContent = 'Initialize Widget';
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Load saved API key
|
||
|
|
const savedApiKey = localStorage.getItem('replypier_api_key');
|
||
|
|
if (savedApiKey) {
|
||
|
|
document.getElementById('apiKeyInput').value = savedApiKey;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Authenticate with API key and initialize widget
|
||
|
|
async function authenticateAndInitialize() {
|
||
|
|
const widgetId = document.getElementById('widgetIdInput').value;
|
||
|
|
const apiKey = document.getElementById('apiKeyInput').value;
|
||
|
|
const customerEmail = document.getElementById('customerEmailInput').value;
|
||
|
|
const authStatus = document.getElementById('authStatus');
|
||
|
|
|
||
|
|
if (!widgetId) {
|
||
|
|
alert('Please enter a widget ID');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!apiKey) {
|
||
|
|
alert('Please enter an API key');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show loading state
|
||
|
|
authStatus.textContent = 'Authenticating...';
|
||
|
|
authStatus.className = 'auth-status loading';
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Call the generate-token endpoint
|
||
|
|
const response = await fetch(`${CM_API_BASE}/generate-token`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Authorization': `Bearer ${apiKey}`
|
||
|
|
},
|
||
|
|
body: JSON.stringify({
|
||
|
|
widget_id: widgetId,
|
||
|
|
customer_email: customerEmail || undefined,
|
||
|
|
ttl_seconds: 3600
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorData = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const data = await response.json();
|
||
|
|
|
||
|
|
if (data.success && data.data?.token) {
|
||
|
|
// Save API key for future use
|
||
|
|
localStorage.setItem('replypier_api_key', apiKey);
|
||
|
|
localStorage.setItem(WIDGET_ID_KEY, widgetId);
|
||
|
|
localStorage.setItem(WIDGET_INITIALIZED_KEY, 'true');
|
||
|
|
|
||
|
|
// Set the token for the widget
|
||
|
|
window.replypierToken = data.data.token;
|
||
|
|
window.replypierId = widgetId;
|
||
|
|
|
||
|
|
// Show success
|
||
|
|
authStatus.textContent = `Authenticated! Token expires: ${new Date(data.data.expires_at).toLocaleString()}`;
|
||
|
|
authStatus.className = 'auth-status success';
|
||
|
|
|
||
|
|
console.log('Token generated successfully:', {
|
||
|
|
widget_id: data.data.widget_id,
|
||
|
|
expires_at: data.data.expires_at,
|
||
|
|
expires_in: data.data.expires_in
|
||
|
|
});
|
||
|
|
|
||
|
|
// Initialize the widget with the token
|
||
|
|
performAuthenticatedWidgetInitialization(widgetId, data.data.token);
|
||
|
|
updateButtonStates(true);
|
||
|
|
} else {
|
||
|
|
throw new Error('Invalid response from server');
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Authentication error:', error);
|
||
|
|
authStatus.textContent = `Error: ${error.message}`;
|
||
|
|
authStatus.className = 'auth-status error';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function performAuthenticatedWidgetInitialization(widgetId, token) {
|
||
|
|
// Set widget configuration - widget loader checks window.replypierToken first
|
||
|
|
window.replypierId = widgetId;
|
||
|
|
window.replypierToken = token;
|
||
|
|
window.replypierBaseUrl = CM_API_BASE;
|
||
|
|
|
||
|
|
// Remove existing script if any
|
||
|
|
const existingScript = document.getElementById('replypier-script');
|
||
|
|
if (existingScript) {
|
||
|
|
existingScript.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Also remove any existing widget elements to ensure fresh initialization
|
||
|
|
const existingContainer = document.getElementById('replypier-container');
|
||
|
|
const existingButton = document.getElementById('replypier-button');
|
||
|
|
const existingClose = document.getElementById('replypier-mobile-close');
|
||
|
|
const existingTopbar = document.getElementById('replypier-mobile-topbar');
|
||
|
|
if (existingContainer) existingContainer.remove();
|
||
|
|
if (existingButton) existingButton.remove();
|
||
|
|
if (existingClose) existingClose.remove();
|
||
|
|
if (existingTopbar) existingTopbar.remove();
|
||
|
|
|
||
|
|
// Clear ReplyPier instance
|
||
|
|
if (window.ReplyPier) {
|
||
|
|
delete window.ReplyPier;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait a moment for cleanup
|
||
|
|
setTimeout(() => {
|
||
|
|
// Create and add new script
|
||
|
|
const script = document.createElement('script');
|
||
|
|
script.id = 'replypier-script';
|
||
|
|
script.src = CM_LOADER_URL;
|
||
|
|
|
||
|
|
script.onload = function() {
|
||
|
|
if (window.ReplyPier) {
|
||
|
|
window.ReplyPier.init({
|
||
|
|
id: widgetId
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.body.appendChild(script);
|
||
|
|
}, 200);
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
|
||
|
|
</html>
|