2026-07-16 12:17:00 +00:00
|
|
|
import { IncomingMessage } from 'http';
|
|
|
|
|
import { WebSocket } from 'ws';
|
|
|
|
|
import jwt from 'jsonwebtoken';
|
|
|
|
|
import { AuthPayload } from '../middleware/requireAuth';
|
|
|
|
|
import { getTunnel } from '../db';
|
|
|
|
|
import { registry } from '../relay/TunnelRegistry';
|
2026-07-16 13:30:14 +00:00
|
|
|
import { dispatchAgentMsg } from '../relay/pending';
|
2026-07-16 12:17:00 +00:00
|
|
|
|
|
|
|
|
export function handleAgentUpgrade( req: IncomingMessage, ws: WebSocket ): void
|
|
|
|
|
{
|
|
|
|
|
const match = req.url?.match( /^\/api\/agent\/([^/?]+)/ );
|
|
|
|
|
if ( !match ) { ws.close( 1008, 'Bad URL' ); return; }
|
|
|
|
|
const tunnelId = match[ 1 ];
|
|
|
|
|
|
|
|
|
|
const url = new URL( req.url!, 'http://localhost' );
|
|
|
|
|
const token = url.searchParams.get( 'token' );
|
|
|
|
|
if ( !token ) { ws.close( 1008, 'Missing token' ); return; }
|
|
|
|
|
|
|
|
|
|
let payload: AuthPayload;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
payload = jwt.verify( token, process.env.JWT_SECRET ?? '' ) as AuthPayload;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
ws.close( 1008, 'Invalid token' );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const tunnel = getTunnel( tunnelId );
|
|
|
|
|
if ( !tunnel ) { ws.close( 1008, 'Tunnel not found' ); return; }
|
|
|
|
|
if ( tunnel.ownerId !== payload.userId ) { ws.close( 1008, 'Forbidden' ); return; }
|
|
|
|
|
|
|
|
|
|
registry.register( tunnelId, ws );
|
|
|
|
|
console.log( `[agent] connected: "${tunnel.name}" (${tunnelId})` );
|
|
|
|
|
|
|
|
|
|
ws.on( 'message', ( data ) =>
|
|
|
|
|
{
|
2026-07-16 13:30:14 +00:00
|
|
|
try { dispatchAgentMsg( JSON.parse( data.toString() ) ); }
|
2026-07-16 12:17:00 +00:00
|
|
|
catch { /* ignore malformed messages */ }
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
ws.on( 'close', () =>
|
|
|
|
|
{
|
|
|
|
|
registry.unregister( tunnelId );
|
|
|
|
|
console.log( `[agent] disconnected: "${tunnel.name}" (${tunnelId})` );
|
|
|
|
|
} );
|
|
|
|
|
}
|