import { randomBytes } from 'node:crypto'; import protocol from '../frontend/direct-protocol.js'; // One instance per authenticated room. It has no media endpoint or binary path. export function createDirectRelay({ now = Date.now } = {}) { const transfers = new Map(); const rates = new Map(); function handle(from, raw, peers, send) { const m = protocol.parse(raw); if (!m || !peers.has(from)) return false; const t = now(); for (const [token, x] of transfers) if (x.expires < t || !peers.has(x.from) || !peers.has(x.to)) transfers.delete(token); const rate = rates.get(from); const r = rate && t - rate.start < 60000 ? rate : { start: t, count: 0 }; rates.set(from, r); if (++r.count > 240) return false; if (m.action === 'invite') { if (m.to === from || !peers.has(m.to) || transfers.size >= 64) return false; const token = randomBytes(24).toString('base64url'); transfers.set(token, { from, to: m.to, file: m.file, accepted: false, expires: t + 60000 }); send(peers.get(m.to), { type: 'direct', action: 'invite', from, token, file: m.file }); send(peers.get(from), { type: 'direct', action: 'invited', to: m.to, token, file: m.file }); return true; } const x = transfers.get(m.token); if (!x || ![x.from, x.to].includes(from)) return false; const target = from === x.from ? x.to : x.from; if (m.action === 'accept') { if (from !== x.to || x.accepted) return false; x.accepted = true; x.expires = t + 30 * 60000; } else if (m.action === 'signal' && !x.accepted) return false; else if (m.action === 'decline' || m.action === 'complete') transfers.delete(m.token); send(peers.get(target), { ...m, from }); return true; } return { handle }; }