mirror of
https://github.com/soconnor0919/hristudio.git
synced 2025-12-11 06:34:44 -05:00
478 lines
16 KiB
HTML
478 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>WebSocket Connection Test | HRIStudio</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||
background: #f8fafc;
|
||
color: #334155;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.container {
|
||
max-width: 800px;
|
||
margin: 2rem auto;
|
||
padding: 0 1rem;
|
||
}
|
||
|
||
.card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
overflow: hidden;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.card-header {
|
||
background: #1e293b;
|
||
color: white;
|
||
padding: 1rem;
|
||
font-size: 1.25rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.card-content {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 20px;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.status-connecting {
|
||
background: #dbeafe;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.status-connected {
|
||
background: #dcfce7;
|
||
color: #166534;
|
||
}
|
||
|
||
.status-failed {
|
||
background: #fef2f2;
|
||
color: #dc2626;
|
||
}
|
||
|
||
.status-fallback {
|
||
background: #fef3c7;
|
||
color: #92400e;
|
||
}
|
||
|
||
.dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: currentColor;
|
||
}
|
||
|
||
.dot.pulse {
|
||
animation: pulse 1.5s infinite;
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
|
||
.log {
|
||
background: #f1f5f9;
|
||
border: 1px solid #e2e8f0;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
height: 300px;
|
||
overflow-y: auto;
|
||
font-family: "Courier New", monospace;
|
||
font-size: 0.875rem;
|
||
white-space: pre-wrap;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
button {
|
||
background: #3b82f6;
|
||
color: white;
|
||
border: none;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
button:hover:not(:disabled) {
|
||
background: #2563eb;
|
||
}
|
||
|
||
button:disabled {
|
||
background: #94a3b8;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.input-group {
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.input-group label {
|
||
display: block;
|
||
margin-bottom: 0.5rem;
|
||
font-weight: 500;
|
||
color: #475569;
|
||
}
|
||
|
||
input[type="text"] {
|
||
width: 100%;
|
||
padding: 0.5rem;
|
||
border: 1px solid #d1d5db;
|
||
border-radius: 6px;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.info-item {
|
||
background: #f8fafc;
|
||
padding: 0.75rem;
|
||
border-radius: 6px;
|
||
border: 1px solid #e2e8f0;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 0.75rem;
|
||
color: #64748b;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.info-value {
|
||
font-weight: 500;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.alert {
|
||
padding: 1rem;
|
||
border-radius: 8px;
|
||
margin: 1rem 0;
|
||
border-left: 4px solid;
|
||
}
|
||
|
||
.alert-info {
|
||
background: #eff6ff;
|
||
border-color: #3b82f6;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.alert-warning {
|
||
background: #fefce8;
|
||
border-color: #eab308;
|
||
color: #a16207;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
🔌 WebSocket Connection Test
|
||
</div>
|
||
<div class="card-content">
|
||
<div class="alert alert-info">
|
||
<strong>Development Mode:</strong> WebSocket connections are expected to fail in Next.js development server.
|
||
The app automatically falls back to polling for real-time updates.
|
||
</div>
|
||
|
||
<div id="status" class="status-badge status-failed">
|
||
<div class="dot"></div>
|
||
<span>Disconnected</span>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label for="trialId">Trial ID:</label>
|
||
<input type="text" id="trialId" value="931c626d-fe3f-4db3-a36c-50d6898e1b17">
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label for="userId">User ID:</label>
|
||
<input type="text" id="userId" value="08594f2b-64fe-4952-947f-3edc5f144f52">
|
||
</div>
|
||
|
||
<div class="controls">
|
||
<button id="connectBtn" onclick="testConnection()">Test WebSocket Connection</button>
|
||
<button id="disconnectBtn" onclick="disconnect()" disabled>Disconnect</button>
|
||
<button onclick="clearLog()">Clear Log</button>
|
||
<button onclick="testPolling()">Test Polling Fallback</button>
|
||
</div>
|
||
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<div class="info-label">Connection Attempts</div>
|
||
<div class="info-value" id="attempts">0</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Messages Received</div>
|
||
<div class="info-value" id="messages">0</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Connection Time</div>
|
||
<div class="info-value" id="connectionTime">N/A</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Last Error</div>
|
||
<div class="info-value" id="lastError">None</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
📋 Connection Log
|
||
</div>
|
||
<div class="card-content">
|
||
<div id="log" class="log"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
ℹ️ How This Works
|
||
</div>
|
||
<div class="card-content">
|
||
<h3 style="margin-bottom: 0.5rem;">Expected Behavior:</h3>
|
||
<ul style="margin-left: 2rem; margin-bottom: 1rem;">
|
||
<li><strong>Development:</strong> WebSocket fails, app uses polling fallback (2-second intervals)</li>
|
||
<li><strong>Production:</strong> WebSocket connects successfully, minimal polling backup</li>
|
||
</ul>
|
||
|
||
<h3 style="margin-bottom: 0.5rem;">Testing Steps:</h3>
|
||
<ol style="margin-left: 2rem;">
|
||
<li>Click "Test WebSocket Connection" - should fail with connection error</li>
|
||
<li>Click "Test Polling Fallback" - should work and show API responses</li>
|
||
<li>Check browser Network tab for ongoing tRPC polling requests</li>
|
||
<li>Open actual wizard interface to see full functionality</li>
|
||
</ol>
|
||
|
||
<div class="alert alert-warning" style="margin-top: 1rem;">
|
||
<strong>Note:</strong> This test confirms the WebSocket failure is expected in development.
|
||
Your trial runner works perfectly using the polling fallback system.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let ws = null;
|
||
let attempts = 0;
|
||
let messages = 0;
|
||
let startTime = null;
|
||
|
||
const elements = {
|
||
status: document.getElementById('status'),
|
||
log: document.getElementById('log'),
|
||
connectBtn: document.getElementById('connectBtn'),
|
||
disconnectBtn: document.getElementById('disconnectBtn'),
|
||
attempts: document.getElementById('attempts'),
|
||
messages: document.getElementById('messages'),
|
||
connectionTime: document.getElementById('connectionTime'),
|
||
lastError: document.getElementById('lastError')
|
||
};
|
||
|
||
function updateStatus(text, className, pulse = false) {
|
||
elements.status.innerHTML = `
|
||
<div class="dot ${pulse ? 'pulse' : ''}"></div>
|
||
<span>${text}</span>
|
||
`;
|
||
elements.status.className = `status-badge ${className}`;
|
||
}
|
||
|
||
function log(message, type = 'info') {
|
||
const timestamp = new Date().toLocaleTimeString();
|
||
const prefix = {
|
||
info: 'ℹ️',
|
||
success: '✅',
|
||
error: '❌',
|
||
warning: '⚠️',
|
||
websocket: '🔌',
|
||
polling: '🔄'
|
||
}[type] || 'ℹ️';
|
||
|
||
elements.log.textContent += `[${timestamp}] ${prefix} ${message}\n`;
|
||
elements.log.scrollTop = elements.log.scrollHeight;
|
||
}
|
||
|
||
function updateButtons(connecting = false, connected = false) {
|
||
elements.connectBtn.disabled = connecting || connected;
|
||
elements.disconnectBtn.disabled = !connected;
|
||
}
|
||
|
||
function generateToken() {
|
||
const userId = document.getElementById('userId').value;
|
||
return btoa(JSON.stringify({
|
||
userId: userId,
|
||
timestamp: Math.floor(Date.now() / 1000)
|
||
}));
|
||
}
|
||
|
||
function testConnection() {
|
||
const trialId = document.getElementById('trialId').value;
|
||
const token = generateToken();
|
||
|
||
if (!trialId) {
|
||
log('Please enter a trial ID', 'error');
|
||
return;
|
||
}
|
||
|
||
attempts++;
|
||
elements.attempts.textContent = attempts;
|
||
startTime = Date.now();
|
||
|
||
updateStatus('Connecting...', 'status-connecting', true);
|
||
updateButtons(true, false);
|
||
|
||
const wsUrl = `ws://localhost:3000/api/websocket?trialId=${trialId}&token=${token}`;
|
||
log(`Attempting WebSocket connection to: ${wsUrl}`, 'websocket');
|
||
log('This is expected to fail in development mode...', 'warning');
|
||
|
||
try {
|
||
ws = new WebSocket(wsUrl);
|
||
|
||
ws.onopen = function() {
|
||
const duration = Date.now() - startTime;
|
||
elements.connectionTime.textContent = `${duration}ms`;
|
||
updateStatus('Connected', 'status-connected');
|
||
updateButtons(false, true);
|
||
log('🎉 WebSocket connected successfully!', 'success');
|
||
log('This is unexpected in development mode - you may be in production', 'info');
|
||
};
|
||
|
||
ws.onmessage = function(event) {
|
||
messages++;
|
||
elements.messages.textContent = messages;
|
||
try {
|
||
const data = JSON.parse(event.data);
|
||
log(`📨 Received: ${data.type} - ${JSON.stringify(data.data)}`, 'success');
|
||
} catch (e) {
|
||
log(`📨 Received (raw): ${event.data}`, 'success');
|
||
}
|
||
};
|
||
|
||
ws.onclose = function(event) {
|
||
updateStatus('Connection Failed (Expected)', 'status-failed');
|
||
updateButtons(false, false);
|
||
|
||
if (event.code === 1006) {
|
||
log('✅ Connection failed as expected in development mode', 'success');
|
||
log('This confirms WebSocket failure behavior is working correctly', 'info');
|
||
elements.lastError.textContent = 'Expected dev failure';
|
||
} else {
|
||
log(`Connection closed: ${event.code} - ${event.reason}`, 'error');
|
||
elements.lastError.textContent = `${event.code}: ${event.reason}`;
|
||
}
|
||
|
||
updateStatus('Fallback to Polling (Normal)', 'status-fallback');
|
||
log('🔄 App will automatically use polling fallback', 'polling');
|
||
};
|
||
|
||
ws.onerror = function(error) {
|
||
log('✅ WebSocket error occurred (expected in dev mode)', 'success');
|
||
log('Error details: Connection establishment failed', 'info');
|
||
elements.lastError.textContent = 'Connection refused (expected)';
|
||
};
|
||
|
||
} catch (error) {
|
||
log(`Failed to create WebSocket: ${error.message}`, 'error');
|
||
updateStatus('Connection Failed', 'status-failed');
|
||
updateButtons(false, false);
|
||
elements.lastError.textContent = error.message;
|
||
}
|
||
}
|
||
|
||
function disconnect() {
|
||
if (ws) {
|
||
ws.close(1000, 'Manual disconnect');
|
||
ws = null;
|
||
}
|
||
updateStatus('Disconnected', 'status-failed');
|
||
updateButtons(false, false);
|
||
log('Disconnected by user', 'info');
|
||
}
|
||
|
||
function clearLog() {
|
||
elements.log.textContent = '';
|
||
messages = 0;
|
||
elements.messages.textContent = messages;
|
||
log('Log cleared', 'info');
|
||
}
|
||
|
||
async function testPolling() {
|
||
log('🔄 Testing polling fallback (tRPC API)...', 'polling');
|
||
|
||
try {
|
||
const trialId = document.getElementById('trialId').value;
|
||
const response = await fetch(`/api/trpc/trials.get?batch=1&input=${encodeURIComponent(JSON.stringify({0:{json:{id:trialId}}}))}`);
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
log('✅ Polling fallback working! API response received', 'success');
|
||
log(`Response status: ${response.status}`, 'info');
|
||
log('This is how the app gets real-time updates in development', 'polling');
|
||
|
||
if (data[0]?.result?.data) {
|
||
log(`Trial status: ${data[0].result.data.json.status}`, 'info');
|
||
}
|
||
} else {
|
||
log(`❌ Polling failed: ${response.status} ${response.statusText}`, 'error');
|
||
if (response.status === 401) {
|
||
log('You may need to sign in first', 'warning');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
log(`❌ Polling error: ${error.message}`, 'error');
|
||
log('Make sure the dev server is running', 'warning');
|
||
}
|
||
}
|
||
|
||
// Initialize
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
log('WebSocket test page loaded', 'info');
|
||
log('Click "Test WebSocket Connection" to verify expected failure', 'info');
|
||
log('Click "Test Polling Fallback" to verify API connectivity', 'info');
|
||
|
||
// Auto-test on load
|
||
setTimeout(() => {
|
||
log('Running automatic connection test...', 'websocket');
|
||
testConnection();
|
||
}, 1000);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|