export { P2PColyseusDurableRoom } from './durable-room'; interface Env { P2P_COLYSEUS_ROOM: { idFromName(name: string): unknown; get(id: unknown): { fetch(request: Request): Promise }; }; P2P_COLYSEUS_MAX_REQUEST_BODY_BYTES?: string | undefined; P2P_COLYSEUS_ALLOWED_ORIGINS?: string | undefined; } export default { async fetch(request: Request, env: Env): Promise { let forwardedRequest = request; if (request.method === 'OPTIONS') { if (!isOriginAllowed(request, env)) { return json({ kind: 'error', message: 'Origin not allowed' }, 403, request, env); } return new Response(null, { headers: corsHeaders(request, env) }); } if (request.method !== 'GET' && request.method !== 'POST') { return json({ kind: 'error', message: 'Use GET or POST' }, 405, request, env); } if (!isOriginAllowed(request, env)) { return json({ kind: 'error', message: 'Origin not allowed' }, 403, request, env); } const upgrade = request.headers.get('Upgrade')?.toLowerCase(); if (upgrade === 'websocket') { const url = new URL(request.url); if ( !url.searchParams.get('peerId') || !url.searchParams.get('targetPeerId') || !url.searchParams.get('roomId') ) { return json( { kind: 'error', message: 'Missing peerId, targetPeerId, or roomId' }, 400, request, env, ); } } else if (request.method === 'POST') { const body = await request.text(); if (byteLength(body) > getMaxRequestBodyBytes(env)) { return json({ kind: 'error', message: 'Request body too large' }, 413, request, env); } forwardedRequest = new Request(request.url, { method: request.method, headers: request.headers, body, }); } const id = env.P2P_COLYSEUS_ROOM.idFromName('global'); const room = env.P2P_COLYSEUS_ROOM.get(id); if (upgrade === 'websocket') { return room.fetch(forwardedRequest); } const response = await room.fetch(forwardedRequest); const headers = new Headers(response.headers); for (const [key, value] of Object.entries(corsHeaders(request, env))) headers.set(key, value); return new Response(response.body, { status: response.status, statusText: response.statusText, headers, }); }, }; function json(value: unknown, status = 200, request?: Request, env?: Env): Response { return new Response(JSON.stringify(value), { status, headers: { 'Content-Type': 'application/json', ...corsHeaders(request, env), }, }); } function byteLength(value: string): number { return new TextEncoder().encode(value).byteLength; } function corsHeaders(request?: Request, env?: Env): Record { const origin = request?.headers.get('Origin') ?? '*'; const allowedOrigin = env && isOriginAllowed(request, env) && origin !== '*' ? origin : '*'; return { 'Access-Control-Allow-Origin': allowedOrigin, 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; } function isOriginAllowed(request: Request | undefined, env: Env): boolean { const origin = request?.headers.get('Origin'); if (!origin) return true; const configured = env.P2P_COLYSEUS_ALLOWED_ORIGINS; if (!configured) return true; const allowed = configured .split(',') .map((value) => value.trim()) .filter(Boolean); return allowed.includes('*') || allowed.includes(origin); } function getMaxRequestBodyBytes(env: Env): number { const value = env.P2P_COLYSEUS_MAX_REQUEST_BODY_BYTES; if (!value) return 32 * 1024; const parsed = Number(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : 32 * 1024; }