Close unconfirmed transfer paths and reject binary signalling envelopes

This commit is contained in:
Jonathan Sykes
2026-10-03 20:45:04 +08:00
parent 5661eea6e6
commit d78b06ff4b
6 changed files with 24 additions and 14 deletions

View File

@@ -12,13 +12,16 @@
async function offer(key, to, meta) {
if (!enabled()) throw Error('Direct device transfer is unavailable or turned off');
if (!meta?.id) throw Error('Choose a saved song first');
if (pending.has(`${key}:${to}:${meta.id}`) || [...transfers.values()].some(s => s.key === key && s.peer === to && s.claim.id === meta.id)) return;
const file = await root.OPFS.getFileObject(meta.id);
if (!file) throw Error('Save this song on this device before sending it');
const hash = root.Sha256.create();
for (let at = 0; at < file.size; at += 1024 * 1024) hash.update(new Uint8Array(await file.slice(at, at + 1024 * 1024).arrayBuffer()));
const claim = root.DirectProtocol.file({ ...meta, cid: hash.hex(), size: file.size, extension: file.name.split('.').pop().toLowerCase(), streamMime: await root.DirectStream?.inspect(file), title: meta.title || meta.id });
if (!claim) throw Error('Unsupported file metadata');
pending.set(`${key}:${to}:${claim.id}`, { file, claim });
const pendingKey = `${key}:${to}:${claim.id}`;
const item = { file, claim }; pending.set(pendingKey, item);
setTimeout(() => { if (pending.get(pendingKey) === item) pending.delete(pendingKey); }, 60000);
send(key, { type: 'direct', action: 'invite', to, file: claim });
hooks.notify('Waiting for the receiving device to accept');
}
@@ -30,6 +33,7 @@
if (error) { waiting.get(s.claim.id)?.resolve(false); waiting.delete(s.claim.id); hooks.notify(`${error}. Partial copy kept for resume.`); hooks.fallback(s.claim, () => offer(s.key, s.peer, s.claim).catch(e => hooks.notify(e.message))); }
}
function connection(s, receiver) {
clearTimeout(s.timer);
const pc = s.pc = new RTCPeerConnection({ iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] });
s.ice = [];
pc.onicecandidate = e => { if (e.candidate) signal(s.key, s.token, { kind: 'ice', candidate: e.candidate.toJSON() }); };
@@ -88,7 +92,7 @@
}
async function handle(key, m) {
if (m.type !== 'direct') return;
if (m.action === 'request') { if (enabled()) offer(key, m.from, hooks.meta?.(m.id) || { id: m.id, title: m.id }).catch(e => hooks.notify(e.message)); return; }
if (m.action === 'request') { if (enabled() && hooks.settings().p2pShare !== false) offer(key, m.from, hooks.meta?.(m.id) || { id: m.id, title: m.id }).catch(e => hooks.notify(e.message)); return; }
try {
if (m.action === 'invite') {
if (!enabled() || !root.DirectProtocol.file(m.file)) return send(key, { type: 'direct', action: 'decline', token: m.token });
@@ -97,7 +101,7 @@
if (m.action === 'invited') {
const p = pending.get(`${key}:${m.to}:${m.file.id}`); if (!p) return;
pending.delete(`${key}:${m.to}:${m.file.id}`);
transfers.set(m.token, { key, token: m.token, peer: m.to, claim: p.claim, file: p.file }); return;
const s = { key, token: m.token, peer: m.to, claim: p.claim, file: p.file }; transfers.set(m.token, s); s.timer = setTimeout(() => finish(s), 60000); return;
}
const s = transfers.get(m.token); if (!s || s.key !== key || m.from !== s.peer) return;
if (m.action === 'accept') connection(s, false);