Fix WebTransport server implementation and add test client
Server fixes: - Move H3Connection initialization to ProtocolNegotiated event (matches official aioquic pattern) - Fix datagram routing to use session_id instead of flow_id - Add max_datagram_frame_size=65536 to enable QUIC datagrams - Fix send_datagram() to use keyword arguments - Add certificate chain handling for Let's Encrypt - Add no-cache headers to static server Command-line improvements: - Move settings from environment variables to argparse - Add comprehensive CLI arguments with defaults - Default mode=wt, cert=cert.pem, key=key.pem Test clients: - Add test_webtransport_client.py - Python WebTransport client that successfully connects - Add test_http3.py - Basic HTTP/3 connectivity test Client updates: - Auto-configure server URL and certificate hash from /cert-hash.json - Add ES6 module support Status: ✅ Python WebTransport client works perfectly ✅ Server properly handles WebTransport connections and datagrams ❌ Chrome fails due to cached QUIC state (QUIC_IETF_GQUIC_ERROR_MISSING) 🔍 Firefox sends packets but fails differently - to be debugged next session 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
101
client/client.js
101
client/client.js
@@ -193,24 +193,61 @@ async function readLoop() {
|
||||
}
|
||||
|
||||
async function connectWT() {
|
||||
// Check if WebTransport is available
|
||||
if (typeof WebTransport === 'undefined') {
|
||||
setStatus('ERROR: WebTransport not supported in this browser. Use Chrome/Edge 97+ or Firefox with flag enabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = ui.url.value.trim();
|
||||
const hashHex = ui.hash.value.trim();
|
||||
|
||||
if (!url) {
|
||||
setStatus('ERROR: Please enter a server URL');
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus('connecting...');
|
||||
console.log('Connecting to:', url);
|
||||
|
||||
const opts = {};
|
||||
if (hashHex) {
|
||||
const bytes = new Uint8Array(hashHex.match(/.{1,2}/g).map(b => parseInt(b,16)));
|
||||
opts.serverCertificateHashes = [{ algorithm: 'sha-256', value: bytes }];
|
||||
try {
|
||||
const bytes = new Uint8Array(hashHex.match(/.{1,2}/g).map(b => parseInt(b,16)));
|
||||
opts.serverCertificateHashes = [{ algorithm: 'sha-256', value: bytes }];
|
||||
console.log('Using certificate hash:', hashHex);
|
||||
} catch (err) {
|
||||
setStatus('ERROR: Invalid certificate hash format');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.warn('No certificate hash provided - connection may fail for self-signed certs');
|
||||
}
|
||||
|
||||
try {
|
||||
const wt = new WebTransport(url, opts);
|
||||
await wt.ready;
|
||||
transport = wt;
|
||||
writer = wt.datagrams.writable.getWriter();
|
||||
setStatus('connected to server');
|
||||
console.log('WebTransport connected successfully');
|
||||
|
||||
readLoop();
|
||||
requestAnimationFrame(render);
|
||||
|
||||
// Send JOIN immediately (spectator → player upon space handled below)
|
||||
const pkt = buildJoin(1, nextSeq(), ui.name.value.trim());
|
||||
await writer.write(pkt);
|
||||
} catch (err) {
|
||||
console.error('WebTransport connection failed:', err);
|
||||
if (err.message.includes('certificate')) {
|
||||
setStatus('ERROR: Certificate validation failed. Check cert hash or use valid CA cert.');
|
||||
} else if (err.message.includes('net::')) {
|
||||
setStatus('ERROR: Network error - is server running on ' + url + '?');
|
||||
} else {
|
||||
setStatus('ERROR: ' + err.message);
|
||||
}
|
||||
}
|
||||
const wt = new WebTransport(url, opts);
|
||||
await wt.ready;
|
||||
transport = wt;
|
||||
writer = wt.datagrams.writable.getWriter();
|
||||
setStatus('connected');
|
||||
readLoop();
|
||||
requestAnimationFrame(render);
|
||||
// Send JOIN immediately (spectator → player upon space handled below)
|
||||
const pkt = buildJoin(1, nextSeq(), ui.name.value.trim());
|
||||
await writer.write(pkt);
|
||||
}
|
||||
|
||||
function dirFromKey(e) {
|
||||
@@ -229,7 +266,47 @@ async function onKey(e) {
|
||||
try { await writer.write(pkt); } catch (err) { /* ignore */ }
|
||||
}
|
||||
|
||||
// Auto-configure on page load
|
||||
async function autoConfigureFromServer() {
|
||||
try {
|
||||
// Try to fetch cert hash from the static server
|
||||
const response = await fetch('/cert-hash.json');
|
||||
if (!response.ok) {
|
||||
setStatus('Auto-config unavailable, please enter manually');
|
||||
return;
|
||||
}
|
||||
const config = await response.json();
|
||||
|
||||
// Auto-populate fields
|
||||
if (config.wtUrl) {
|
||||
// Use the hostname from browser but with the WT port from server
|
||||
const hostname = window.location.hostname || '127.0.0.1';
|
||||
const wtPort = config.wtPort || 4433;
|
||||
ui.url.value = `https://${hostname}:${wtPort}/`;
|
||||
}
|
||||
if (config.sha256) {
|
||||
ui.hash.value = config.sha256;
|
||||
}
|
||||
|
||||
setStatus('Auto-configured from server');
|
||||
console.log('Auto-configured:', config);
|
||||
} catch (err) {
|
||||
console.warn('Auto-config failed:', err);
|
||||
// Fallback: just populate URL from browser location
|
||||
const hostname = window.location.hostname || '127.0.0.1';
|
||||
ui.url.value = `https://${hostname}:4433/`;
|
||||
setStatus('Manual configuration required');
|
||||
}
|
||||
}
|
||||
|
||||
ui.connect.onclick = () => { connectWT().catch(e => setStatus('connect failed: ' + e)); };
|
||||
window.addEventListener('keydown', onKey);
|
||||
window.addEventListener('resize', render);
|
||||
|
||||
// Auto-configure when page loads
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', autoConfigureFromServer);
|
||||
} else {
|
||||
autoConfigureFromServer();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user