43 lines
1.9 KiB
JavaScript
43 lines
1.9 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 === 'request') {
|
|
if (m.to === from || !peers.has(m.to)) return false;
|
|
send(peers.get(m.to), { type: 'direct', action: 'request', from, id: m.id }); return true;
|
|
}
|
|
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 };
|
|
}
|