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:
Vladyslav Doloman
2025-10-19 23:50:08 +00:00
parent ed5cb14b30
commit 1de5a8f3e6
9 changed files with 890 additions and 147 deletions

View File

@@ -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();
}