File size: 8,841 Bytes
4fd620e 9ae1216 00fdb1d 4fd620e 4bb3f7c 00fdb1d 4bb3f7c 9ae1216 4fd620e 00fdb1d 4fd620e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | // DaisyChain-Web signaling + static host.
// - Serves public/ (the page users open).
// - WebSocket signaling: introduces peers in a room and relays WebRTC
// offers/answers/ICE. It never sees the compute — that's P2P over WebRTC.
// Only dependency: `ws`. Run: npm install && node server.js
const http = require("http");
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const { WebSocketServer } = require("ws");
// Snapdrop-style: peers are grouped by their PUBLIC IP, so only devices on the
// same network auto-discover each other. An explicit ?room=CODE overrides this
// to connect across networks (share the code with people you invite).
function clientIP(req) {
const xff = req.headers["x-forwarded-for"];
if (xff) return xff.split(",")[0].trim();
return (req.socket.remoteAddress || "unknown").replace(/^::ffff:/, "");
}
function roomFor(req) {
const u = new URL(req.url, "http://x");
const code = u.searchParams.get("room");
if (code) return "room:" + code;
const h = crypto.createHash("sha256").update(clientIP(req)).digest("hex").slice(0, 10);
return "net:" + h; // same network -> same room (IP not exposed in the id)
}
const PORT = process.env.PORT || 8787;
const PUB = path.join(__dirname, "public");
const TYPES = { ".html": "text/html", ".js": "text/javascript",
".css": "text/css", ".json": "application/json" };
// ---- training text: FineWeb-Edu, served by THIS server ----------------------
// The browser used to call datasets-server.huggingface.co/rows directly, which
// 503s routinely under load. The parquet files themselves sit on the HF CDN
// behind plain resolve/ URLs — public, range-request friendly, and far more
// reliable than the rows API. So this endpoint reads a random slice of a
// random shard of the 10BT sample straight off the CDN with hyparquet (pure
// JS, no native deps) and hands the browser plain text. FineWeb-Edu is the
// ONLY dataset; there is no pass-through of client-chosen dataset ids.
const FWE_BASE = "https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu/resolve/main/sample/10BT/";
const FWE_SHARDS = Array.from({ length: 14 }, (_, i) => `${String(i).padStart(3, "0")}_00000.parquet`);
const FWE_ROWS_PER_SLICE = 130; // ~550k chars, what one training run consumes
const fweCache = new Map(); // shard -> { file, metadata } (metadata is ~kBs)
let hyparquet = null; // ESM — loaded on first use
async function fweSlice() {
hyparquet = hyparquet || await import("hyparquet");
const shard = FWE_SHARDS[Math.floor(Math.random() * FWE_SHARDS.length)];
let entry = fweCache.get(shard);
if (!entry) {
const file = await hyparquet.asyncBufferFromUrl({ url: FWE_BASE + shard });
const metadata = await hyparquet.parquetMetadataAsync(file);
entry = { file, metadata };
fweCache.set(shard, entry);
}
const total = Number(entry.metadata.num_rows);
const start = Math.floor(Math.random() * Math.max(1, total - FWE_ROWS_PER_SLICE));
const rows = await hyparquet.parquetReadObjects({
file: entry.file, metadata: entry.metadata, columns: ["text"],
rowStart: start, rowEnd: start + FWE_ROWS_PER_SLICE,
});
return { shard, start, text: rows.map(r => r.text).join("\n") };
}
// keepalive: proxies (incl. HuggingFace's) close idle WebSockets — ping every
// 30s keeps the pipe warm and detects dead clients within a minute
setInterval(() => {
for (const room of rooms.values())
for (const [pid, v] of room.peers) {
if (v.ws.isAlive === false) { v.ws.terminate(); continue; }
v.ws.isAlive = false;
send(v.ws, { type: "ping" });
}
}, 30000);
const server = http.createServer((req, res) => {
let p = decodeURIComponent(req.url.split("?")[0]);
if (p === "/mode") { // deployment flags for the client
res.writeHead(200, { "Content-Type": "application/json" });
// DAISY_FORCE_ROOMS=1 (set on the HF Space): no LAN auto-grouping — every
// visitor creates their own private room and invites devices by link.
// DAISY_RTC_CONFIG: optional JSON RTCPeerConnection config (own TURN etc.,
// PairDrop-style server-provided rtcConfig).
let rtc = null;
try { if (process.env.DAISY_RTC_CONFIG) rtc = JSON.parse(process.env.DAISY_RTC_CONFIG); } catch (e) {}
return res.end(JSON.stringify({ forceRooms: !!process.env.DAISY_FORCE_ROOMS, rtc }));
}
if (p === "/data") { // a fresh random slice of FineWeb-Edu
return fweSlice()
.then(({ shard, start, text }) => {
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8",
"X-FWE-Shard": shard, "X-FWE-Row": String(start) });
res.end(text);
})
.catch((e) => {
console.error("fineweb slice failed:", e.message);
res.writeHead(503, { "Content-Type": "text/plain" });
res.end("fineweb-edu unavailable: " + e.message);
});
}
if (p === "/") p = "/index.html";
const file = path.join(PUB, path.normalize(p));
if (!file.startsWith(PUB)) { res.writeHead(403); return res.end(); }
fs.readFile(file, (err, data) => {
if (err) { res.writeHead(404); return res.end("not found"); }
res.writeHead(200, { "Content-Type": TYPES[path.extname(file)] || "application/octet-stream" });
res.end(data);
});
});
const wss = new WebSocketServer({ server });
// roomId -> { peers: Map(id -> {ws,name}), host: id|null, pending: Map(id -> {ws,name}) }
// Private rooms (room:CODE) are gated: the creator is the host, and everyone
// arriving later waits until the host accepts them — knowing the code is not
// enough. Network rooms (net:) keep auto-join (same LAN, Snapdrop-style).
const rooms = new Map();
let nextId = 1;
function send(ws, obj) { if (ws.readyState === 1) ws.send(JSON.stringify(obj)); }
wss.on("connection", (ws, req) => {
const roomId = roomFor(req);
const name = (new URL(req.url, "http://x").searchParams.get("name") || "").slice(0, 40) || ("p" + nextId);
const id = "p" + (nextId++);
ws.peerId = id; ws.roomId = roomId;
if (!rooms.has(roomId)) rooms.set(roomId, { peers: new Map(), host: null, pending: new Map() });
const room = rooms.get(roomId);
const isPrivate = roomId.startsWith("room:");
function admit(pid, peer) {
const roster = [...room.peers.entries()].map(([qid, v]) => ({ id: qid, name: v.name }));
send(peer.ws, { type: "welcome", id: pid, room: roomId, peers: roster, host: room.host === pid });
for (const [, v] of room.peers) send(v.ws, { type: "peer-joined", id: pid, name: peer.name });
room.peers.set(pid, peer);
}
if (isPrivate && room.peers.size === 0) room.host = id; // creator hosts
if (isPrivate && room.host !== id) {
room.pending.set(id, { ws, name });
send(ws, { type: "waiting" });
const h = room.peers.get(room.host);
if (h) send(h.ws, { type: "join-request", id, name });
} else {
admit(id, { ws, name });
}
ws.on("message", (buf) => {
let msg; try { msg = JSON.parse(buf); } catch { return; }
if (msg.type === "signal" && msg.to && room.peers.has(id)) { // relay WebRTC signaling
const target = room.peers.get(msg.to);
if (target) send(target.ws, { type: "signal", from: id, data: msg.data });
} else if (msg.type === "pong") { // keepalive reply
ws.isAlive = true;
} else if (msg.type === "relay" && msg.to && room.peers.has(id)) {
// PairDrop-style WS fallback: when two devices can't form a direct
// WebRTC path (symmetric NATs, no TURN), the training payloads flow
// through here instead of dropping the peer
const target = room.peers.get(msg.to);
if (target) send(target.ws, { type: "relay", from: id, data: msg.data });
} else if (msg.type === "admit" && id === room.host) { // host verdict on a joiner
const p = room.pending.get(msg.id);
if (!p) return;
room.pending.delete(msg.id);
if (msg.allow) admit(msg.id, p);
else { send(p.ws, { type: "denied" }); p.ws.close(); }
}
});
ws.on("close", () => {
room.pending.delete(id);
if (room.peers.delete(id))
for (const [, v] of room.peers) send(v.ws, { type: "peer-left", id });
if (room.host === id) { // host left: promote oldest
room.host = room.peers.keys().next().value ?? null;
const h = room.peers.get(room.host);
if (h) {
send(h.ws, { type: "host" });
for (const [pid, p] of room.pending) send(h.ws, { type: "join-request", id: pid, name: p.name });
}
}
if (room.peers.size === 0 && room.pending.size === 0) rooms.delete(roomId);
});
});
server.listen(PORT, () => console.log(`DaisyChain-Web on http://localhost:${PORT}`));
|