Prefer direct copies for playback, saves and exports within verified device groups
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
* reports both: `online` (now) and `lastVerifiedAt` / `stale` (history).
|
||||
* Peers are addressed by an opaque id (peerIdOf), never by device id.
|
||||
* ========================================================================== */
|
||||
import { createDirectRelay } from './direct-relay.js';
|
||||
import { createHash, timingSafeEqual } from 'node:crypto';
|
||||
|
||||
const sha = (s) => createHash('sha256').update(String(s)).digest('hex');
|
||||
@@ -24,7 +25,8 @@ export const peerIdOf = (deviceId) => sha('peer:' + deviceId).slice(0, 12);
|
||||
const MAX_MSG = 64 * 1024;
|
||||
const BUDGET_PER_MIN = 300;
|
||||
|
||||
export function createP2pHub({ getDevice, enabled = () => true, now = () => Date.now(), log = console } = {}) {
|
||||
export function createP2pHub({ getDevice, authorizeProfile = async () => '', enabled = () => true, now = () => Date.now(), log = console } = {}) {
|
||||
const directRooms = new Map();
|
||||
const online = new Map(); // deviceId -> ws
|
||||
const byPeer = new Map(); // peer -> deviceId (online only)
|
||||
|
||||
@@ -46,6 +48,8 @@ export function createP2pHub({ getDevice, enabled = () => true, now = () => Date
|
||||
const secret = String(m.secret || '');
|
||||
const d = /^dev_[0-9a-f]{16}$/.test(deviceId) ? await getDevice(deviceId).catch(() => null) : null;
|
||||
if (!d || !same(d.secret_hash, sha(secret))) { try { ws.close(4401, 'unknown device'); } catch { /* gone */ } return; }
|
||||
ws.data.profile = await authorizeProfile(m.profile, m.profileSecret, deviceId).catch(() => '');
|
||||
ws.data.inventory = [];
|
||||
ws.data.authed = true;
|
||||
ws.data.deviceId = deviceId;
|
||||
ws.data.peer = peerIdOf(deviceId);
|
||||
@@ -54,6 +58,14 @@ export function createP2pHub({ getDevice, enabled = () => true, now = () => Date
|
||||
online.set(deviceId, ws);
|
||||
byPeer.set(ws.data.peer, deviceId);
|
||||
send(ws, { type: 'hello', peer: ws.data.peer });
|
||||
directory(ws.data.profile);
|
||||
}
|
||||
|
||||
function directory(profile) {
|
||||
if (!profile) return;
|
||||
const sockets = [...online.values()].filter(ws => ws.data.profile === profile);
|
||||
const peers = sockets.map(ws => ({ id: ws.data.peer, name: 'Device ' + ws.data.peer.slice(-4), files: ws.data.inventory || [] }));
|
||||
for (const ws of sockets) send(ws, { type: 'direct-peers', peers: peers.filter(p => p.id !== ws.data.peer) });
|
||||
}
|
||||
|
||||
async function message(ws, raw) {
|
||||
@@ -66,6 +78,16 @@ export function createP2pHub({ getDevice, enabled = () => true, now = () => Date
|
||||
let m;
|
||||
try { m = JSON.parse(s); } catch { return; }
|
||||
if (!ws.data.authed) { if (m && m.type === 'auth' && !ws.data.authing) { ws.data.authing = true; await auth(ws, m); } return; }
|
||||
if (m?.type === 'direct-inventory' && ws.data.profile) {
|
||||
ws.data.inventory = Array.isArray(m.ids) ? [...new Set(m.ids.filter(id => typeof id === 'string' && /^[A-Za-z0-9_-]{1,128}$/.test(id)))].slice(0, 1000) : [];
|
||||
directory(ws.data.profile); return;
|
||||
}
|
||||
if (m?.type === 'direct' && ws.data.profile) {
|
||||
const profile = ws.data.profile;
|
||||
const peers = new Map([...online.values()].filter(x => x.data.profile === profile).map(x => [x.data.peer, x]));
|
||||
if (!directRooms.has(profile)) directRooms.set(profile, createDirectRelay());
|
||||
directRooms.get(profile).handle(ws.data.peer, m, peers, send); return;
|
||||
}
|
||||
if (m && m.type === 'signal' && typeof m.to === 'string') {
|
||||
const target = online.get(byPeer.get(m.to));
|
||||
if (!target || target === ws) { send(ws, { type: 'error', error: 'peer offline', to: m.to }); return; }
|
||||
@@ -78,6 +100,8 @@ export function createP2pHub({ getDevice, enabled = () => true, now = () => Date
|
||||
if (deviceId && online.get(deviceId) === ws) {
|
||||
online.delete(deviceId);
|
||||
byPeer.delete(peer);
|
||||
directory(ws.data.profile);
|
||||
if (![...online.values()].some(x => x.data.profile === ws.data.profile)) directRooms.delete(ws.data.profile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user