92 lines
4.1 KiB
Diff
92 lines
4.1 KiB
Diff
--- a/frontend/p2p-client.js
|
|
+++ b/frontend/p2p-client.js
|
|
@@ -6,6 +6,9 @@
|
|
* changed() a save/delete happened → re-report soon
|
|
* device() { deviceId, secret } or null
|
|
* authHeaders() { 'X-Device': … } for other P2P calls
|
|
+ * contribute(videoId) / unknownVideos()
|
|
+ * hand a saved file the server doesn't
|
|
+ * know yet to /api/p2p/intake (plan 016)
|
|
* onMessage(type, fn) / signal(to, data) / peer()
|
|
* live /ws/p2p socket (plan 014): the
|
|
* device is "online" while it is open
|
|
@@ -33,6 +36,7 @@
|
|
let again = false;
|
|
let timer = null;
|
|
let lastSync = 0;
|
|
+ let lastUnknown = []; // cids the server did not recognise at the last report
|
|
|
|
function device() {
|
|
try {
|
|
@@ -130,6 +134,7 @@
|
|
if (r.status === 401) { try { localStorage.removeItem(KEY); } catch { /* ignore */ } return null; }
|
|
const j = await r.json();
|
|
if (!j || !j.ok) return null;
|
|
+ lastUnknown = Array.isArray(j.unknown) ? j.unknown : [];
|
|
const accepted = new Set(j.accepted || []);
|
|
for (const rec of recs) {
|
|
if (rec.cid && accepted.has(rec.cid) && rec.state !== 'verified') { rec.state = 'verified'; await window.DeviceDB.putFile(rec); }
|
|
@@ -151,6 +156,33 @@
|
|
return j;
|
|
}
|
|
|
|
+ // ---- intake (plan 016) ---------------------------------------------------------
|
|
+ // Send one saved video to the server so it can hash + validate it and add its
|
|
+ // cid to the catalog. Uploads the whole file: only on the user's request
|
|
+ // ("Verify & share") or when the server asks for a copy (plan 018).
|
|
+ async function contribute(videoId, { cid = null, title = '', channel = '' } = {}) {
|
|
+ const dev = await ensureDevice();
|
|
+ const rec = await window.DeviceDB.getFile(videoId);
|
|
+ const file = window.OPFS.getFileObject ? await window.OPFS.getFileObject(videoId) : null;
|
|
+ if (!file) return { ok: false, error: 'not saved on this device' };
|
|
+ const h = { 'X-Device': dev.deviceId + '.' + dev.secret };
|
|
+ const t = await (await fetch('/api/p2p/intake', {
|
|
+ method: 'POST', headers: { ...h, 'Content-Type': 'application/json' },
|
|
+ body: JSON.stringify({ videoId, cid: cid || (rec && rec.cid) || undefined, size: file.size, title, channel }),
|
|
+ })).json().catch(() => ({ ok: false, error: 'server unreachable' }));
|
|
+ if (!t.ok || t.known) { if (t.known) changed(); return t; }
|
|
+ const r = await (await fetch(t.url, { method: 'PUT', headers: h, body: file }))
|
|
+ .json().catch(() => ({ ok: false, error: 'upload failed' }));
|
|
+ if (r.ok) changed();
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ async function unknownVideos() {
|
|
+ if (!lastUnknown.length || !window.DeviceDB) return [];
|
|
+ const recs = await window.DeviceDB.listFiles();
|
|
+ return recs.filter((r) => r.cid && lastUnknown.includes(r.cid)).map((r) => r.videoId);
|
|
+ }
|
|
+
|
|
// ---- presence socket (/ws/p2p) — plan 014 ------------------------------------
|
|
// Open while sharing OR receiving is on. Credentials go in the first message,
|
|
// never the URL. Reconnects with backoff (5 s … 5 min).
|
|
@@ -258,7 +290,7 @@
|
|
}
|
|
|
|
window.P2PClient = {
|
|
- start, changed, sync, device, authHeaders, config,
|
|
+ start, changed, sync, device, authHeaders, config, contribute, unknownVideos,
|
|
onMessage, signal, peer: () => myPeer, isConnected: () => !!(ws && ws.readyState === 1 && myPeer),
|
|
};
|
|
}());
|
|
--- a/frontend/opfs.js
|
|
+++ b/frontend/opfs.js
|
|
@@ -108,6 +108,17 @@
|
|
}
|
|
},
|
|
|
|
+ // The saved File itself (disk-backed, not read into memory) — used as an
|
|
+ // upload body by P2P intake. null when not saved.
|
|
+ async getFileObject(videoId) {
|
|
+ try {
|
|
+ const found = await findHandle(videoId);
|
|
+ return found ? await found[0].getFile() : null;
|
|
+ } catch {
|
|
+ return null;
|
|
+ }
|
|
+ },
|
|
+
|
|
// Bytes [offset, offset+length) of a saved video — answers the server's
|
|
// P2P range challenges (p2p-client.js). null when the file is missing.
|
|
async readRange(videoId, offset, length) {
|