Files
ytplayer/server/direct-relay.js

39 lines
1.7 KiB
JavaScript

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 };
}