Files
hristudio/public/ws-check.html
2025-09-02 08:25:41 -04:00

478 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>