UNPKG

@spectrumsense/spectrum-chat-dev

Version:

Embeddable AI Widget - Add trusted, evidence-based answers directly to your website. Simple installation, enterprise-grade security.

497 lines (444 loc) 15.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Spectrum Chat Security Test</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 2rem; background: #f3f4f6; } .header { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem; } h1 { margin: 0 0 0.5rem 0; color: #111827; } .subtitle { color: #6b7280; margin: 0; } .test-panel { background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 1.5rem; } .test-panel h2 { margin: 0 0 1rem 0; font-size: 1.25rem; color: #111827; } .phase-toggle { display: flex; gap: 1rem; margin-bottom: 1.5rem; } button { padding: 0.75rem 1.5rem; font-size: 1rem; border: 2px solid #e5e7eb; border-radius: 6px; background: white; cursor: pointer; transition: all 0.2s; } button:hover { background: #f9fafb; border-color: #d1d5db; } button.active { background: #2563eb; color: white; border-color: #2563eb; } button:disabled { opacity: 0.5; cursor: not-allowed; } .info-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; } .info-card { background: #f9fafb; padding: 1rem; border-radius: 6px; border: 1px solid #e5e7eb; } .info-label { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; color: #6b7280; margin-bottom: 0.25rem; } .info-value { font-size: 0.875rem; color: #111827; font-family: 'Courier New', monospace; word-break: break-all; } .log-panel { background: #1f2937; color: #f3f4f6; padding: 1rem; border-radius: 6px; font-family: 'Courier New', monospace; font-size: 0.875rem; max-height: 400px; overflow-y: auto; margin-bottom: 1rem; } .log-entry { padding: 0.25rem 0; border-bottom: 1px solid #374151; } .log-entry:last-child { border-bottom: none; } .log-time { color: #9ca3af; margin-right: 0.5rem; } .log-type { font-weight: 600; margin-right: 0.5rem; } .log-info { color: #60a5fa; } .log-success { color: #34d399; } .log-error { color: #f87171; } .log-warning { color: #fbbf24; } .controls { display: flex; gap: 1rem; flex-wrap: wrap; } .badge { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; font-size: 0.875rem; font-weight: 600; } .badge-blue { background: #dbeafe; color: #1e40af; } .badge-green { background: #d1fae5; color: #065f46; } .badge-red { background: #fee2e2; color: #991b1b; } </style> </head> <body> <div class="header"> <h1>🔒 Spectrum Chat Security Test</h1> <p class="subtitle">End-to-End Testing: Phase 0 (Site-Key) & Phase 1 (JWT)</p> </div> <div class="test-panel"> <h2>Security Testing</h2> <p style="color: #6b7280; margin: 0 0 1rem 0;"> <strong>JWT Authentication (Phase 1):</strong> Testing secure authentication with site-key, origin validation, and JWT session tokens. </p> <div style="background: #f0fdf4; border: 1px solid #86efac; border-radius: 6px; padding: 1rem;"> <div style="display: flex; align-items: center; gap: 0.5rem;"> <span style="font-size: 1.5rem;"></span> <span style="color: #166534; font-weight: 600;">JWT Enabled by Default</span> </div> <p style="color: #166534; margin: 0.5rem 0 0 2rem; font-size: 0.875rem;"> Enhanced security with automatic token management and refresh </p> </div> </div> <div class="test-panel"> <h2>Session Information</h2> <div class="info-grid"> <div class="info-card"> <div class="info-label">API URL</div> <div class="info-value" id="api-url">Loading...</div> </div> <div class="info-card"> <div class="info-label">Site Key</div> <div class="info-value" id="site-key">Loading...</div> </div> <div class="info-card"> <div class="info-label">JWT Enabled</div> <div class="info-value" id="jwt-status">Loading...</div> </div> <div class="info-card"> <div class="info-label">Conversation ID</div> <div class="info-value" id="conversation-id">None</div> </div> <div class="info-card"> <div class="info-label">Session ID</div> <div class="info-value" id="session-id">None</div> </div> <div class="info-card"> <div class="info-label">Token Valid</div> <div class="info-value" id="token-valid">N/A</div> </div> </div> </div> <div class="test-panel"> <h2>Test Controls</h2> <div class="controls"> <button onclick="openWidget()">Open Widget</button> <button onclick="closeWidget()">Close Widget</button> <button onclick="testSession()" id="test-session-btn">Test Session Creation</button> <button onclick="testStartConversation()">Test Start Conversation</button> <button onclick="testContinueConversation()" id="test-continue-btn" disabled>Test Continue Conversation</button> <button onclick="clearSession()">Clear Session</button> <button onclick="refreshStatus()">Refresh Status</button> <button onclick="clearLog()">Clear Log</button> </div> </div> <div class="test-panel"> <h2>Test Log</h2> <div class="log-panel" id="log-panel"> <div class="log-entry"> <span class="log-time">[Ready]</span> <span class="log-type log-info">INFO</span> <span>Waiting for tests to run...</span> </div> </div> </div> <!-- Spectrum Chat Widget --> <script> // Configure the widget (JWT enabled by default) window.SpectrumChatConfig = { siteKey: 'pub_bright_spectrum_htlzbncn', }; </script> <script src="/dist/spectrum-chat.js"></script> <script> // Initialize window.addEventListener('DOMContentLoaded', () => { log('Page loaded, initializing tests...', 'info'); log('JWT authentication enabled by default', 'info'); setTimeout(() => { refreshStatus(); log('Widget initialized successfully', 'success'); }, 1000); }); // Widget controls function openWidget() { if (window.SpectrumChatGlobal) { window.SpectrumChatGlobal.open(); log('Widget opened', 'info'); } } function closeWidget() { if (window.SpectrumChatGlobal) { window.SpectrumChatGlobal.close(); log('Widget closed', 'info'); } } function clearSession() { if (window.SpectrumChatGlobal) { window.SpectrumChatGlobal.clearSession(); log('Session cleared (conversation, token, messages)', 'warning'); refreshStatus(); } } // Test functions async function testSession() { log('Testing session creation...', 'info'); if (!window.SpectrumChatGlobal) { log('Widget not initialized', 'error'); return; } try { const tokenData = await window.SpectrumChatGlobal.initializeSession(); if (tokenData) { log('Session created successfully', 'success'); log(`Session ID: ${tokenData.session_id}`, 'info'); log(`Expires at: ${tokenData.expires_at}`, 'info'); log(`Token: ${tokenData.token.substring(0, 30)}...`, 'info'); refreshStatus(); } else { log('Session creation returned null (JWT not enabled?)', 'warning'); } } catch (error) { log(`Session creation failed: ${error.message}`, 'error'); console.error(error); } } async function testStartConversation() { log('Testing start conversation...', 'info'); const testMessage = `Test message at ${new Date().toLocaleTimeString()}: What is your return policy?`; log(`Sending message: "${testMessage}"`, 'info'); try { const config = window.SpectrumChatGlobal.getConfig(); // Build request const requestBody = { message: testMessage, siteKey: config.siteKey, citations: config.enableCitations }; const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' }; // Add JWT token if Phase 1 if (config.useJWT) { const tokenData = window.SpectrumChatGlobal.getTokenData(); if (!tokenData || !tokenData.token) { log('No token found, creating session first...', 'info'); await testSession(); const newTokenData = window.SpectrumChatGlobal.getTokenData(); if (newTokenData && newTokenData.token) { headers['Authorization'] = `Bearer ${newTokenData.token}`; log('Added Authorization header', 'info'); } } else { headers['Authorization'] = `Bearer ${tokenData.token}`; log('Added Authorization header', 'info'); } } log('Making POST request to /api/v1/conversations', 'info'); log(`Request body: ${JSON.stringify(requestBody, null, 2)}`, 'info'); const response = await fetch(config.apiUrl, { method: 'POST', headers: headers, body: JSON.stringify(requestBody) }); const data = await response.json(); if (response.ok) { log('✅ Conversation started successfully', 'success'); log(`Response: ${data.text.substring(0, 100)}...`, 'success'); log(`Conversation ID: ${data.conversation_id}`, 'success'); if (data.sources && data.sources.length > 0) { log(`Citations: ${data.sources.length} sources found`, 'info'); } // Store conversation ID manually for testing sessionStorage.setItem('spectrum-chat-conversation-id', data.conversation_id); refreshStatus(); document.getElementById('test-continue-btn').disabled = false; } else { log(`❌ API returned error: ${response.status}`, 'error'); log(`Error: ${data.error || 'Unknown error'}`, 'error'); } } catch (error) { log(`❌ Request failed: ${error.message}`, 'error'); console.error(error); } } async function testContinueConversation() { log('Testing continue conversation...', 'info'); const conversationId = window.SpectrumChatGlobal.getConversationId(); if (!conversationId) { log('No active conversation. Start a conversation first.', 'error'); return; } const testMessage = `Follow-up at ${new Date().toLocaleTimeString()}: How long do I have to return items?`; log(`Sending message: "${testMessage}"`, 'info'); log(`Conversation ID: ${conversationId}`, 'info'); try { const config = window.SpectrumChatGlobal.getConfig(); // Build URL for continue conversation const continueUrl = `http://localhost:8000/api/v1/conversations/${conversationId}`; const requestBody = { message: testMessage, citations: config.enableCitations }; const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json' }; // Add JWT token if Phase 1 if (config.useJWT) { const tokenData = window.SpectrumChatGlobal.getTokenData(); if (tokenData && tokenData.token) { headers['Authorization'] = `Bearer ${tokenData.token}`; log('Added Authorization header', 'info'); } } log(`Making POST request to /api/v1/conversations/${conversationId}`, 'info'); log(`Request body: ${JSON.stringify(requestBody, null, 2)}`, 'info'); const response = await fetch(continueUrl, { method: 'POST', headers: headers, body: JSON.stringify(requestBody) }); const data = await response.json(); if (response.ok) { log('✅ Conversation continued successfully', 'success'); log(`Response: ${data.text.substring(0, 100)}...`, 'success'); if (data.sources && data.sources.length > 0) { log(`Citations: ${data.sources.length} sources found`, 'info'); } refreshStatus(); } else { log(`❌ API returned error: ${response.status}`, 'error'); log(`Error: ${data.error || 'Unknown error'}`, 'error'); } } catch (error) { log(`❌ Request failed: ${error.message}`, 'error'); console.error(error); } } // Status update function refreshStatus() { if (!window.SpectrumChatGlobal) { return; } const config = window.SpectrumChatGlobal.getConfig(); const conversationId = window.SpectrumChatGlobal.getConversationId(); const sessionId = window.SpectrumChatGlobal.getSessionId(); const isTokenValid = window.SpectrumChatGlobal.isTokenValid(); document.getElementById('api-url').textContent = config.apiUrl || 'Not set'; document.getElementById('site-key').textContent = config.siteKey || 'Not set'; document.getElementById('jwt-status').textContent = config.useJWT ? '✅ Enabled' : '❌ Disabled'; document.getElementById('conversation-id').textContent = conversationId || 'None'; document.getElementById('session-id').textContent = sessionId || 'None'; document.getElementById('token-valid').textContent = isTokenValid ? '✅ Valid' : (sessionId ? '❌ Invalid/Expired' : 'Not created yet'); // Enable/disable continue button document.getElementById('test-continue-btn').disabled = !conversationId; } // Logging function log(message, type = 'info') { const logPanel = document.getElementById('log-panel'); const entry = document.createElement('div'); entry.className = 'log-entry'; const time = new Date().toLocaleTimeString(); const typeClass = `log-${type}`; const typeLabel = type.toUpperCase(); entry.innerHTML = ` <span class="log-time">[${time}]</span> <span class="log-type ${typeClass}">${typeLabel}</span> <span>${message}</span> `; logPanel.appendChild(entry); logPanel.scrollTop = logPanel.scrollHeight; } function clearLog() { document.getElementById('log-panel').innerHTML = ` <div class="log-entry"> <span class="log-time">[Ready]</span> <span class="log-type log-info">INFO</span> <span>Log cleared. Ready for new tests.</span> </div> `; } // Auto-refresh status every 5 seconds setInterval(refreshStatus, 5000); </script> </body> </html>