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