Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
134 kB · 3952 lines
JavaScript
at commit 558fe57f
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953// Session Server, 23.12.04.14.57// Represents a "room" or user or "client" backend// which at the moment is run once for every "piece"// that requests it.
/* #region todo + Now - [-] Fix live reloading of in-production udp. + Done - [c] `code.channel` should return a promise, and wait for a `code-channel:subbed`. event here? This way users get better confirmation if the socket doesn't go through or if there is a server issue. 23.07.04.18.01 (Might not actually be that necessary.) - [x] Add `obscenity` filter. - [x] Conditional redis sub to dev updates. (Will save bandwidth if extension gets lots of use, also would be more secure.) - [x] Secure the "code" path to require a special string. - [x] Secure the "reload" path (must be in dev mode, sorta okay) - [c] Speed up developer reload by using redis pub/sub. - [x] Send a signal to everyone once a user leaves. - [x] Get "developer" live reloading working again. - [x] Add sockets back. - [x] Make a "local" option. - [x] Read through: https://redis.io/docs/data-types#endregion */
// Add redis pub/sub here...
import Fastify from "fastify";import geckos from "@geckos.io/server";import geoip from "geoip-lite";import { WebSocket, WebSocketServer } from "ws";import ip from "ip";import chokidar from "chokidar";import fs from "fs";import path from "path";import crypto from "crypto";import dotenv from "dotenv";import dgram from "dgram";dotenv.config();
// Module streaming - path to public directoryconst PUBLIC_DIR = path.resolve(process.cwd(), "../system/public/aesthetic.computer");
// Module hash cache (invalidated on file change)const moduleHashes = new Map(); // path -> { hash, content, mtime }
// Compute hash for a module filefunction getModuleHash(modulePath) { const fullPath = path.join(PUBLIC_DIR, modulePath); try { const stats = fs.statSync(fullPath); const cached = moduleHashes.get(modulePath); // Return cached if mtime matches if (cached && cached.mtime === stats.mtimeMs) { return cached; } // Read and hash const content = fs.readFileSync(fullPath, "utf8"); const hash = crypto.createHash("sha256").update(content).digest("hex").slice(0, 16); const entry = { hash, content, mtime: stats.mtimeMs }; moduleHashes.set(modulePath, entry); return entry; } catch (err) { return null; }}
// Fairy:point throttle (for silo firehose visualization)const fairyThrottle = new Map(); // channelId -> last publish timestampconst FAIRY_THROTTLE_MS = 100; // 10Hz max per connection
// Raw UDP fairy relay (for native bare-metal clients)const udpRelay = dgram.createSocket("udp4");const udpClients = new Map(); // key "ip:port" → { address, port, handle, lastSeen }const UDP_MIDI_SOURCE_TTL_MS = 20000;const notepatMidiSources = new Map(); // key "@handle:machine" -> source metadataconst notepatMidiSubscribers = new Map(); // connection id -> { ws, all, handle, machineId }
// Error logging ring buffer (for dashboard display)const errorLog = [];const MAX_ERRORS = 50;const ERROR_RETENTION_MS = 60 * 60 * 1000; // 1 hour
function logError(level, message) { const entry = { level, message: typeof message === 'string' ? message : JSON.stringify(message), timestamp: new Date().toISOString() }; errorLog.push(entry); if (errorLog.length > MAX_ERRORS) errorLog.shift();}
// Capture uncaught errorsprocess.on('uncaughtException', (err) => { logError('error', `Uncaught: ${err.message}`); console.error('Uncaught Exception:', err);});
process.on('unhandledRejection', (reason, promise) => { logError('error', `Unhandled Rejection: ${reason}`); console.error('Unhandled Rejection:', reason);});
import { exec } from "child_process";
// FCM (Firebase Cloud Messaging)import { initializeApp, cert } from "firebase-admin/app"; // Firebase notifications.//import serviceAccount from "./aesthetic-computer-firebase-adminsdk-79w8j-5b5cdfced8.json" assert { type: "json" };import { getMessaging } from "firebase-admin/messaging";
let serviceAccount;try { const response = await fetch(process.env.GCM_FIREBASE_CONFIG_URL); if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } serviceAccount = await response.json();} catch (error) { console.error("Error fetching service account:", error); // Handle the error as needed}
initializeApp( { credential: cert(serviceAccount) }, //, //"aesthetic" + ~~performance.now(),);
// Initialize ChatManager for multi-instance chat supportconst chatManager = new ChatManager({ dev: process.env.NODE_ENV === "development" });await chatManager.init();
// Graceful shutdown — persist in-memory chat messages before exitlet shuttingDown = false;async function gracefulShutdown(signal) { if (shuttingDown) return; shuttingDown = true; console.log(`\n${signal} received, persisting chat messages...`); try { await chatManager.shutdown(); } catch (err) { console.error("Shutdown error:", err); } process.exit(0);}process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));process.on("SIGINT", () => gracefulShutdown("SIGINT"));
// Helper function to get handles of users currently on a specific piece// Used by chatManager to determine who's actually viewing the chat piecefunction getHandlesOnPiece(pieceName) { const handles = []; for (const [id, client] of Object.entries(clients)) { if (client.location === pieceName && client.handle) { handles.push(client.handle); } } return [...new Set(handles)]; // Remove duplicates}
// Expose the function to chatManagerchatManager.setPresenceResolver(getHandlesOnPiece);
// 🎯 Duel Manager — server-authoritative game for dumduel piececonst duelManager = new DuelManager();
import { filter } from "./filter.mjs"; // Profanity filtering.import { ChatManager } from "./chat-manager.mjs"; // Multi-instance chat support.import { DuelManager } from "./duel-manager.mjs"; // Server-authoritative duel game.
// *** AC Machines — remote device monitoring ***// Devices connect via /machines?role=device&machineId=X&token=Y// Viewers connect via /machines?role=viewer&token=Yimport { MongoClient } from "mongodb";
const machinesDevices = new Map(); // machineId → { ws, user, handle, machineId, info, lastHeartbeat }const machinesViewers = new Map(); // userSub → Set<ws>let machinesDb = null;
async function getMachinesDb() { if (machinesDb) return machinesDb; const connStr = process.env.MONGODB_CONNECTION_STRING; if (!connStr) return null; try { const client = new MongoClient(connStr); await client.connect(); machinesDb = client.db(process.env.MONGODB_NAME || "aesthetic"); return machinesDb; } catch (e) { error("[machines] MongoDB connect error:", e.message); return null; }}
let machineTokenSecret = null;let machineTokenSecretAt = 0;const MACHINE_SECRET_TTL = 5 * 60 * 1000; // refresh from DB every 5 min
async function loadMachineTokenSecret() { const now = Date.now(); if (machineTokenSecret && now - machineTokenSecretAt < MACHINE_SECRET_TTL) { return machineTokenSecret; } try { const db = await getMachinesDb(); if (!db) return machineTokenSecret; const doc = await db.collection("secrets").findOne({ _id: "machine-token" }); if (doc?.secret) { machineTokenSecret = doc.secret; machineTokenSecretAt = now; } } catch (e) { error("[machines] Failed to load machine-token secret:", e.message); } return machineTokenSecret;}
async function verifyMachineToken(token) { if (!token) return null; const secret = await loadMachineTokenSecret(); if (!secret) return null; try { const [payloadB64, sigB64] = token.split("."); if (!payloadB64 || !sigB64) return null; const expectedSig = crypto .createHmac("sha256", secret) .update(payloadB64) .digest("base64url"); if (sigB64 !== expectedSig) return null; return JSON.parse(Buffer.from(payloadB64, "base64url").toString()); } catch { return null; }}
// Verify an AC auth token (Bearer token from authorize()) by calling Auth0 userinfoasync function verifyACToken(token) { if (!token) return null; try { const res = await fetch("https://aesthetic.us.auth0.com/userinfo", { headers: { Authorization: `Bearer ${token}` }, }); if (!res.ok) return null; return await res.json(); // { sub, nickname, name, ... } } catch { return null; }}
function broadcastToMachineViewers(userSub, msg) { const viewers = machinesViewers.get(userSub); if (!viewers) return; const data = JSON.stringify(msg); for (const v of viewers) { if (v.readyState === WebSocket.OPEN) v.send(data); }}
async function upsertMachine(userSub, machineId, info) { const db = await getMachinesDb(); if (!db) return; const col = db.collection("ac-machines"); const now = new Date(); await col.updateOne( { user: userSub, machineId }, { $set: { user: userSub, machineId, ...info, status: "online", linked: true, lastSeen: now, updatedAt: now, }, $setOnInsert: { createdAt: now, bootCount: 0 }, $inc: { bootCount: 1 }, }, { upsert: true }, );}
async function updateMachineHeartbeat(userSub, machineId, uptime, currentPiece) { const db = await getMachinesDb(); if (!db) return; await db.collection("ac-machines").updateOne( { user: userSub, machineId }, { $set: { lastSeen: new Date(), uptime, currentPiece, status: "online" } }, );}
async function insertMachineLog(userSub, machineId, msg) { const db = await getMachinesDb(); if (!db) return; await db.collection("ac-machine-logs").insertOne({ machineId, user: userSub, type: msg.logType || "log", level: msg.level || "info", message: msg.message, data: msg.data || null, crashInfo: msg.crashInfo || null, when: msg.when ? new Date(msg.when) : new Date(), receivedAt: new Date(), });}
async function setMachineOffline(userSub, machineId) { const db = await getMachinesDb(); if (!db) return; await db.collection("ac-machines").updateOne( { user: userSub, machineId }, { $set: { status: "offline", updatedAt: new Date() } }, );}
// *** SockLogs - Remote console log forwarding from devices ***// Devices with ?socklogs param send logs via WebSocket// Viewers (CLI or web) can subscribe to see device logs in real-timeconst socklogsDevices = new Map(); // deviceId -> { ws, lastLog, logCount }const socklogsViewers = new Set(); // Set of viewer WebSockets
function socklogsBroadcast(deviceId, logEntry) { const message = JSON.stringify({ type: 'log', deviceId, ...logEntry, serverTime: Date.now() }); for (const viewer of socklogsViewers) { if (viewer.readyState === WebSocket.OPEN) { viewer.send(message); } }}
function socklogsStatus() { return { devices: Array.from(socklogsDevices.entries()).map(([id, info]) => ({ deviceId: id, logCount: info.logCount, lastLog: info.lastLog, connectedAt: info.connectedAt })), viewerCount: socklogsViewers.size };}
import { createClient } from "redis";const redisConnectionString = process.env.REDIS_CONNECTION_STRING;const dev = process.env.NODE_ENV === "development";
// Dev log file for remote debuggingconst DEV_LOG_FILE = path.join(process.cwd(), "../system/public/aesthetic.computer/dev-logs.txt");
const { keys } = Object;let fastify; //, termkit, term;
if (dev) { // Load local ssl certs in development mode. fastify = Fastify({ https: { // allowHTTP1: true, key: fs.readFileSync("../ssl-dev/localhost-key.pem"), cert: fs.readFileSync("../ssl-dev/localhost.pem"), }, logger: true, });
// Import the `terminal-kit` library if dev is true. // try { // termkit = (await import("terminal-kit")).default; // } catch (err) { // error("Failed to load terminal-kit", error); // }} else { fastify = Fastify({ logger: true }); // Still log in production. No reason not to?}
// Insert `cors` headers as needed. 23.12.19.16.31// TODO: Is this even necessary?fastify.options("*", async (req, reply) => { const allowedOrigins = [ "https://aesthetic.local:8888", "https://aesthetic.computer", "https://notepat.com", ];
const origin = req.headers.origin; log("✈️ Preflight origin:", origin); // Check if the incoming origin is allowed if (allowedOrigins.includes(origin)) { reply.header("Access-Control-Allow-Origin", origin); } reply.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); reply.send();});
const server = fastify.server;
const DEV_LOG_DIR = "/tmp/dev-logs/";const deviceLogFiles = new Map(); // Track which devices have log files
// Ensure log directory existsif (dev) { try { fs.mkdirSync(DEV_LOG_DIR, { recursive: true }); } catch (error) { console.error("Failed to create dev log directory:", error); }}
const info = { port: process.env.PORT, // 8889 in development via `package.json` name: process.env.SESSION_BACKEND_ID, service: process.env.JAMSOCKET_SERVICE,};
const codeChannels = {}; // Used to filter `code` updates from redis to// clients who explicitly have the channel set.const codeChannelState = {}; // Store last code sent to each channel for late joiners
// DAW channel for M4L device ↔ IDE communicationconst dawDevices = new Set(); // Connection IDs of /device instancesconst dawIDEs = new Set(); // Connection IDs of IDE instances in Ableton mode
// Unified client tracking: each client has handle, user, location, and connection typesconst clients = {}; // Map of connection ID to { handle, user, location, websocket: true/false, udp: true/false }
// Device naming for local dev (persisted to file)const DEVICE_NAMES_FILE = path.join(process.cwd(), "../.device-names.json");let deviceNames = {}; // Map of IP -> { name, group }function loadDeviceNames() { try { if (fs.existsSync(DEVICE_NAMES_FILE)) { deviceNames = JSON.parse(fs.readFileSync(DEVICE_NAMES_FILE, 'utf8')); log("📱 Loaded device names:", Object.keys(deviceNames).length); } } catch (e) { log("📱 Could not load device names:", e.message); }}function saveDeviceNames() { try { fs.writeFileSync(DEVICE_NAMES_FILE, JSON.stringify(deviceNames, null, 2)); } catch (e) { log("📱 Could not save device names:", e.message); }}if (dev) loadDeviceNames();
// Get the dev host machine nameimport os from "os";const DEV_HOST_NAME = os.hostname();const DEV_LAN_IP = (() => { // First, try to read from /tmp/host-lan-ip (written by entry.fish in devcontainer) try { const hostIpFile = '/tmp/host-lan-ip'; if (fs.existsSync(hostIpFile)) { const ip = fs.readFileSync(hostIpFile, 'utf-8').trim(); if (ip && ip.match(/^\d+\.\d+\.\d+\.\d+$/)) { console.log(`🖥️ Using host LAN IP from ${hostIpFile}: ${ip}`); return ip; } } } catch (e) { /* ignore */ } // Fallback: try to detect from network interfaces const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const iface of interfaces[name]) { if (iface.family === 'IPv4' && !iface.internal && iface.address.startsWith('192.168.')) { return iface.address; } } } return null;})();console.log(`🖥️ Dev host: ${DEV_HOST_NAME}, LAN IP: ${DEV_LAN_IP || 'N/A'}`);
// Helper: Assign device letters (A, B, C...) based on connection orderfunction getDeviceLetter(connectionId) { // Get sorted list of connection IDs const sortedIds = Object.keys(connections) .map(id => parseInt(id)) .sort((a, b) => a - b); const index = sortedIds.indexOf(parseInt(connectionId)); if (index === -1) return '?'; // A=65, B=66, etc. Wrap around after Z return String.fromCharCode(65 + (index % 26));}
// Helper: Find connections by ID, IP, handle, or device letterfunction targetClients(target) { if (target === 'all') { return Object.entries(connections) .filter(([id, ws]) => ws?.readyState === WebSocket.OPEN) .map(([id, ws]) => ({ id: parseInt(id), ws })); } const results = []; for (const [id, ws] of Object.entries(connections)) { const client = clients[id]; const cleanTarget = target.replace('@', ''); const cleanIp = client?.ip?.replace('::ffff:', ''); const deviceLetter = getDeviceLetter(id); if ( String(id) === String(target) || cleanIp === target || client?.handle === `@${cleanTarget}` || client?.handle === cleanTarget || deviceNames[cleanIp]?.name?.toLowerCase() === target.toLowerCase() || deviceLetter.toLowerCase() === target.toLowerCase() // Match by letter (A, B, C...) ) { if (ws?.readyState === WebSocket.OPEN) { results.push({ id: parseInt(id), ws }); } } } return results;}
// *** Start up two `redis` clients. (One for subscribing, and for publishing)const redisEnabled = !!redisConnectionString;const sub = redisEnabled ? (!dev ? createClient({ url: redisConnectionString }) : createClient()) : null;if (sub) sub.on("error", (err) => { log("🔴 Redis subscriber client error!", err); logError('error', `Redis sub: ${err.message}`);});
const pub = redisEnabled ? (!dev ? createClient({ url: redisConnectionString }) : createClient()) : null;if (pub) pub.on("error", (err) => { log("🔴 Redis publisher client error!", err); logError('error', `Redis pub: ${err.message}`);});
try { if (sub && pub) { await sub.connect(); await pub.connect();
await sub.subscribe("code", (message) => { const parsed = JSON.parse(message); if (codeChannels[parsed.codeChannel]) { const msg = pack("code", message, "development"); subscribers(codeChannels[parsed.codeChannel], msg); } });
await sub.subscribe("scream", (message) => { everyone(pack("scream", message, "screamer")); // Socket back to everyone. }); } else { log("⚠️ Redis disabled — code/scream channels unavailable"); }} catch (err) { error("🔴 Could not connect to `redis` instance.");}
const secret = process.env.GITHUB_WEBHOOK_SECRET;
fastify.post("/update", (request, reply) => { const signature = request.headers["x-hub-signature"]; const hash = "sha1=" + crypto .createHmac("sha1", secret) .update(JSON.stringify(request.body)) .digest("hex");
if (hash !== signature) { reply.status(401).send({ error: "Invalid signature" }); return; }
// log("Path:", process.env.PATH);
// Restart service in production. // exec( // "cd /home/aesthetic-computer/session-server; pm2 stop all; git pull; npm install; pm2 start all", // (err, stdout, stderr) => { // if (err) { // error(`exec error: ${error}`); // return; // } // log(`stdout: ${stdout}`); // error(`stderr: ${stderr}`); // }, // );
reply.send({ status: "ok" });});
// *** Robots.txt - prevent crawling ***fastify.get("/robots.txt", async (req, reply) => { reply.type("text/plain"); return "User-agent: *\nDisallow: /";});
// *** Module HTTP endpoint - serve modules directly (bypasses Netlify proxy) ***// Used by boot.mjs on localhost when the main proxy is flakyfastify.get("/module/*", async (req, reply) => { const modulePath = req.params["*"]; const moduleData = getModuleHash(modulePath); if (moduleData) { reply .header("Content-Type", "application/javascript; charset=utf-8") .header("Access-Control-Allow-Origin", "*") .header("Cache-Control", "no-cache") .send(moduleData.content); } else { reply.status(404).send({ error: "Module not found", path: modulePath }); }});
// *** Build Stream - pipe terminal output to WebSocket clients ***// Available in both dev and production for build progress streamingfastify.post("/build-stream", async (req) => { const line = typeof req.body === 'string' ? req.body : req.body.line || ''; everyone(pack("build:log", { line, timestamp: Date.now() })); return { status: "ok" };});
fastify.post("/build-status", async (req) => { everyone(pack("build:status", { ...req.body, timestamp: Date.now() })); return { status: "ok" };});
// *** FF1 Art Computer Proxy ***// Proxies displayPlaylist commands to FF1 via direct connection or cloud relayconst FF1_RELAY_URL = "https://artwork-info.feral-file.workers.dev/api/cast";
// Load FF1 config from machines.jsonfunction getFF1Config() { try { const machinesPath = path.resolve(process.cwd(), "../aesthetic-computer-vault/machines.json"); const machines = JSON.parse(fs.readFileSync(machinesPath, "utf8")); return machines.machines?.["ff1-dvveklza"] || null; } catch (e) { log("⚠️ Could not load FF1 config from machines.json:", e.message); return null; }}
// Execute FF1 cast via SSH through MacBook (for devcontainer)async function castViaSSH(ff1Config, payload) { const { exec } = await import("child_process"); const { promisify } = await import("util"); const execAsync = promisify(exec); const ip = ff1Config.ip; const port = ff1Config.port || 1111; const payloadJson = JSON.stringify(payload).replace(/'/g, "'\\''"); // Escape for shell // SSH through MacBook to reach FF1 on local network const sshCmd = `ssh -o ConnectTimeout=5 jas@host.docker.internal "curl -s --connect-timeout 5 -X POST -H 'Content-Type: application/json' http://${ip}:${port}/api/cast -d '${payloadJson}'"`; log(`📡 FF1 cast via SSH: http://${ip}:${port}/api/cast`); const { stdout, stderr } = await execAsync(sshCmd, { timeout: 15000 }); if (stderr && !stdout) { throw new Error(stderr); } try { return JSON.parse(stdout); } catch { return { raw: stdout }; }}
fastify.post("/ff1/cast", async (req, reply) => { reply.header("Access-Control-Allow-Origin", "*"); reply.header("Access-Control-Allow-Methods", "POST, OPTIONS"); reply.header("Access-Control-Allow-Headers", "Content-Type"); const { topicID, apiKey, command, request, useDirect } = req.body || {}; const ff1Config = getFF1Config(); // Build the DP-1 payload const payload = { command: command || "displayPlaylist", request: request || {} }; // Strategy 1: Try direct connection via SSH tunnel (in dev mode) if (dev && ff1Config?.ip) { try { const result = await castViaSSH(ff1Config, payload); return { success: true, method: "direct-ssh", response: result }; } catch (sshErr) { log(`⚠️ FF1 SSH cast failed: ${sshErr.message}`); // Fall through to cloud relay } } // Strategy 2: Try direct connection (if useDirect or localhost tunnel is running) if (useDirect) { const deviceUrl = `http://localhost:1111/api/cast`; try { log(`📡 FF1 direct cast to ${deviceUrl}`); const directResponse = await fetch(deviceUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: AbortSignal.timeout(5000), // 5s timeout }); if (directResponse.ok) { const result = await directResponse.json(); return { success: true, method: "direct", response: result }; } log(`⚠️ FF1 direct cast failed: ${directResponse.status}`); } catch (directErr) { log(`⚠️ FF1 direct connection failed: ${directErr.message}`); } } // Strategy 3: Use cloud relay with topicID const relayTopicId = topicID || ff1Config?.topicId; if (!relayTopicId) { reply.status(400); return { success: false, error: "No topicID provided and no FF1 config found. Get topicID from your FF1 app settings." }; } const relayUrl = `${FF1_RELAY_URL}?topicID=${encodeURIComponent(relayTopicId)}`; try { log(`☁️ FF1 relay cast to ${relayUrl}`); const headers = { "Content-Type": "application/json" }; if (apiKey || ff1Config?.apiKey) { headers["API-KEY"] = apiKey || ff1Config?.apiKey; } const relayResponse = await fetch(relayUrl, { method: "POST", headers, body: JSON.stringify(payload), signal: AbortSignal.timeout(10000), // 10s timeout }); const responseText = await relayResponse.text(); let responseData; try { responseData = JSON.parse(responseText); } catch { responseData = { raw: responseText }; } if (!relayResponse.ok) { // Check if relay is down (404 or Cloudflare errors) if (relayResponse.status === 404 || responseText.includes("error code:")) { reply.status(503); return { success: false, error: "FF1 cloud relay is unavailable", hint: "The Feral File relay service appears to be down. Use ac-ff1 tunnel for local development.", details: responseData }; } reply.status(relayResponse.status); return { success: false, error: `FF1 relay error: ${relayResponse.status}`, details: responseData }; } return { success: true, method: "relay", response: responseData }; } catch (relayErr) { reply.status(500); return { success: false, error: relayErr.message }; }});
// FF1 CORS preflightfastify.options("/ff1/cast", async (req, reply) => { reply.header("Access-Control-Allow-Origin", "*"); reply.header("Access-Control-Allow-Methods", "POST, OPTIONS"); reply.header("Access-Control-Allow-Headers", "Content-Type"); return "";});
// *** Chat Log Endpoint (for system logs from other services) ***fastify.post("/chat/log", async (req, reply) => { const host = req.headers.host; // Determine which chat instance based on a header or default to chat-system const chatHost = req.headers["x-chat-instance"] || "chat-system.aesthetic.computer"; const instance = chatManager.getInstance(chatHost); if (!instance) { reply.status(404); return { status: "error", message: "Unknown chat instance" }; } const result = await chatManager.handleLog(instance, req.body, req.headers.authorization); reply.status(result.status); return result.body;});
// *** Chat Status Endpoint ***fastify.get("/chat/status", async (req) => { return chatManager.getStatus();});
const PROFILE_SECRET_CACHE_MS = 60 * 1000;let profileSecretCacheValue = null;let profileSecretCacheAt = 0;
function pickProfileStreamSecret(record) { if (!record || typeof record !== "object") return null; const candidates = [ record.secret, record.token, record.profileSecret, record.value, ]; for (const raw of candidates) { if (!raw) continue; const value = `${raw}`.trim(); if (value) return value; } return null;}
function profileSecretsMatch(expected, provided) { if (!expected || !provided) return false; const left = Buffer.from(expected); const right = Buffer.from(provided); if (left.length !== right.length) return false; try { return crypto.timingSafeEqual(left, right); } catch (_) { return false; }}
async function resolveProfileStreamSecret() { const now = Date.now(); if (profileSecretCacheAt && now - profileSecretCacheAt < PROFILE_SECRET_CACHE_MS) { return profileSecretCacheValue; }
let resolved = null; try { if (chatManager?.db) { const record = await chatManager.db .collection("secrets") .findOne({ _id: "profile-stream" }); resolved = pickProfileStreamSecret(record); } } catch (err) { error("👤 Could not load profile-stream secret from MongoDB:", err?.message || err); }
if (!resolved) { const envSecret = `${process.env.PROFILE_STREAM_SECRET || ""}`.trim(); resolved = envSecret || null; }
profileSecretCacheValue = resolved; profileSecretCacheAt = now; return profileSecretCacheValue;}
// *** Profile Stream Event Ingest ***// Accepts server-to-server profile events from Netlify functions.fastify.post("/profile-event", async (req, reply) => { try { const expectedSecret = await resolveProfileStreamSecret(); const providedSecret = `${req.headers["x-profile-secret"] || ""}`.trim() || null; if (expectedSecret && !profileSecretsMatch(expectedSecret, providedSecret)) { reply.status(401); return { ok: false, error: "Unauthorized" }; }
const body = req.body || {}; const handle = body.handle; const handleKey = normalizeProfileHandle(handle); if (!handleKey) { reply.status(400); return { ok: false, error: "Missing or invalid handle" }; }
if (body.event && typeof body.event === "object") { emitProfileActivity(handleKey, body.event); }
if (body.counts && typeof body.counts === "object") { broadcastProfileStream(handleKey, "counts:update", { handle: handleKey, counts: body.counts, }); }
if (body.countsDelta && typeof body.countsDelta === "object") { emitProfileCountDelta(handleKey, body.countsDelta); }
if (body.presence && typeof body.presence === "object") { broadcastProfileStream(handleKey, "presence:update", { handle: handleKey, reason: body.reason || "external", changed: Array.isArray(body.changed) ? body.changed : [], presence: body.presence, }); }
return { ok: true }; } catch (err) { error("👤 profile-event ingest failed:", err); reply.status(500); return { ok: false, error: err.message }; }});
// *** Live Reload of Pieces in Development ***if (dev) { fastify.post("/reload", async (req) => { everyone(pack("reload", req.body, "pieces")); return { msg: "Reload request sent!", body: req.body }; }); // Jump to a specific piece (navigate) fastify.post("/jump", async (req) => { const { piece } = req.body; // Broadcast to all browser clients everyone(pack("jump", { piece }, "pieces")); // Send direct message to VSCode extension clients vscodeClients.forEach(client => { if (client?.readyState === WebSocket.OPEN) { client.send(pack("vscode:jump", { piece }, "vscode")); } }); return { msg: "Jump request sent!", piece, vscodeConnected: vscodeClients.size > 0 }; }); // GET /devices - List all connected clients with metadata and names fastify.get("/devices", async () => { const clientList = getClientStatus(); // Enhance with device names and letters const enhanced = clientList.map((c, index) => ({ ...c, letter: getDeviceLetter(c.id), deviceName: deviceNames[c.ip]?.name || null, deviceGroup: deviceNames[c.ip]?.group || null, })); return { devices: enhanced, host: { name: DEV_HOST_NAME, ip: DEV_LAN_IP }, timestamp: Date.now() }; }); // GET /dev-info - Get dev host info for client overlay fastify.get("/dev-info", async (req, reply) => { // Add CORS headers for cross-origin requests from main site reply.header("Access-Control-Allow-Origin", "*"); reply.header("Access-Control-Allow-Methods", "GET"); return { host: DEV_HOST_NAME, ip: DEV_LAN_IP, mode: "LAN Dev", timestamp: Date.now() }; }); // POST /jump/:target - Targeted jump (by ID, IP, handle, or device name) fastify.post("/jump/:target", async (req) => { const { target } = req.params; const { piece, ahistorical, alias } = req.body; const targeted = targetClients(target); if (targeted.length === 0) { return { error: "No matching device", target }; } targeted.forEach(({ ws }) => { ws.send(pack("jump", { piece, ahistorical, alias }, "pieces")); }); return { msg: "Targeted jump sent", piece, count: targeted.length, targets: targeted.map(t => t.id) }; }); // POST /reload/:target - Targeted reload fastify.post("/reload/:target", async (req) => { const { target } = req.params; const targeted = targetClients(target); targeted.forEach(({ ws }) => { ws.send(pack("reload", req.body, "pieces")); }); return { msg: "Targeted reload sent", count: targeted.length }; }); // POST /piece-reload/:target - Targeted KidLisp reload fastify.post("/piece-reload/:target", async (req) => { const { target } = req.params; const { source, createCode, authToken } = req.body; const targeted = targetClients(target); targeted.forEach(({ ws }) => { ws.send(pack("piece-reload", { source, createCode, authToken }, "kidlisp")); }); return { msg: "Targeted piece-reload sent", count: targeted.length }; }); // POST /device/name - Set a friendly name for a device by IP fastify.post("/device/name", async (req) => { const { ip, name, group } = req.body; if (!ip) return { error: "IP required" }; const cleanIp = ip.replace('::ffff:', ''); if (name) { deviceNames[cleanIp] = { name, group: group || null, updatedAt: Date.now() }; } else { delete deviceNames[cleanIp]; } saveDeviceNames(); // Notify the device of its new name const targeted = targetClients(cleanIp); targeted.forEach(({ ws }) => { ws.send(pack("dev:identity", { name, host: DEV_HOST_NAME, hostIp: DEV_LAN_IP, mode: "LAN Dev" }, "dev")); }); return { msg: name ? "Device named" : "Device name cleared", ip: cleanIp, name, notified: targeted.length }; }); // GET /device/names - List all device names fastify.get("/device/names", async () => { return { names: deviceNames }; });}
// *** HTTP Server Initialization ***
// Track UDP channels manually (geckos.io doesn't expose this)const udpChannels = {};
// 🩰 Initialize geckos.io BEFORE server starts listening// Configure for devcontainer/Docker environment:// - iceServers: Use local TURN server for relay (required in Docker/devcontainer)// - portRange: constrain UDP to small range that can be exposed from container// - cors: allow from any origin in dev mode
// Detect external IP for TURN server (browsers need to reach TURN from outside container)// In devcontainer, we expose ports to the host, so use the host's LAN IP// Priority: TURN_HOST env var > DEV_LAN_IP > localhostconst getExternalTurnHost = () => { // Check for explicitly set TURN host if (process.env.TURN_HOST) return process.env.TURN_HOST; // Use the DEV_LAN_IP if available (detected earlier) if (DEV_LAN_IP) return DEV_LAN_IP; // Fallback to localhost (won't work for external clients but ok for local testing) return 'localhost';};
const turnHost = getExternalTurnHost();console.log("🩰 TURN server host for ICE:", turnHost);
const devIceServers = [ { urls: `stun:${turnHost}:3478` }, { urls: `turn:${turnHost}:3478`, username: 'aesthetic', credential: 'computer123' },];const prodIceServers = [ { urls: 'stun:stun.l.google.com:19302' }, // TODO: Add production TURN server];
const io = geckos({ iceServers: dev ? devIceServers : prodIceServers, // Force relay-only mode in dev to work through container networking // Direct UDP won't work from host browser to container internal IP iceTransportPolicy: dev ? 'relay' : 'all', portRange: { min: 10000, max: 10007, }, cors: { allowAuthorization: true, origin: dev ? "*" : (req) => { const allowed = ["https://aesthetic.computer", "https://notepat.com", "https://kidlisp.com", "https://pj.kidlisp.com"]; const reqOrigin = req.headers?.origin; return allowed.includes(reqOrigin) ? reqOrigin : allowed[0]; }, },});io.addServer(server); // Hook up to the HTTP Server - must be before listen()console.log("🩰 Geckos.io server attached to fastify server (UDP ports 10000-10007)");
const start = async () => { try { if (dev) { fastify.listen({ host: "0.0.0.0", // ip.address(), port: info.port, }); } else { fastify.listen({ host: "0.0.0.0", port: info.port }); } } catch (err) { fastify.log.error(err); process.exit(1); }};
await start();
// *** Status Page Data Collection ***
// Get unified client status - user-centric viewfunction getClientStatus() { const identityMap = new Map(); // Map by identity (handle or user or IP) // Helper to get identity key for a client const getIdentityKey = (client) => { // Priority: handle > user > IP (for grouping same person) if (client.handle) return `handle:${client.handle}`; if (client.user) return `user:${client.user}`; if (client.ip) return `ip:${client.ip}`; return null; }; // Process all WebSocket connections Object.keys(connections).forEach((id) => { const client = clients[id] || {}; const ws = connections[id]; const identityKey = getIdentityKey(client); if (!identityKey) return; // Skip if no identity info if (!identityMap.has(identityKey)) { identityMap.set(identityKey, { handle: client.handle || null, location: client.location || null, ip: client.ip || null, geo: client.geo || null, connectionIds: { websocket: [], udp: [] }, protocols: { websocket: false, udp: false }, connections: { websocket: [], udp: [] } }); } const identity = identityMap.get(identityKey); // Update with latest info if (client.handle && !identity.handle) identity.handle = client.handle; if (client.location) identity.location = client.location; if (client.ip && !identity.ip) identity.ip = client.ip; if (client.geo && !identity.geo) identity.geo = client.geo; identity.connectionIds.websocket.push(parseInt(id)); identity.protocols.websocket = true; identity.connections.websocket.push({ id: parseInt(id), alive: ws.isAlive || false, readyState: ws.readyState, ping: ws.lastPing || null, codeChannel: findCodeChannel(parseInt(id)), worlds: getWorldMemberships(parseInt(id)) }); }); // Process all UDP connections Object.keys(udpChannels).forEach((id) => { const client = clients[id] || {}; const udp = udpChannels[id]; const identityKey = getIdentityKey(client); if (!identityKey) return; // Skip if no identity info if (!identityMap.has(identityKey)) { identityMap.set(identityKey, { handle: client.handle || null, location: client.location || null, ip: client.ip || null, geo: client.geo || null, connectionIds: { websocket: [], udp: [] }, protocols: { websocket: false, udp: false }, connections: { websocket: [], udp: [] } }); } const identity = identityMap.get(identityKey); // Update with latest info if (client.handle && !identity.handle) identity.handle = client.handle; if (client.location) identity.location = client.location; if (client.ip && !identity.ip) identity.ip = client.ip; if (client.geo && !identity.geo) identity.geo = client.geo; identity.connectionIds.udp.push(id); identity.protocols.udp = true; identity.connections.udp.push({ id: id, connectedAt: udp.connectedAt, state: udp.state || 'unknown' }); }); // Convert to array and add summary info return Array.from(identityMap.values()).map(identity => { const wsCount = identity.connectionIds.websocket.length; const udpCount = identity.connectionIds.udp.length; const totalConnections = wsCount + udpCount; return { handle: identity.handle, location: identity.location, ip: identity.ip, geo: identity.geo, protocols: identity.protocols, connectionCount: { websocket: wsCount, udp: udpCount, total: totalConnections }, // Simplified connection info - just take first of each type for display websocket: identity.connections.websocket.length > 0 ? identity.connections.websocket[0] : null, udp: identity.connections.udp.length > 0 ? identity.connections.udp[0] : null, multipleTabs: totalConnections > 1 }; });}
function getWorldMemberships(connectionId) { const worlds = []; Object.keys(worldClients).forEach(piece => { if (worldClients[piece][connectionId]) { worlds.push({ piece, handle: worldClients[piece][connectionId].handle, showing: worldClients[piece][connectionId].showing, ghost: worldClients[piece][connectionId].ghost || false, }); } }); return worlds;}
function findCodeChannel(connectionId) { for (const [channel, subscribers] of Object.entries(codeChannels)) { if (subscribers.has(connectionId)) return channel; } return null;}
function getFullStatus() { const clientList = getClientStatus(); // Get chat status with recent messages const chatStatus = chatManager.getStatus(); const chatWithMessages = chatStatus.map(instance => { // Don't expose sotce chat messages — it's a paid subscriber network. const isSotce = instance.name === "chat-sotce"; const recentMessages = (!isSotce && instance.messages > 0) ? chatManager.getRecentMessages(instance.host, 5) : []; return { ...instance, recentMessages }; }); // Filter old errors const cutoff = Date.now() - ERROR_RETENTION_MS; const recentErrors = errorLog.filter(e => new Date(e.timestamp).getTime() > cutoff); return { timestamp: Date.now(), server: { uptime: process.uptime(), environment: dev ? "development" : "production", port: info.port, }, totals: { websocket: wss.clients.size, udp: Object.keys(udpChannels).length, unique_clients: clientList.length }, clients: clientList, chat: chatWithMessages, errors: recentErrors.slice(-20).reverse() // Most recent first };}
// *** Socket Server Initialization ***// #region socketlet wss;let connections = {}; // All active WebSocket connections.const worldClients = {}; // All connected 🧒 to a space like `field`.
let connectionId = 0; // TODO: Eventually replace with a username arrived at through// a client <-> server authentication function.
wss = new WebSocketServer({ server });log( `🤖 session.aesthetic.computer (${ dev ? "Development" : "Production" }) socket: wss://${ip.address()}:${info.port}`,);
// *** Status Page Routes (defined after wss initialization) ***// Status JSON endpointfastify.get("/status", async (request, reply) => { return getFullStatus();});
// Status dashboard HTML at rootfastify.get("/", async (request, reply) => { reply.type("text/html"); return `<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="robots" content="noindex, nofollow"> <title>session-server</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: monospace; background: #000; color: #0f0; padding: 1.5rem; line-height: 1.5; } .header { border-bottom: 1px solid #333; padding-bottom: 1rem; margin-bottom: 1.5rem; } .header h1 { color: #0ff; font-size: 1.2rem; } .header .status { color: #888; font-size: 0.9rem; margin-top: 0.5rem; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; } @media (max-width: 900px) { .grid { grid-template-columns: 1fr; } } .section { background: #0a0a0a; border: 1px solid #222; border-radius: 4px; padding: 1rem; } .section h2 { color: #0ff; font-size: 0.95rem; margin-bottom: 0.75rem; border-bottom: 1px solid #222; padding-bottom: 0.5rem; } .client { background: #111; border-left: 3px solid #0f0; padding: 0.75rem; margin-bottom: 0.75rem; } .name { color: #0ff; font-weight: bold; } .ping { color: yellow; } .detail { color: #888; margin-top: 0.2rem; font-size: 0.85rem; } .empty { color: #555; font-style: italic; } .chat-instance { background: #111; border-left: 3px solid #f0f; padding: 0.75rem; margin-bottom: 0.75rem; } .chat-instance.offline { border-left-color: #f00; opacity: 0.6; } .chat-instance .name { color: #f0f; } .chat-msg { background: #0a0a0a; padding: 0.4rem 0.6rem; margin-top: 0.4rem; font-size: 0.8rem; border-radius: 3px; } .chat-msg .from { color: #0ff; } .chat-msg .text { color: #aaa; } .chat-msg .time { color: #555; font-size: 0.75rem; } .error-log { background: #1a0000; border-left: 3px solid #f00; padding: 0.5rem; margin-bottom: 0.5rem; font-size: 0.8rem; } .error-log .time { color: #555; } .error-log .msg { color: #f66; } .warn-log { background: #1a1a00; border-left: 3px solid #ff0; } .warn-log .msg { color: #ff6; } .no-errors { color: #0f0; font-style: italic; } .tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; } .tab { padding: 0.4rem 0.8rem; background: #111; border: 1px solid #333; color: #888; cursor: pointer; border-radius: 3px; font-family: monospace; font-size: 0.85rem; } .tab.active { background: #0f0; color: #000; border-color: #0f0; } .tab-content { display: none; } .tab-content.active { display: block; } </style></head><body> <div class="header"> <h1>🧩 session-server</h1> <div class="status"> <span id="ws-status">🔴</span> | Uptime: <span id="uptime">--</span> | Online: <span id="client-count">0</span> | Chat: <span id="chat-count">0</span> </div> </div> <div class="tabs"> <button class="tab active" data-tab="overview">Overview</button> <button class="tab" data-tab="chat">💬 Chat</button> <button class="tab" data-tab="errors">⚠️ Errors</button> </div> <div id="overview" class="tab-content active"> <div class="grid"> <div class="section"> <h2>🧑💻 Connected Clients</h2> <div id="clients"></div> </div> <div class="section"> <h2>💬 Chat Instances</h2> <div id="chat-status"></div> </div> </div> </div> <div id="chat" class="tab-content"> <div class="grid"> <div class="section" id="chat-system-section"> <h2>💬 chat-system</h2> <div id="chat-system-messages"></div> </div> <div class="section" id="chat-clock-section"> <h2>🕐 chat-clock</h2> <div id="chat-clock-messages"></div> </div>
</div> </div> <div id="errors" class="tab-content"> <div class="section"> <h2>⚠️ Recent Errors & Warnings</h2> <div id="error-log"></div> </div> </div>
<script> // Tab switching document.querySelectorAll('.tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); tab.classList.add('active'); document.getElementById(tab.dataset.tab).classList.add('active'); }); }); const ws = new WebSocket(\`\${location.protocol === 'https:' ? 'wss:' : 'ws:'}//\${location.host}/status-stream\`); ws.onopen = () => { document.getElementById('ws-status').innerHTML = '🟢'; }; ws.onclose = () => { document.getElementById('ws-status').innerHTML = '🔴'; setTimeout(() => location.reload(), 2000); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'status') update(data.data); }; function formatTime(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); return d.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } function escapeHtml(str) { if (!str) return ''; return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); } function update(s) { const hrs = Math.floor(s.server.uptime / 3600); const min = Math.floor((s.server.uptime % 3600) / 60); document.getElementById('uptime').textContent = \`\${hrs}h \${min}m\`; document.getElementById('client-count').textContent = s.totals.unique_clients; // Chat instance count const totalChatters = s.chat ? s.chat.reduce((sum, c) => sum + c.connections, 0) : 0; document.getElementById('chat-count').textContent = totalChatters; // Clients section const clientsHtml = s.clients.length === 0 ? '<div class="empty">Nobody online</div>' : s.clients.map(c => { let out = '<div class="client">'; out += '<div class="name">'; out += escapeHtml(c.handle) || '(anonymous)'; if (c.multipleTabs && c.connectionCount.total > 1) out += \` (×\${c.connectionCount.total})\`; if (c.websocket?.ping) out += \` <span class="ping">(\${c.websocket.ping}ms)</span>\`; out += '</div>'; if (c.location && c.location !== '*keep-alive*') out += \`<div class="detail">📍 \${escapeHtml(c.location)}</div>\`; if (c.geo) { let geo = '🗺️ '; if (c.geo.city) geo += c.geo.city + ', '; if (c.geo.region) geo += c.geo.region + ', '; geo += c.geo.country; out += \`<div class="detail">\${geo}</div>\`; } else if (c.ip) { out += \`<div class="detail">🌐 \${c.ip}</div>\`; } if (c.websocket?.worlds?.length > 0) { const w = c.websocket.worlds[0]; out += \`<div class="detail">🌍 \${escapeHtml(w.piece)}\`; if (w.showing) out += \` (viewing \${escapeHtml(w.showing)})\`; if (w.ghost) out += ' 👻'; out += '</div>'; } const p = []; if (c.protocols.websocket) p.push(c.connectionCount.websocket > 1 ? \`ws×\${c.connectionCount.websocket}\` : 'ws'); if (c.protocols.udp) p.push(c.connectionCount.udp > 1 ? \`udp×\${c.connectionCount.udp}\` : 'udp'); if (p.length) out += \`<div class="detail" style="opacity:0.5">\${p.join(' + ')}</div>\`; out += '</div>'; return out; }).join(''); document.getElementById('clients').innerHTML = clientsHtml; // Chat status section (overview) if (s.chat) { const chatHtml = s.chat.map(c => { const isOnline = c.messages >= 0; return \`<div class="chat-instance \${isOnline ? '' : 'offline'}"> <div class="name">\${escapeHtml(c.name)} \${isOnline ? '🟢' : '🔴'}</div> <div class="detail">🧑🤝🧑 \${c.connections} connected</div> <div class="detail">💾 \${c.messages} messages loaded</div> </div>\`; }).join(''); document.getElementById('chat-status').innerHTML = chatHtml; } else { document.getElementById('chat-status').innerHTML = '<div class="empty">Chat not initialized</div>'; } // Chat messages (detailed view) if (s.chat) { s.chat.forEach(c => { const name = c.name.replace('chat-', ''); const el = document.getElementById(\`chat-\${name}-messages\`) || document.getElementById(\`chat-\${c.name}-messages\`); if (el && c.recentMessages) { const msgsHtml = c.recentMessages.length === 0 ? '<div class="empty">No recent messages</div>' : c.recentMessages.map(m => \`<div class="chat-msg"> <span class="from">\${escapeHtml(m.from)}</span> <span class="text">\${escapeHtml(m.text)}</span> <span class="time">\${formatTime(m.when)}</span> </div>\`).join(''); el.innerHTML = msgsHtml; } }); } // Error log if (s.errors && s.errors.length > 0) { const errHtml = s.errors.map(e => \`<div class="\${e.level === 'error' ? 'error-log' : 'warn-log error-log'}"> <span class="time">[\${formatTime(e.timestamp)}]</span> <span class="msg">\${escapeHtml(e.message)}</span> </div>\`).join(''); document.getElementById('error-log').innerHTML = errHtml; } else { document.getElementById('error-log').innerHTML = '<div class="no-errors">✅ No errors in the last hour</div>'; } } </script></body></html>`;});
// Pack messages into a simple object protocol of `{type, content}`.function pack(type, content, id) { return JSON.stringify({ type, content, id });}
// Enable ping-pong behavior to keep connections persistently tracked.// (In the future could just tie connections to logged in users or// persistent tokens to keep persistence.)const interval = setInterval(function ping() { wss.clients.forEach((client) => { if (client.isAlive === false) { return client.terminate(); } client.isAlive = false; client.pingStart = Date.now(); // Start ping timer client.ping(); });}, 15000); // 15 second pings from server before termination.
wss.on("close", function close() { clearInterval(interval); connections = {};});
// Construct the server.wss.on("connection", async (ws, req) => { const connectionInfo = { url: req.url, host: req.headers.host, origin: req.headers.origin, userAgent: req.headers['user-agent'], remoteAddress: req.socket.remoteAddress, }; log('🔌 WebSocket connection received:', JSON.stringify(connectionInfo, null, 2)); log('🔌 Total wss.clients.size:', wss.clients.size); log('🔌 Current connections count:', Object.keys(connections).length); // Route status dashboard WebSocket connections separately if (req.url === '/status-stream') { log('📊 Status dashboard viewer connected from:', req.socket.remoteAddress); statusClients.add(ws); // Mark as dashboard viewer (don't add to game clients) ws.isDashboardViewer = true; // Send initial state ws.send(JSON.stringify({ type: 'status', data: getFullStatus(), })); ws.on('close', () => { log('📊 Status dashboard viewer disconnected'); statusClients.delete(ws); }); ws.on('error', (err) => { error('📊 Status dashboard error:', err); statusClients.delete(ws); }); return; // Don't process as a game client }
// Route targeted profile stream connections if (req.url?.startsWith('/profile-stream')) { let requestedHandle = null; try { const parsedUrl = new URL(req.url, 'http://localhost'); requestedHandle = parsedUrl.searchParams.get('handle'); } catch (err) { error('👤 Invalid profile-stream URL:', err); }
const key = addProfileStreamClient(ws, requestedHandle); if (!key) { ws.send( JSON.stringify({ type: 'profile:error', data: { message: 'Missing or invalid handle query param.' }, }), ); try { ws.close(); } catch (_) {} return; }
log('👤 Profile stream viewer connected for:', key, 'from:', req.socket.remoteAddress);
ws.on('close', () => { removeProfileStreamClient(ws); log('👤 Profile stream viewer disconnected for:', key); });
ws.on('error', (err) => { error('👤 Profile stream error:', err); removeProfileStreamClient(ws); });
return; // Don't process as a game client } // Route chat connections to ChatManager based on host const host = req.headers.host; if (chatManager.isChatHost(host)) { log('💬 Chat client connection from:', host); chatManager.handleConnection(ws, req); return; // Don't process as a game client } // Route AC Machines connections — device monitoring & remote commands if (req.url.startsWith('/machines')) { const urlParams = new URL(req.url, 'http://localhost').searchParams; const role = urlParams.get('role') || 'device'; const token = urlParams.get('token') || ''; const machineId = urlParams.get('machineId') || '';
if (role === 'viewer') { // Browser dashboard viewer — verify AC auth token via Auth0 const authUser = await verifyACToken(token); if (!authUser?.sub) { ws.close(4001, 'Unauthorized'); return; } const userSub = authUser.sub; const userHandle = authUser.nickname || authUser.name || null;
log(`Machines viewer connected: ${userHandle || userSub}`);
if (!machinesViewers.has(userSub)) machinesViewers.set(userSub, new Set()); machinesViewers.get(userSub).add(ws);
// Send initial state: all online machines for this user const userMachines = []; for (const [mid, device] of machinesDevices) { if (device.user === userSub) { userMachines.push({ machineId: mid, ...device.info, status: "online", lastHeartbeat: device.lastHeartbeat, }); } } ws.send(JSON.stringify({ type: "machines-state", machines: userMachines }));
// Handle viewer → device commands ws.on('message', (data) => { try { const msg = JSON.parse(data.toString()); if (msg.type === "command" && msg.machineId) { const device = machinesDevices.get(msg.machineId); if (device && device.user === userSub && device.ws.readyState === WebSocket.OPEN) { const commandId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6); device.ws.send(JSON.stringify({ type: "command", command: msg.cmd, commandId, target: msg.args?.target || msg.args?.piece || undefined, })); log(`Command '${msg.cmd}' → ${msg.machineId} (${commandId})`); } } // Swank eval: forward CL expression to device for evaluation if (msg.type === "swank:eval" && msg.machineId && msg.expr) { const device = machinesDevices.get(msg.machineId); if (device && device.user === userSub && device.ws.readyState === WebSocket.OPEN) { const evalId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6); device.ws.send(JSON.stringify({ type: "swank:eval", expr: msg.expr, evalId, })); log(`🔮 Swank eval → ${msg.machineId}: ${msg.expr.slice(0, 60)}`); } } } catch (e) { error('🖥️ Machines viewer message error:', e); } });
ws.on('close', () => { log(`🖥️ Machines viewer disconnected: ${userHandle || userSub}`); const viewers = machinesViewers.get(userSub); if (viewers) { viewers.delete(ws); if (viewers.size === 0) machinesViewers.delete(userSub); } });
ws.on('error', (err) => { error('🖥️ Machines viewer error:', err); const viewers = machinesViewers.get(userSub); if (viewers) { viewers.delete(ws); if (viewers.size === 0) machinesViewers.delete(userSub); } });
} else { // Device connection const tokenPayload = await verifyMachineToken(token); const userSub = tokenPayload?.sub || null; const userHandle = tokenPayload?.handle || null; const linked = !!tokenPayload;
log(`📡 Machines device connected: ${machineId} (${linked ? userHandle : 'unlinked'})`);
machinesDevices.set(machineId, { ws, user: userSub, handle: userHandle, machineId, linked, info: {}, lastHeartbeat: Date.now(), });
if (userSub) { broadcastToMachineViewers(userSub, { type: "device-connected", machineId, linked }); }
ws.on('message', async (data) => { try { const msg = JSON.parse(data.toString()); const device = machinesDevices.get(machineId); if (!device) return;
switch (msg.type) { case "register": device.info = { version: msg.version, buildName: msg.buildName, gitHash: msg.gitHash, buildTs: msg.buildTs, hw: msg.hw, ip: msg.ip, wifiSSID: msg.wifiSSID, hostname: msg.hostname, label: msg.label, currentPiece: msg.currentPiece || "notepat", }; device.lastHeartbeat = Date.now(); try { await upsertMachine(userSub, machineId, device.info); } catch (e) { error("📡 upsert:", e.message); } if (userSub) broadcastToMachineViewers(userSub, { type: "machine-registered", machineId, ...device.info, status: "online" }); break;
case "heartbeat": device.lastHeartbeat = Date.now(); device.info.uptime = msg.uptime; device.info.currentPiece = msg.currentPiece || device.info.currentPiece; device.info.battery = msg.battery; device.info.charging = msg.charging; device.info.fps = msg.fps; try { await updateMachineHeartbeat(userSub, machineId, msg.uptime, device.info.currentPiece); } catch (e) { error("📡 heartbeat:", e.message); } if (userSub) broadcastToMachineViewers(userSub, { type: "heartbeat", machineId, uptime: msg.uptime, currentPiece: device.info.currentPiece, battery: msg.battery, charging: msg.charging, fps: msg.fps, timestamp: Date.now(), }); break;
case "log": try { await insertMachineLog(userSub, machineId, msg); } catch (e) { error("📡 log insert:", e.message); } if (userSub) { const logMessage = msg.message || (typeof msg.data === "string" ? msg.data : JSON.stringify(msg.data)); broadcastToMachineViewers(userSub, { type: "log", machineId, level: msg.logType === "crash" ? "error" : (msg.level || "info"), message: logMessage, logType: msg.logType || "log", data: msg.data || null, when: msg.when || new Date().toISOString(), }); } break;
case "command-ack": case "command-response": if (userSub) broadcastToMachineViewers(userSub, { type: msg.type, machineId, commandId: msg.commandId, command: msg.command, data: msg.data }); break;
case "swank:result": // Forward Swank eval result from device to viewer if (userSub) broadcastToMachineViewers(userSub, { type: "swank:result", machineId, evalId: msg.evalId, ok: msg.ok, result: msg.result, }); break; } } catch (e) { error('📡 Machines device message error:', e); } });
ws.on('close', async () => { log(`📡 Machines device disconnected: ${machineId}`); machinesDevices.delete(machineId); if (userSub) { broadcastToMachineViewers(userSub, { type: "status-change", machineId, status: "offline" }); try { await setMachineOffline(userSub, machineId); } catch (e) { error("📡 offline:", e.message); } } });
ws.on('error', (err) => { error(`📡 Machines device error (${machineId}):`, err); machinesDevices.delete(machineId); }); }
return; // Don't process as a game client }
// Route socklogs connections - devices sending logs and viewers subscribing if (req.url.startsWith('/socklogs')) { const urlParams = new URL(req.url, 'http://localhost').searchParams; const role = urlParams.get('role') || 'device'; // 'device' or 'viewer' const deviceId = urlParams.get('deviceId') || `device-${Date.now()}`; if (role === 'viewer') { // Viewer wants to see logs from devices log('👁️ SockLogs viewer connected'); socklogsViewers.add(ws); // Send current status ws.send(JSON.stringify({ type: 'status', ...socklogsStatus() })); ws.on('close', () => { log('👁️ SockLogs viewer disconnected'); socklogsViewers.delete(ws); }); ws.on('error', (err) => { error('👁️ SockLogs viewer error:', err); socklogsViewers.delete(ws); }); } else { // Device sending logs log(`📱 SockLogs device connected: ${deviceId}`); socklogsDevices.set(deviceId, { ws, logCount: 0, lastLog: null, connectedAt: Date.now() }); // Notify viewers of new device for (const viewer of socklogsViewers) { if (viewer.readyState === WebSocket.OPEN) { viewer.send(JSON.stringify({ type: 'device-connected', deviceId, status: socklogsStatus() })); } } ws.on('message', (data) => { try { const msg = JSON.parse(data.toString()); if (msg.type === 'log') { const device = socklogsDevices.get(deviceId); if (device) { device.logCount++; device.lastLog = Date.now(); } socklogsBroadcast(deviceId, msg); } } catch (e) { error('📱 SockLogs parse error:', e); } }); ws.on('close', () => { log(`📱 SockLogs device disconnected: ${deviceId}`); socklogsDevices.delete(deviceId); // Notify viewers for (const viewer of socklogsViewers) { if (viewer.readyState === WebSocket.OPEN) { viewer.send(JSON.stringify({ type: 'device-disconnected', deviceId, status: socklogsStatus() })); } } }); ws.on('error', (err) => { error(`📱 SockLogs device error (${deviceId}):`, err); socklogsDevices.delete(deviceId); }); } return; // Don't process as a game client } log('🎮 Game client connection detected, adding to connections'); // Regular game client connection handling below const ip = req.socket.remoteAddress || "localhost"; // beautify ip ws.isAlive = true; // For checking persistence between ping-pong messages. ws.pingStart = null; // Track ping timing ws.lastPing = null; // Store last measured ping
ws.on("pong", () => { ws.isAlive = true; if (ws.pingStart) { ws.lastPing = Date.now() - ws.pingStart; ws.pingStart = null; } }); // Receive a pong and stay alive!
// Assign the conection a unique id. connections[connectionId] = ws; const id = connectionId; let codeChannel; // Used to subscribe to incoming piece code. // Initialize client record with IP and geolocation if (!clients[id]) clients[id] = {}; clients[id].websocket = true; // Clean IP and get geolocation const cleanIp = ip.replace('::ffff:', ''); clients[id].ip = cleanIp; const geo = geoip.lookup(cleanIp); if (geo) { clients[id].geo = { country: geo.country, region: geo.region, city: geo.city, timezone: geo.timezone, ll: geo.ll // [latitude, longitude] }; log(`🌍 Geolocation for ${cleanIp}:`, geo.country, geo.region, geo.city); } else { log(`🌍 No geolocation data for ${cleanIp}`); }
log("🧏 Someone joined:", `${id}:${ip}`, "Online:", wss.clients.size, "🫂"); log("🎮 Added to connections. Total game clients:", Object.keys(connections).length);
const content = { id, playerCount: wss.clients.size };
// Send a message to all other clients except this one. function others(string) { wss.clients.forEach((c) => { if (c !== ws && c?.readyState === WebSocket.OPEN) c.send(string); }); }
// Send a self-connection message back to the client. ws.send( pack( "connected", JSON.stringify({ ip, playerCount: content.playerCount }), id, ), );
// In dev mode, send device identity info for LAN overlay if (dev) { const deviceName = deviceNames[cleanIp]?.name || null; const deviceLetter = getDeviceLetter(id); const identityPayload = { name: deviceName, letter: deviceLetter, host: DEV_HOST_NAME, hostIp: DEV_LAN_IP, mode: "LAN Dev", connectionId: id, }; console.log(`📱 Sending dev:identity to ${cleanIp}:`, identityPayload); ws.send(pack("dev:identity", identityPayload, "dev")); }
// Send a join message to everyone else. others( pack( "joined", JSON.stringify({ text: `${connectionId} has joined. Connections open: ${content.playerCount}`, }), id, ), );
connectionId += 1;
// Relay all incoming messages from this client to everyone else. ws.on("message", (data) => { // Parse incoming message and attach client identifier. let msg; try { msg = JSON.parse(data.toString()); } catch (error) { console.error("📚 Failed to parse JSON:", error); return; }
// 📦 Module streaming - handle module requests before other processing if (msg.type === "module:request") { const modulePath = msg.path; const withDeps = msg.withDeps === true; // Request all dependencies too const knownHashes = msg.knownHashes || {}; // Client's cached hashes if (withDeps) { // Recursively gather all dependencies const modules = {}; let skippedCount = 0; const gatherDeps = (p, fromPath = null) => { if (modules[p] || modules[p] === null) return; // Already gathered (or marked as cached) const data = getModuleHash(p); if (!data) { // Only warn for top-level not found, not for deps (which might be optional) if (!fromPath) log(`📦 Module not found: ${p}`); return; } // Check if client already has this hash cached if (knownHashes[p] === data.hash) { modules[p] = null; // Mark as "client has it" - don't send content skippedCount++; } else { modules[p] = { hash: data.hash, content: data.content }; } // Debug: show when gathering specific important modules if (p.includes('headers') || p.includes('kidlisp')) { log(`📦 Gathering ${p} (from ${fromPath || 'top'})${knownHashes[p] === data.hash ? ' [cached]' : ''}`); } // Parse static imports from content - match ES module import/export from statements // This regex only matches valid relative imports ending in .mjs or .js // Skip commented lines by checking each line doesn't start with // const staticImportRegex = /^(?!\s*\/\/).*?(?:import|export)\s+(?:[^;]*?\s+from\s+)?["'](\.{1,2}\/[^"'\s]+\.m?js)["']/gm; let match; while ((match = staticImportRegex.exec(data.content)) !== null) { const importPath = match[1]; // Skip invalid paths if (importPath.includes('...') || importPath.length > 200) continue; // Resolve relative path const dir = path.dirname(p); const resolved = path.normalize(path.join(dir, importPath)); log(`📦 Found dep: ${p} -> ${importPath} (resolved: ${resolved})`); gatherDeps(resolved, p); } // Parse dynamic imports - import("./path") or import('./path') or import(`./path`) // Skip commented lines const dynamicImportRegex = /^(?!\s*\/\/).*?import\s*\(\s*["'`](\.{1,2}\/[^"'`\s]+\.m?js)["'`]\s*\)/gm; while ((match = dynamicImportRegex.exec(data.content)) !== null) { const importPath = match[1]; // Skip invalid paths if (importPath.includes('...') || importPath.length > 200) continue; // Resolve relative path const dir = path.dirname(p); const resolved = path.normalize(path.join(dir, importPath)); gatherDeps(resolved, p); } }; gatherDeps(modulePath); // Filter out null entries (modules client already has) and count const modulesToSend = {}; const cachedPaths = []; for (const [p, data] of Object.entries(modules)) { if (data === null) { cachedPaths.push(p); } else { modulesToSend[p] = data; } } const totalModules = Object.keys(modules).length; const sentModules = Object.keys(modulesToSend).length; if (totalModules > 0) { // Log bundle stats if (skippedCount > 0) { log(`📦 Bundle for ${modulePath}: ${sentModules}/${totalModules} sent (${skippedCount} cached)`); } else { log(`📦 Bundle for ${modulePath}: ${sentModules} modules`); } ws.send(JSON.stringify({ type: "module:bundle", entry: modulePath, modules: modulesToSend, cached: cachedPaths // Tell client which paths to use from cache })); } else { ws.send(JSON.stringify({ type: "module:error", path: modulePath, error: "Module not found" })); } } else { // Single module request (original behavior) const moduleData = getModuleHash(modulePath); if (moduleData) { ws.send(JSON.stringify({ type: "module:response", path: modulePath, hash: moduleData.hash, content: moduleData.content })); log(`📦 Module sent: ${modulePath} (${moduleData.content.length} bytes)`); } else { ws.send(JSON.stringify({ type: "module:error", path: modulePath, error: "Module not found" })); log(`📦 Module not found: ${modulePath}`); } } return; } if (msg.type === "module:check") { const modulePath = msg.path; const clientHash = msg.hash; const moduleData = getModuleHash(modulePath); if (moduleData) { ws.send(JSON.stringify({ type: "module:status", path: modulePath, changed: moduleData.hash !== clientHash, hash: moduleData.hash })); } else { ws.send(JSON.stringify({ type: "module:status", path: modulePath, changed: true, hash: null, error: "Module not found" })); } return; } if (msg.type === "module:list") { // Return list of available modules (for prefetching) const modules = [ "lib/disk.mjs", "lib/graph.mjs", "lib/num.mjs", "lib/geo.mjs", "lib/parse.mjs", "lib/help.mjs", "lib/text.mjs", "bios.mjs" ]; const moduleInfo = modules.map(p => { const data = getModuleHash(p); return data ? { path: p, hash: data.hash, size: data.content.length } : null; }).filter(Boolean); ws.send(JSON.stringify({ type: "module:list", modules: moduleInfo })); return; }
// 🎹 DAW Channel - M4L device ↔ IDE communication if (msg.type === "daw:join") { // Device (kidlisp.com/device) joining to receive code dawDevices.add(id); log(`🎹 DAW device joined: ${id} (total: ${dawDevices.size})`); ws.send(JSON.stringify({ type: "daw:joined", id })); return; } if (msg.type === "daw:code") { // IDE sending code to all connected devices log(`🎹 DAW code broadcast from ${id} to ${dawDevices.size} devices`); const codeMsg = JSON.stringify({ type: "daw:code", content: msg.content, from: id }); // Broadcast to all DAW devices for (const deviceId of dawDevices) { const deviceWs = connections[deviceId]; if (deviceWs && deviceWs.readyState === WebSocket.OPEN) { deviceWs.send(codeMsg); log(`🎹 Sent code to device ${deviceId}`); } } return; }
if (msg.type === "notepat:midi:sources") { sendNotepatMidiSources(ws); return; }
if (msg.type === "notepat:midi:subscribe") { const filter = msg.content || {}; addNotepatMidiSubscriber(id, ws, filter); return; }
if (msg.type === "notepat:midi:unsubscribe") { removeNotepatMidiSubscriber(id); if (ws.readyState === WebSocket.OPEN) { ws.send(pack("notepat:midi:unsubscribed", true, "midi-relay")); } return; }
msg.id = id; // TODO: When sending a server generated message, use a special id.
// Extract user identity and handle from ANY message that contains it if (msg.content?.user?.sub) { if (!clients[id]) clients[id] = { websocket: true }; const userSub = msg.content.user.sub; const userChanged = !clients[id].user || clients[id].user !== userSub; if (userChanged) { clients[id].user = userSub; log("🔑 User identity from", msg.type + ":", userSub.substring(0, 20) + "...", "conn:", id); } // Extract handle from message if present (e.g., location:broadcast includes it) if (msg.content.handle && (!clients[id].handle || clients[id].handle !== msg.content.handle)) { clients[id].handle = msg.content.handle; log("✅ Handle from message:", msg.content.handle, "conn:", id); emitProfilePresence(msg.content.handle, "identify", ["handle"]); } }
if (msg.type === "scream") { // Alert all connected users via redis pub/sub to the scream. log("😱 About to scream..."); const out = filter(msg.content); pub .publish("scream", out) .then((result) => { log("😱 Scream succesfully published:", result);
let piece = ""; if (out.indexOf("pond") > -1) piece = "pond"; else if (out.indexOf("field") > -1) piece = "field";
//if (!dev) { getMessaging() .send({ notification: { title: "😱 Scream", body: out, //, }, // android: { // notification: { // imageUrl: "https://aesthetic.computer/api/logo.png", // }, apns: { payload: { aps: { "mutable-content": 1, "interruption-level": "time-sensitive", // Marks as time-sensitive priority: 10, // Highest priority "content-available": 1, // Tells iOS to wake the app }, }, headers: { "apns-priority": "10", // Immediate delivery priority "apns-push-type": "alert", // Explicit push type "apns-expiration": "0", // Message won't be stored by APNs }, fcm_options: { image: "https://aesthetic.computer/api/logo.png", }, }, webpush: { headers: { image: "https://aesthetic.computer/api/logo.png", }, }, topic: "scream", data: { piece }, }) .then((response) => { log("☎️ Successfully sent notification:", response); }) .catch((error) => { log("📵 Error sending notification:", error); }); //} }) .catch((error) => { log("🙅♀️ Error publishing scream:", error); }); // Send a notification to all devices subscribed to the `scream` topic. } else if (msg.type === "code-channel:sub") { // Filter code-channel updates based on this user. codeChannel = msg.content; if (!codeChannels[codeChannel]) codeChannels[codeChannel] = new Set(); codeChannels[codeChannel].add(id); // Send current channel state to late joiners if (codeChannelState[codeChannel]) { // Note: codeChannelState stores the original msg.content object, // pack() will JSON.stringify it, so don't double-stringify here const stateMsg = pack("code", codeChannelState[codeChannel], id); send(stateMsg); log(`📥 Sent current state to late joiner on channel ${codeChannel}`); } } else if (msg.type === "code-channel:info") { // Return viewer count for a code channel const ch = msg.content; const count = codeChannels[ch]?.size || 0; send(pack("code-channel:info", { channel: ch, viewers: count }, id)); } else if (msg.type === "slide" && msg.content?.codeChannel) { // Handle slide broadcast (low-latency value updates, no state storage) const targetChannel = msg.content.codeChannel; // Don't store slide updates as state (they're transient) // Just broadcast immediately for low latency if (codeChannels[targetChannel]) { const slideMsg = pack("slide", msg.content, id); subscribers(codeChannels[targetChannel], slideMsg); } } else if (msg.type === "code" && msg.content?.codeChannel) { // Handle code broadcast to channel subscribers (for kidlisp.com pop-out sync) const targetChannel = msg.content.codeChannel; // Store the latest state for late joiners codeChannelState[targetChannel] = msg.content; if (codeChannels[targetChannel]) { // Note: msg.content is already an object, pack() will JSON.stringify it const codeMsg = pack("code", msg.content, id); subscribers(codeChannels[targetChannel], codeMsg); log(`📢 Broadcast code to channel ${targetChannel} (${codeChannels[targetChannel].size} subscribers)`); } } else if (msg.type === "login") { if (msg.content?.user?.sub) { if (!clients[id]) clients[id] = { websocket: true }; clients[id].user = msg.content.user.sub; // Fetch the user's handle from the API const userSub = msg.content.user.sub; log("🔑 Login attempt for user:", userSub.substring(0, 20) + "...", "connection:", id); fetch(`https://aesthetic.computer/handle/${encodeURIComponent(userSub)}`) .then(response => { log("📡 Handle API response status:", response.status, "for", userSub.substring(0, 20) + "..."); return response.json(); }) .then(data => { log("📦 Handle API data:", JSON.stringify(data), "for connection:", id); if (data.handle) { clients[id].handle = data.handle; log("✅ User logged in:", data.handle, `(${userSub.substring(0, 12)}...)`, "connection:", id); emitProfilePresence(data.handle, "login", ["handle", "online", "connections"]); } else { log("⚠️ User logged in (no handle in response):", userSub.substring(0, 12), "..., connection:", id); } }) .catch(err => { log("❌ Failed to fetch handle for:", userSub.substring(0, 20) + "...", "Error:", err.message); }); } } else if (msg.type === "identify") { // VSCode extension identifying itself if (msg.content?.type === "vscode") { vscodeClients.add(ws); log("✅ VSCode extension connected, conn:", id); // Send confirmation ws.send(pack("identified", { type: "vscode", id }, id)); } } else if (msg.type === "dev:log") { // 📡 Remote log forwarding from connected devices (LAN Dev mode) if (dev && msg.content) { const { level, args, deviceName, connectionId, time, queued } = msg.content; const client = clients[id]; const deviceLabel = deviceName || client?.ip || `conn:${connectionId}`; const levelEmoji = level === 'error' ? '🔴' : level === 'warn' ? '🟡' : '🔵'; const queuedTag = queued ? ' [Q]' : ''; // Format the log output const timestamp = new Date(time).toLocaleTimeString(); const argsStr = Array.isArray(args) ? args.join(' ') : String(args); console.log(`${levelEmoji} [${timestamp}] ${deviceLabel}${queuedTag}: ${argsStr}`); } } else if (msg.type === "location:broadcast") { /* sub .subscribe(`logout:broadcast:${msg.content.user.sub}`, () => { ws.send(pack(`logout:broadcast:${msg.content.user.sub}`, true, id)); }) .then(() => { log("🏃 Subscribed to logout updates from:", msg.content.user.sub); }) .catch((err) => error( "🏃 Could not unsubscribe from logout:broadcast for:", msg.content.user.sub, err, ), ); */ } else if (msg.type === "logout:broadcast:subscribe") { /* console.log("Logout broadcast:", msg.type, msg.content); pub .publish(`logout:broadcast:${msg.content.user.sub}`, "true") .then((result) => { console.log("🏃 Logout broadcast successful for:", msg.content); }) .catch((error) => { log("🙅♀️ Error publishing logout:", error); }); */ } else if (msg.type === "location:broadcast") { // Receive a slug location for this handle. if (msg.content.slug !== "*keep-alive*") { log("🗼 Location:", msg.content.slug, "Handle:", msg.content.handle, "ID:", id); } // Store handle and location for this client if (!clients[id]) clients[id] = { websocket: true }; const previousLocation = clients[id].location; // Extract user identity from message if (msg.content?.user?.sub) { clients[id].user = msg.content.user.sub; } // Extract handle directly from message if (msg.content.handle) { clients[id].handle = msg.content.handle; } // Extract and store location if (msg.content.slug) { // Don't overwrite location with keep-alive if (msg.content.slug !== "*keep-alive*") { clients[id].location = msg.content.slug; log(`📍 Location updated for ${clients[id].handle || id}: "${msg.content.slug}"`); if (previousLocation !== msg.content.slug) { emitProfileActivity(msg.content.handle || clients[id].handle, { type: "piece", when: Date.now(), label: `Piece ${msg.content.slug}`, ref: msg.content.slug, }); } } else { log(`💓 Keep-alive from ${clients[id].handle || id}, location unchanged`); } }
emitProfilePresence( msg.content.handle || clients[id].handle, "location:broadcast", ["online", "currentPiece", "connections"], );
// Publish to redis... pub .publish("slug:" + msg.content.handle, msg.content.slug) .then((result) => { if (msg.content.slug !== "*keep-alive*") { log( "🐛 Slug succesfully published for:", msg.content.handle, msg.content.slug, ); } }) .catch((error) => { log("🙅♀️ Error publishing slug:", error); });
// TODO: - [] When a user is ghosted, then subscribe to their location // updates. // - [] And stop subscribing when they are unghosted. } else if (msg.type === "dev-log" && dev) { // Create device-specific log files and only notify in terminal const timestamp = new Date().toISOString(); const deviceId = `client-${id}`; const logFileName = `${DEV_LOG_DIR}${deviceId}.log`; // Check if this is a new device if (!deviceLogFiles.has(deviceId)) { deviceLogFiles.set(deviceId, logFileName); console.log(`📱 New device logging: ${deviceId} -> ${logFileName}`); console.log(` tail -f ${logFileName}`); } // Write to device-specific log file const logEntry = `[${timestamp}] ${msg.content.level || 'LOG'}: ${msg.content.message}\n`; try { fs.appendFileSync(logFileName, logEntry); } catch (error) { console.error(`Failed to write to ${logFileName}:`, error); } } else { // 🗺️ World Messages // TODO: Should all messages be prefixed with their piece?
// Filter for `world:${piece}:${label}` type messages. if (msg.type.startsWith("world:")) { const parsed = msg.type.split(":"); const piece = parsed[1]; const label = parsed.pop(); const worldHandle = resolveProfileHandle(id, piece, msg.content?.handle);
// TODO: Store client position on disconnect, based on their handle.
if (label === "show") { // Store any existing show picture in clients list. worldClients[piece][id].showing = msg.content; emitProfileActivity(worldHandle, { type: "show", when: Date.now(), label: `Showing in ${piece}`, piece, ref: piece, }); emitProfilePresence(worldHandle, `world:${piece}:show`, ["world", "showing"]); }
if (label === "hide") { // Store any existing show picture in clients list. worldClients[piece][id].showing = null; emitProfileActivity(worldHandle, { type: "hide", when: Date.now(), label: `Hide in ${piece}`, piece, ref: piece, }); emitProfilePresence(worldHandle, `world:${piece}:hide`, ["world", "showing"]); }
// Intercept chats and filter them (skip for laer-klokken). if (label === "write") { if (piece !== "laer-klokken") msg.content = filter(msg.content); const chatText = typeof msg.content === "string" ? msg.content : msg.content?.text; if (chatText) { emitProfileActivity(worldHandle, { type: "chat", when: Date.now(), label: `Chat ${piece}: ${truncateProfileText(chatText, 80)}`, piece, ref: piece, text: chatText, }); emitProfileCountDelta(worldHandle, { chats: 1 }); } }
if (label === "join") { if (!worldClients[piece]) worldClients[piece] = {};
// Check to see if the client handle matches and a connection can // be reassociated.
let pickedUpConnection = false; keys(worldClients[piece]).forEach((clientID) => { // TODO: Break out of this loop early. const client = worldClients[piece][clientID]; if ( client["handle"].startsWith("@") && client["handle"] === msg.content.handle && client.ghosted ) { // log("👻 Ghosted?", client);
log( "👻 Unghosting:", msg.content.handle, "old id:", clientID, "new id:", id, ); pickedUpConnection = true;
client.ghosted = false;
sub .unsubscribe("slug:" + msg.content.handle) .then(() => { log("🐛 Unsubscribed from slug for:", msg.content.handle); }) .catch((err) => { error( "🐛 Could not unsubscribe from slug for:", msg.content.handle, err, ); });
delete worldClients[piece][clientID];
ws.send(pack(`world:${piece}:list`, worldClients[piece], id));
// Replace the old client with the new data. worldClients[piece][id] = { ...msg.content }; } });
if (!pickedUpConnection) ws.send(pack(`world:${piece}:list`, worldClients[piece], id));
// ❤️🔥 TODO: No need to send the current user back via `list` here. if (!pickedUpConnection) worldClients[piece][id] = { ...msg.content };
// ^ Send existing list to just this user.
others(JSON.stringify(msg)); // Alert everyone else about the join.
log("🧩 Clients in piece:", piece, worldClients[piece]); emitProfileActivity(worldHandle, { type: "join", when: Date.now(), label: `Joined ${piece}`, piece, ref: piece, }); emitProfilePresence(worldHandle, `world:${piece}:join`, ["world", "connections"]); return; } else if (label === "move") { // log("🚶♂️", piece, msg.content); if (typeof worldClients?.[piece]?.[id] === "object") worldClients[piece][id].pos = msg.content.pos; } else { log(`${label}:`, msg.content); }
if (label === "persist") { log("🧮 Persisting this client...", msg.content); }
// All world: messages are only broadcast to "others", with the // exception of "write" with relays the filtered message back: if (label === "write") { everyone(JSON.stringify(msg)); } else { others(JSON.stringify(msg)); } return; }
// 🎮 1v1 game position updates should only go to others (not back to sender) if (msg.type === "1v1:move") { // Log occasionally in production for debugging (1 in 100 messages) if (Math.random() < 0.01) { log(`🎮 1v1:move relay: ${msg.content?.handle || id} -> ${wss.clients.size - 1} others`); } others(JSON.stringify(msg)); return; } // 🎾 Squash game position updates — relay to others only (not back to sender) if (msg.type === "squash:move") { others(JSON.stringify(msg)); return; }
// 🔊 Audio data from kidlisp.com — relay only to code-channel subscribers if (msg.type === "audio" && msg.content?.codeChannel) { const ch = msg.content.codeChannel; if (codeChannels[ch]) { subscribers(codeChannels[ch], pack("audio", msg.content, id)); } return; }
// 🎮 1v1 join/state messages - log and relay to everyone if (msg.type === "1v1:join" || msg.type === "1v1:state") { log(`🎮 ${msg.type}: ${msg.content?.handle || id} -> all ${wss.clients.size} clients`); }
// 🎯 Duel messages — routed to DuelManager (server-authoritative) if (msg.type === "duel:join") { const handle = typeof msg.content === "string" ? JSON.parse(msg.content).handle : msg.content?.handle; if (handle) duelManager.playerJoin(handle, id); return; } if (msg.type === "duel:leave") { const handle = typeof msg.content === "string" ? JSON.parse(msg.content).handle : msg.content?.handle; if (handle) duelManager.playerLeave(handle); return; } if (msg.type === "duel:ping") { const parsed = typeof msg.content === "string" ? JSON.parse(msg.content) : msg.content; if (parsed?.handle) duelManager.handlePing(parsed.handle, parsed.ts, id); return; } if (msg.type === "duel:input") { const parsed = typeof msg.content === "string" ? JSON.parse(msg.content) : msg.content; if (parsed?.handle) duelManager.receiveInput(parsed.handle, parsed); return; }
everyone(JSON.stringify(msg)); // Relay any other message to every user. } });
// More info: https://stackoverflow.com/a/49791634/8146077 ws.on("close", () => { log("🚪 Someone left:", id, "Online:", wss.clients.size, "🫂"); const departingHandle = normalizeProfileHandle(clients?.[id]?.handle); if (departingHandle) duelManager.playerLeave(departingHandle); removeNotepatMidiSubscriber(id);
// Remove from VSCode clients if present vscodeClients.delete(ws); // Remove from DAW devices if present if (dawDevices.has(id)) { dawDevices.delete(id); log(`🎹 DAW device disconnected: ${id} (remaining: ${dawDevices.size})`); } if (dawIDEs.has(id)) { dawIDEs.delete(id); log(`🎹 DAW IDE disconnected: ${id}`); }
// Delete the user from the worldClients pieces index. // keys(worldClients).forEach((piece) => { // delete worldClients[piece][id]; // if (keys(worldClients[piece]).length === 0) // delete worldClients[piece]; // });
if (clients[id]?.user) { const userSub = clients[id].user; sub .unsubscribe("logout:broadcast:" + userSub) .then(() => { log("🏃 Unsubscribed from logout:broadcast for:", userSub); }) .catch((err) => { error( "🏃 Could not unsubscribe from logout:broadcast for:", userSub, err, ); }); }
// Send a message to everyone else on the server that this client left.
let ghosted = false;
keys(worldClients).forEach((piece) => { if (worldClients[piece][id]) { // Turn this client into a ghost, unless it's the last one in the // world region. if ( worldClients[piece][id].handle.startsWith("@") && keys(worldClients[piece]).length > 1 ) { const handle = worldClients[piece][id].handle; log("👻 Ghosted:", handle); log("World clients after ghosting:", worldClients[piece]); worldClients[piece][id].ghost = true; ghosted = true;
function kick() { log("👢 Kicked:", handle, id); clearTimeout(kickTimer); sub .unsubscribe("slug:" + handle) .then(() => { log("🐛 Unsubscribed from slug for:", handle); }) .catch((err) => { error("🐛 Could not unsubscribe from slug for:", handle, err); }); // Delete the user from the worldClients pieces index. delete worldClients[piece][id]; if (keys(worldClients[piece]).length === 0) delete worldClients[piece]; everyone(pack(`world:${piece}:kick`, {}, id)); // Kick this ghost. }
let kickTimer = setTimeout(kick, 5000);
const worlds = ["field", "horizon"]; // Whitelist for worlds... // This could eventually be communicated based on a parameter in // the subscription? 24.03.09.15.05
// Subscribe to slug updates from redis. sub .subscribe("slug:" + handle, (slug) => { if (slug !== "*keep-alive*") { log(`🐛 ${handle} is now in:`, slug); if (!worlds.includes(slug)) everyone(pack(`world:${piece}:slug`, { handle, slug }, id)); }
if (worlds.includes(slug)) { kick(); } else { clearTimeout(kickTimer); kickTimer = setTimeout(kick, 5000); } // Whitelist slugs here }) .then(() => { log("🐛 Subscribed to slug updates from:", handle); }) .catch((err) => error("🐛 Could not subscribe to slug for:", handle, err), );
// Send a message to everyone on the server that this client is a ghost. everyone(pack(`world:${piece}:ghost`, {}, id)); } else { // Delete the user from the worldClients pieces index. delete worldClients[piece][id]; if (keys(worldClients[piece]).length === 0) delete worldClients[piece]; } } });
// Send a message to everyone else on the server that this client left. if (!ghosted) everyone(pack("left", { count: wss.clients.size }, id));
// Delete from the connection index. delete connections[id]; // Clean up client record if no longer connected via any protocol if (clients[id]) { clients[id].websocket = false; // If also not connected via UDP, delete the client record entirely if (!udpChannels[id]) { delete clients[id]; } }
// Clear out the codeChannel if the last user disconnects from it. if (codeChannel !== undefined) { codeChannels[codeChannel]?.delete(id); if (codeChannels[codeChannel]?.size === 0) { delete codeChannels[codeChannel]; delete codeChannelState[codeChannel]; // Clean up stored state too log(`🗑️ Cleaned up empty channel: ${codeChannel}`); } }
if (departingHandle) { emitProfilePresence(departingHandle, "disconnect", ["online", "connections"]); emitProfileActivity(departingHandle, { type: "presence", when: Date.now(), label: "Disconnected", }); } });});
// Sends a message to all connected clients.function everyone(string) { wss.clients.forEach((c) => { if (c?.readyState === WebSocket.OPEN) c.send(string); });}
// Sends a message to a particular set of client ids on// this instance that are part of the `subs` Set.function subscribers(subs, msg) { subs.forEach((connectionId) => { connections[connectionId]?.send(msg); });}
// 🎯 Wire DuelManager send functionsduelManager.setSendFunctions({ sendUDP: (channelId, event, data) => { const entry = udpChannels[channelId]; if (entry?.channel?.webrtcConnection?.state === "open") { try { entry.channel.emit(event, data); } catch {} } }, sendWS: (wsId, type, content) => { connections[wsId]?.send(pack(type, JSON.stringify(content), "duel")); }, broadcastWS: (type, content) => { everyone(pack(type, JSON.stringify(content), "duel")); }, resolveUdpForHandle: (handle) => { for (const [id, client] of Object.entries(clients)) { if (client.handle === handle && udpChannels[id]) return id; } return null; },});// #endregion
// *** Status WebSocket Stream ***// Track status dashboard clients (separate from game clients)const statusClients = new Set();// Track targeted profile subscribers by normalized handle key (`@name`)const profileStreamClients = new Map();const profileLastSeen = new Map();
// *** VSCode Extension Clients ***// Track VSCode extension clients for direct jump message routingconst vscodeClients = new Set();
function normalizeProfileHandle(handle) { if (!handle) return null; const raw = `${handle}`.trim(); if (!raw) return null; return `@${raw.replace(/^@+/, "").toLowerCase()}`;}
function normalizeMidiHandle(handle) { const normalized = normalizeProfileHandle(handle); return normalized ? normalized.slice(1) : "";}
function notepatMidiSourceKey(handle, machineId) { const handleKey = normalizeProfileHandle(handle) || "@unknown"; const machineKey = `${machineId || "unknown"}`.trim() || "unknown"; return `${handleKey}:${machineKey}`;}
function listNotepatMidiSources() { return [...notepatMidiSources.values()] .sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)) .map((source) => ({ handle: source.handle || null, machineId: source.machineId, piece: source.piece || "notepat", lastSeen: source.lastSeen || 0, lastEvent: source.lastEvent || null, }));}
function sendNotepatMidiSources(ws) { if (!ws || ws.readyState !== WebSocket.OPEN) return; try { ws.send(pack("notepat:midi:sources", { sources: listNotepatMidiSources() }, "midi-relay")); } catch (err) { error("🎹 Failed to send notepat midi sources:", err); }}
function removeNotepatMidiSubscriber(id) { if (id === undefined || id === null) return; notepatMidiSubscribers.delete(id);}
function addNotepatMidiSubscriber(id, ws, filter = {}) { if (id === undefined || id === null || !ws) return;
notepatMidiSubscribers.set(id, { ws, all: filter.all === true, handle: normalizeMidiHandle(filter.handle), machineId: filter.machineId ? `${filter.machineId}`.trim() : "", });
if (ws.readyState === WebSocket.OPEN) { ws.send(pack("notepat:midi:subscribed", { all: filter.all === true, handle: normalizeMidiHandle(filter.handle) || null, machineId: filter.machineId ? `${filter.machineId}`.trim() : null, }, "midi-relay")); }
sendNotepatMidiSources(ws);}
function broadcastNotepatMidiSources() { for (const [id, sub] of notepatMidiSubscribers) { if (!sub?.ws || sub.ws.readyState !== WebSocket.OPEN) { notepatMidiSubscribers.delete(id); continue; } sendNotepatMidiSources(sub.ws); }}
function notepatMidiSubscriberMatches(sub, event) { if (!sub) return false; if (sub.all) return true;
const eventHandle = normalizeMidiHandle(event?.handle); const eventMachine = event?.machineId ? `${event.machineId}`.trim() : "";
if (sub.handle && sub.handle !== eventHandle) return false; if (sub.machineId && sub.machineId !== eventMachine) return false;
return !!(sub.handle || sub.machineId);}
function broadcastNotepatMidiEvent(event) { for (const [id, sub] of notepatMidiSubscribers) { if (!sub?.ws || sub.ws.readyState !== WebSocket.OPEN) { notepatMidiSubscribers.delete(id); continue; } if (!notepatMidiSubscriberMatches(sub, event)) continue; try { sub.ws.send(pack("notepat:midi", event, "midi-relay")); } catch (err) { error("🎹 Failed to fan out notepat midi event:", err); } }}
function upsertNotepatMidiSource({ handle, machineId, piece, lastEvent, ts, address, port }) { const cleanHandle = normalizeMidiHandle(handle); const cleanMachineId = `${machineId || "unknown"}`.trim() || "unknown"; const key = notepatMidiSourceKey(cleanHandle, cleanMachineId); const previous = notepatMidiSources.get(key); const next = { handle: cleanHandle || null, machineId: cleanMachineId, piece: piece || "notepat", lastSeen: ts || Date.now(), lastEvent: lastEvent || previous?.lastEvent || null, address: address || previous?.address || null, port: port || previous?.port || null, };
notepatMidiSources.set(key, next);
if (!previous) { log(`🎹 Notepat MIDI source online: ${next.handle ? "@" + next.handle : "@unknown"} ${next.machineId}`); }
if ( !previous || previous.handle !== next.handle || previous.machineId !== next.machineId || previous.piece !== next.piece ) { broadcastNotepatMidiSources(); }
return next;}
function compactProfileText(value) { return `${value || ""}`.replace(/\s+/g, " ").trim();}
function truncateProfileText(value, max = 100) { const text = compactProfileText(value); if (text.length <= max) return text; return `${text.slice(0, Math.max(0, max - 3))}...`;}
function getProfilePresence(handleKey) { if (!handleKey) return null; const clientsForStatus = getClientStatus(); const matched = clientsForStatus.find( (client) => normalizeProfileHandle(client?.handle) === handleKey, );
if (!matched) { return { online: false, currentPiece: null, worldPiece: null, showing: null, connections: { websocket: 0, udp: 0, total: 0 }, pingMs: null, lastSeenAt: profileLastSeen.get(handleKey) || null, }; }
const now = Date.now(); profileLastSeen.set(handleKey, now);
const world = matched?.websocket?.worlds?.[0] || null;
return { online: true, currentPiece: matched.location || null, worldPiece: world?.piece || null, showing: world?.showing || null, connections: matched.connectionCount || { websocket: 0, udp: 0, total: 0 }, pingMs: matched?.websocket?.ping || null, lastSeenAt: now, };}
function sendProfileStream(ws, type, data) { if (!ws || ws.readyState !== WebSocket.OPEN) return; try { ws.send(JSON.stringify({ type, data, timestamp: Date.now() })); } catch (err) { error("👤 Failed to send profile stream event:", err); }}
function broadcastProfileStream(handleKey, type, data) { const subs = profileStreamClients.get(handleKey); if (!subs || subs.size === 0) return;
const stale = []; subs.forEach((ws) => { if (ws.readyState !== WebSocket.OPEN) { stale.push(ws); return; } sendProfileStream(ws, type, data); });
stale.forEach((ws) => subs.delete(ws)); if (subs.size === 0) profileStreamClients.delete(handleKey);}
function addProfileStreamClient(ws, handle) { const handleKey = normalizeProfileHandle(handle); if (!handleKey) return null;
if (!profileStreamClients.has(handleKey)) { profileStreamClients.set(handleKey, new Set()); }
profileStreamClients.get(handleKey).add(ws); ws.profileHandleKey = handleKey;
const presence = getProfilePresence(handleKey); sendProfileStream(ws, "profile:snapshot", { handle: handleKey, presence, }); sendProfileStream(ws, "counts:update", { handle: handleKey, counts: { online: presence?.online ? 1 : 0, connections: presence?.connections?.total || 0, }, });
return handleKey;}
function removeProfileStreamClient(ws) { const handleKey = ws?.profileHandleKey; if (!handleKey) return;
const subs = profileStreamClients.get(handleKey); if (!subs) { ws.profileHandleKey = null; return; }
subs.delete(ws); if (subs.size === 0) profileStreamClients.delete(handleKey); ws.profileHandleKey = null;}
function emitProfilePresence(handle, reason = "update", changed = []) { const handleKey = normalizeProfileHandle(handle); if (!handleKey) return;
const presence = getProfilePresence(handleKey); broadcastProfileStream(handleKey, "presence:update", { handle: handleKey, reason, changed, presence, }); broadcastProfileStream(handleKey, "counts:update", { handle: handleKey, counts: { online: presence?.online ? 1 : 0, connections: presence?.connections?.total || 0, }, });}
function emitProfileCountDelta(handle, delta = {}) { const handleKey = normalizeProfileHandle(handle); if (!handleKey) return; if (!delta || typeof delta !== "object") return;
const cleanDelta = {}; for (const [key, value] of Object.entries(delta)) { const amount = Number(value); if (!Number.isFinite(amount) || amount === 0) continue; cleanDelta[key] = amount; } if (Object.keys(cleanDelta).length === 0) return;
broadcastProfileStream(handleKey, "counts:delta", { handle: handleKey, delta: cleanDelta, });}
function emitProfileActivity(handle, event = {}) { const handleKey = normalizeProfileHandle(handle); if (!handleKey) return;
const label = truncateProfileText( event.label || event.text || event.type || "event", 120, ); if (!label) return;
broadcastProfileStream(handleKey, "activity:append", { handle: handleKey, event: { type: event.type || "event", when: event.when || Date.now(), label, ref: event.ref || null, piece: event.piece || null, }, });}
function resolveProfileHandle(id, piece, fromMessage) { return ( normalizeProfileHandle(fromMessage) || normalizeProfileHandle(clients?.[id]?.handle) || normalizeProfileHandle(worldClients?.[piece]?.[id]?.handle) );}
chatManager.setActivityEmitter((payload = {}) => { const handle = payload.handle; if (payload.event) emitProfileActivity(handle, payload.event); if (payload.countsDelta) emitProfileCountDelta(handle, payload.countsDelta);});
// Broadcast status updates every 2 secondssetInterval(() => { if (statusClients.size > 0) { const status = getFullStatus(); statusClients.forEach(client => { if (client.readyState === WebSocket.OPEN) { try { client.send(JSON.stringify({ type: 'status', data: status })); } catch (err) { error('📊 Failed to send status update:', err); } } }); }}, 2000);
// Broadcast targeted profile heartbeat updates every 2 secondssetInterval(() => { if (profileStreamClients.size === 0) return;
for (const handleKey of profileStreamClients.keys()) { const presence = getProfilePresence(handleKey); broadcastProfileStream(handleKey, "presence:update", { handle: handleKey, reason: "heartbeat", changed: [], presence, }); }}, 2000);
// 🧚 UDP Server (using Twilio ICE servers)// #endregion udp
// Note: This currently works off of a monolith via `udp.aesthetic.computer`// as the ports are blocked on jamsocket.
// geckos.io is imported at top and initialized before server.listen()
io.onConnection((channel) => { // Track this UDP channel udpChannels[channel.id] = { connectedAt: Date.now(), state: channel.webrtcConnection.state, user: null, handle: null, channel: channel, // Store reference for targeted sends }; // Get IP address from channel const udpIp = channel.userData?.address || channel.remoteAddress || null; log(`🩰 UDP ${channel.id} connected from:`, udpIp || 'unknown'); // Initialize client record with IP if (!clients[channel.id]) clients[channel.id] = { udp: true }; if (udpIp) { const cleanIp = udpIp.replace('::ffff:', ''); clients[channel.id].ip = cleanIp; // Get geolocation for UDP client const geo = geoip.lookup(cleanIp); if (geo) { clients[channel.id].geo = { country: geo.country, region: geo.region, city: geo.city, timezone: geo.timezone, ll: geo.ll }; log(`🌍 UDP ${channel.id} geolocation:`, geo.city || geo.country); } } // Set a timeout to warn about missing identity setTimeout(() => { if (!clients[channel.id]?.user && !clients[channel.id]?.handle) { log(`⚠️ UDP ${channel.id} has been connected for 10s but hasn't sent identity message`); } }, 10000); // Handle identity message channel.on("udp:identity", (data) => { try { const identity = JSON.parse(data); log(`🩰 UDP ${channel.id} sent identity:`, JSON.stringify(identity).substring(0, 100)); // Initialize client record if needed if (!clients[channel.id]) clients[channel.id] = { udp: true }; // Extract user identity if (identity.user?.sub) { clients[channel.id].user = identity.user.sub; log(`🩰 UDP ${channel.id} user:`, identity.user.sub.substring(0, 20) + "..."); } // Extract handle directly from identity message if (identity.handle) { clients[channel.id].handle = identity.handle; log(`✅ UDP ${channel.id} handle: "${identity.handle}"`); // Resolve UDP channel for duel if this handle is in a duel duelManager.resolveUdpChannel(identity.handle, channel.id); } } catch (e) { error(`🩰 Failed to parse identity for ${channel.id}:`, e); } }); channel.onDisconnect(() => { log(`🩰 ${channel.id} got disconnected`); delete udpChannels[channel.id]; fairyThrottle.delete(channel.id); // Clean up client record if no longer connected via any protocol if (clients[channel.id]) { clients[channel.id].udp = false; // If also not connected via WebSocket, delete the client record entirely if (!connections[channel.id]) { delete clients[channel.id]; } } channel.close(); });
// 💎 TODO: Make these channel names programmable somehow? 24.12.08.04.12
channel.on("tv", (data) => { if (channel.webrtcConnection.state === "open") { try { channel.room.emit("tv", data); } catch (err) { console.warn("Broadcast error:", err); } } else { console.log(channel.webrtcConnection.state); } });
// Just for testing via the aesthetic `udp` piece for now. channel.on("fairy:point", (data) => { // See docs here: https://github.com/geckosio/geckos.io#reliable-messages // TODO: - [] Learn about the differences between channels and rooms.
// log(`🩰 fairy:point - ${data}`); if (channel.webrtcConnection.state === "open") { try { channel.broadcast.emit("fairy:point", data); // ^ emit the to all channels in the same room except the sender
// Bridge to raw UDP clients (native bare-metal) try { const parsed = typeof data === "string" ? JSON.parse(data) : data; const x = parseFloat(parsed.x) || 0; const y = parseFloat(parsed.y) || 0; const handle = parsed.handle || ""; const hlen = Buffer.byteLength(handle, "utf8"); const pkt = Buffer.alloc(10 + hlen); pkt[0] = 0x02; // fairy broadcast pkt.writeFloatLE(x, 1); pkt.writeFloatLE(y, 5); pkt[9] = hlen; pkt.write(handle, 10, "utf8"); for (const [, client] of udpClients) { udpRelay.send(pkt, client.port, client.address); } } catch (e) { /* ignore bridge errors */ }
// Publish to Redis for silo firehose visualization (throttled ~10Hz) const now = Date.now(); const last = fairyThrottle.get(channel.id) || 0; if (now - last >= FAIRY_THROTTLE_MS) { fairyThrottle.set(channel.id, now); pub.publish("fairy:point", data).catch(() => {}); } } catch (err) { console.warn("Broadcast error:", err); } } else { console.log(channel.webrtcConnection.state); } });
// 🎮 1v1 FPS game position updates over UDP (low latency) channel.on("1v1:move", (data) => { if (channel.webrtcConnection.state === "open") { try { // Log occasionally for production debugging (1 in 100) if (Math.random() < 0.01) { const parsed = typeof data === 'string' ? JSON.parse(data) : data; log(`🩰 UDP 1v1:move: ${parsed?.handle || channel.id} broadcasting`); } // Broadcast position to all other players except sender channel.broadcast.emit("1v1:move", data); } catch (err) { console.warn("1v1:move broadcast error:", err); } } });
// 🎾 Squash game position updates over UDP (low latency) channel.on("squash:move", (data) => { if (channel.webrtcConnection.state === "open") { try { channel.broadcast.emit("squash:move", data); } catch (err) { console.warn("squash:move broadcast error:", err); } } });
// 🎯 Duel input over UDP (server-authoritative — NOT relayed, fed to DuelManager) channel.on("duel:input", (data) => { if (channel.webrtcConnection.state === "open") { try { const parsed = typeof data === "string" ? JSON.parse(data) : data; // Resolve handle from channel identity OR from message payload const handle = clients[channel.id]?.handle || parsed.handle; if (handle) { duelManager.receiveInput(handle, parsed); // Also resolve UDP channel if not yet linked if (!clients[channel.id]?.handle && parsed.handle) { duelManager.resolveUdpChannel(parsed.handle, channel.id); } } } catch (err) { console.warn("duel:input error:", err); } } });
// 🎚️ Slide mode: real-time code value updates via UDP (lowest latency) channel.on("slide:code", (data) => { if (channel.webrtcConnection.state === "open") { try { // Broadcast to all including sender (room.emit) for sync channel.room.emit("slide:code", data); } catch (err) { console.warn("slide:code broadcast error:", err); } } });
// 🔊 Audio: real-time audio analysis data via UDP (lowest latency) channel.on("udp:audio", (data) => { if (channel.webrtcConnection.state === "open") { try { channel.room.emit("udp:audio", data); } catch (err) { console.warn("udp:audio broadcast error:", err); } } });});
// #endregion
// ---------------------------------------------------------------------------// 🧚 Raw UDP fairy relay (port 10010) — for native bare-metal clients// Binary packet format:// [1 byte type] [4 float x LE] [4 float y LE] [1 handle_len] [N handle]// Type 0x01 = client→server, 0x02 = server→client broadcast// ---------------------------------------------------------------------------const UDP_FAIRY_PORT = 10010;
function handleNotepatMidiUdpPacket(payload, rinfo) { if (!payload || (payload.type !== "notepat:midi" && payload.type !== "notepat:midi:heartbeat")) { return false; }
const now = Date.now(); const source = upsertNotepatMidiSource({ handle: payload.handle, machineId: payload.machineId, piece: payload.piece || "notepat", lastEvent: payload.type === "notepat:midi" ? payload.event : "heartbeat", ts: now, address: rinfo.address, port: rinfo.port, });
if (!source.handle && !source.machineId) { return true; }
if (payload.type === "notepat:midi:heartbeat") { return true; }
const rawNote = Number(payload.note); const rawVelocity = Number(payload.velocity); const rawChannel = Number(payload.channel); if (!Number.isFinite(rawNote) || !Number.isFinite(rawVelocity) || !Number.isFinite(rawChannel)) { log("🎹 Invalid notepat midi UDP payload:", payload); return true; }
let event = payload.event === "note_off" ? "note_off" : "note_on"; const note = Math.max(0, Math.min(127, Math.round(rawNote))); const velocity = Math.max(0, Math.min(127, Math.round(rawVelocity))); const channel = Math.max(0, Math.min(15, Math.round(rawChannel))); if (event === "note_on" && velocity === 0) event = "note_off";
broadcastNotepatMidiEvent({ type: "notepat:midi", event, note, velocity, channel, handle: source.handle, machineId: source.machineId, piece: source.piece || "notepat", ts: Number.isFinite(Number(payload.ts)) ? Number(payload.ts) : now, });
return true;}
function pruneNotepatMidiSources() { const now = Date.now(); let changed = false;
for (const [key, source] of notepatMidiSources) { if (now - (source.lastSeen || 0) > UDP_MIDI_SOURCE_TTL_MS) { notepatMidiSources.delete(key); changed = true; } }
if (changed) broadcastNotepatMidiSources();}
udpRelay.on("message", (msg, rinfo) => { if (msg.length > 0 && msg[0] === 0x01 && msg.length >= 10) { const key = `${rinfo.address}:${rinfo.port}`; const x = msg.readFloatLE(1); const y = msg.readFloatLE(5); const hlen = msg[9]; const handle = msg.slice(10, 10 + hlen).toString("utf8");
udpClients.set(key, { address: rinfo.address, port: rinfo.port, handle, lastSeen: Date.now() });
// Build broadcast packet (type 0x02) const bcast = Buffer.alloc(msg.length); msg.copy(bcast); bcast[0] = 0x02;
// Broadcast to all other UDP clients for (const [k, client] of udpClients) { if (k !== key) { udpRelay.send(bcast, client.port, client.address); } }
// Also broadcast to Geckos.io WebRTC clients as fairy:point const fairyData = JSON.stringify({ x, y, handle }); try { // Emit to all geckos channels io.room().emit("fairy:point", fairyData); } catch (e) { /* ignore */ }
// Publish to Redis for silo firehose (throttled) const now = Date.now(); const lastFairy = fairyThrottle.get(key) || 0; if (now - lastFairy >= FAIRY_THROTTLE_MS) { fairyThrottle.set(key, now); pub.publish("fairy:point", fairyData).catch(() => {}); } return; }
if (msg.length > 0 && msg[0] === 0x7b) { try { const payload = JSON.parse(msg.toString("utf8")); if (handleNotepatMidiUdpPacket(payload, rinfo)) return; } catch (err) { log("🎹 Failed to parse UDP JSON packet:", err?.message || err); } }});
// Clean up stale UDP clients every 30ssetInterval(() => { const now = Date.now(); for (const [key, client] of udpClients) { if (now - client.lastSeen > 30000) udpClients.delete(key); } pruneNotepatMidiSources();}, 30000);
udpRelay.bind(UDP_FAIRY_PORT, () => { console.log(`🧚 Raw UDP fairy relay listening on port ${UDP_FAIRY_PORT}`);});
// Bridge: forward Geckos fairy:point to UDP clients// (patched into the existing fairy:point handler above via io.room().emit)// When a Geckos client sends fairy:point, also relay to UDP clients:const origFairyHandler = true; // marker — actual bridging done in channel.on("fairy:point") below
// #endregion UDP fairy relay
// 🚧 File Watching in Local Development Mode// File watching uses: https://github.com/paulmillr/chokidarif (dev) { // 1. Watch for local file changes in pieces. chokidar .watch("../system/public/aesthetic.computer/disks") .on("all", (event, path) => { if (event === "change") { const piece = path .split("/") .pop() .replace(/\.mjs|\.lisp$/, ""); everyone(pack("reload", { piece: piece || "*" }, "local")); } }); // 2. Watch base system files. chokidar .watch([ "../system/netlify/functions", "../system/public/privacy-policy.html", "../system/public/aesthetic-direct.html", "../system/public/aesthetic.computer/lib", "../system/public/aesthetic.computer/systems", // This doesn't need a full reload / could just reload the disk module? "../system/public/aesthetic.computer/boot.mjs", "../system/public/aesthetic.computer/bios.mjs", "../system/public/aesthetic.computer/style.css", "../system/public/kidlisp.com", "../system/public/l5.aesthetic.computer", "../system/public/gift.aesthetic.computer", "../system/public/give.aesthetic.computer", "../system/public/news.aesthetic.computer", ]) .on("all", (event, path) => { if (event === "change") everyone(pack("reload", { piece: "*refresh*" }, "local")); });
// 2b. Watch prompt files separately (piece reload instead of full refresh) chokidar .watch("../system/public/aesthetic.computer/prompts") .on("all", (event, path) => { if (event === "change") { const filename = path.split("/").pop(); console.log(`🎨 Prompt file changed: ${filename}`); everyone(pack("reload", { piece: "*piece-reload*" }, "local")); } });
// 3. Watch vscode extension chokidar.watch("../vscode-extension/out").on("all", (event, path) => { if (event === "change") everyone(pack("vscode-extension:reload", { reload: true }, "local")); });}
/*if (termkit) { term = termkit.terminal;
const doc = term.createDocument({ palette: new termkit.Palette(), });
// Create left (log) and right (client list) columns const leftColumn = new termkit.Container({ parent: doc, x: 0, width: "70%", height: "100%", });
const rightColumn = new termkit.Container({ parent: doc, x: "70%", width: "30%", height: "100%", });
term.grabInput();
console.log("grabbed input");
term.on("key", function (name, matches, data) { console.log("'key' event:", name);
// Detect CTRL-C and exit 'manually' if (name === "CTRL_C") { process.exit(); } });
term.on("mouse", function (name, data) { console.log("'mouse' event:", name, data); });
// Log box in the left column const logBox = new termkit.TextBox({ parent: leftColumn, content: "Your logs will appear here...\n", scrollable: true, vScrollBar: true, x: 0, y: 0, width: "100%", height: "100%", mouse: true, // to allow mouse interactions if needed });
// Static list box in the right column const clientList = new termkit.TextBox({ parent: rightColumn, content: "Client List:\n", x: 0, y: 0, width: "100%", height: "100%", });
// Example functions to update contents function addLog(message) { logBox.setContent(logBox.getContent() + message + "\n"); // logBox.scrollBottom(); doc.draw(); }
function updateClientList(clients) { clientList.setContent("Client List:\n" + clients.join("\n")); doc.draw(); }
// Example usage addLog("Server started..."); updateClientList(["Client1", "Client2"]);
// Handle input for graceful exit // term.grabInput(); // term.on("key", (key) => { // if (key === "CTRL_C") { // process.exit(); // } // });
// doc.draw();}*/
function log() { console.log(...arguments);}
function error() { console.error(...arguments);}