fosrl.pangolin/server/routers/ws.ts

163 lines
4.6 KiB
TypeScript
Raw Normal View History

2024-11-04 00:29:25 -05:00
import { Router, Request, Response } from 'express';
import { Server as HttpServer } from 'http';
import { WebSocket, WebSocketServer } from 'ws';
import { IncomingMessage } from 'http';
import { Socket } from 'net';
2024-11-10 17:08:11 -05:00
import { Newt, newts, NewtSession } from '@server/db/schema';
import { eq } from 'drizzle-orm';
import db from '@server/db';
import { validateNewtSessionToken } from '@server/auth/newt';
2024-11-04 00:29:25 -05:00
// Custom interfaces
interface WebSocketRequest extends IncomingMessage {
2024-11-10 17:08:11 -05:00
token?: string;
2024-11-04 00:29:25 -05:00
}
interface AuthenticatedWebSocket extends WebSocket {
2024-11-10 17:08:11 -05:00
newt?: Newt;
isAlive?: boolean;
2024-11-04 00:29:25 -05:00
}
interface TokenPayload {
2024-11-10 17:08:11 -05:00
newt: Newt;
session: NewtSession;
2024-11-04 00:29:25 -05:00
}
const router: Router = Router();
const wss: WebSocketServer = new WebSocketServer({ noServer: true });
// Token verification middleware
const verifyToken = async (token: string): Promise<TokenPayload | null> => {
2024-11-10 17:08:11 -05:00
try {
const { session, newt } = await validateNewtSessionToken(
token
);
if (!session || !newt) {
return null;
}
const existingNewt = await db
.select()
.from(newts)
.where(eq(newts.newtId, newt.newtId));
if (!existingNewt || !existingNewt[0]) {
return null;
}
return { newt: existingNewt[0], session };
} catch (error) {
console.error('Token verification failed:', error);
return null;
}
2024-11-04 00:29:25 -05:00
};
// Handle WebSocket upgrade requests
router.get('/ws', (req: Request, res: Response) => {
2024-11-10 17:08:11 -05:00
// WebSocket upgrade will be handled by the server
res.status(200).send('WebSocket endpoint');
2024-11-04 00:29:25 -05:00
});
// Set up WebSocket server handling
const handleWSUpgrade = (server: HttpServer): void => {
2024-11-10 17:08:11 -05:00
server.on('upgrade', async (request: WebSocketRequest, socket: Socket, head: Buffer) => {
try {
// Extract token from query parameters or headers
const token = request.url?.includes('?')
? new URLSearchParams(request.url.split('?')[1]).get('token') || ''
: request.headers['sec-websocket-protocol'];
if (!token) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
// Verify the token
const tokenPayload = await verifyToken(token);
if (!tokenPayload) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
// Store token payload data in the request for later use
request.token = token;
wss.handleUpgrade(request, socket, head, (ws: AuthenticatedWebSocket) => {
// Attach newt data to the WebSocket instance
ws.newt = tokenPayload.newt;
ws.isAlive = true;
wss.emit('connection', ws, request);
});
} catch (error) {
console.error('Upgrade error:', error);
socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
socket.destroy();
}
});
2024-11-04 00:29:25 -05:00
};
// WebSocket message interface
interface WSMessage {
2024-11-10 17:08:11 -05:00
type: string;
data: any;
2024-11-04 00:29:25 -05:00
}
// WebSocket connection handler
wss.on('connection', (ws: AuthenticatedWebSocket, request: WebSocketRequest) => {
2024-11-10 17:08:11 -05:00
console.log(`Client connected - Newt ID: ${ws.newt?.newtId}`);
// Set up ping-pong for connection health check
const pingInterval = setInterval(() => {
if (ws.isAlive === false) {
clearInterval(pingInterval);
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
}, 30000);
// Handle pong response
ws.on('pong', () => {
ws.isAlive = true;
});
// Set up message handler
ws.on('message', (data) => {
try {
const message: WSMessage = JSON.parse(data.toString());
console.log('Received:', message);
// Echo the message back
ws.send(JSON.stringify({
type: 'echo',
data: message
}));
} catch (error) {
console.error('Message parsing error:', error);
ws.send(JSON.stringify({
type: 'error',
data: 'Invalid message format'
}));
}
});
// Handle client disconnect
ws.on('close', () => {
clearInterval(pingInterval);
console.log(`Client disconnected - Newt ID: ${ws.newt?.newtId}`);
});
// Handle errors
ws.on('error', (error: Error) => {
console.error('WebSocket error:', error);
});
2024-11-04 00:29:25 -05:00
});
export {
2024-11-10 17:08:11 -05:00
router,
handleWSUpgrade
2024-11-04 00:29:25 -05:00
};