import { useEffect, useRef, useState } from 'react'; import { useSearchParams } from 'react-router'; import { fileSource, openXapk, urlSource, type Xapk } from '../lib/xapk'; import { connectHeadset, installXapk, webUsbSupported, type Headset, type Progress } from '../lib/questInstall'; import { fileUrl, getDownloads, questBuild, type QuestBuild } from '../lib/downloads'; const fmtSize = (n: number) => n >= 1e9 ? `${(n / 1e9).toFixed(2)} GB` : n >= 1e6 ? `${(n / 1e6).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 1e3))} KB`; function Step({ n, title, done, children }: { n: number; title: string; done?: boolean; children: React.ReactNode }) { return (

{done ? '✓' : n} {title}

{children}
); } function Bar({ p }: { p: Progress }) { const pct = p.total > 0 ? Math.min(100, Math.round((p.done / p.total) * 100)) : 0; return (
{p.label} {p.phase === 'launch' ? '' : `${fmtSize(p.done)} / ${fmtSize(p.total)} · ${pct}%`}
); } export function Quest() { const supported = webUsbSupported(); const [xapk, setXapk] = useState(null); const [headset, setHeadset] = useState(null); const [progress, setProgress] = useState>({}); const [busy, setBusy] = useState<'file' | 'connect' | 'install' | null>(null); const [err, setErr] = useState(null); const [done, setDone] = useState(false); const fileRef = useRef(null); // The server's latest published Quest build, if it offers downloads. const [latest, setLatest] = useState(null); const [params] = useSearchParams(); const open = async (load: () => Promise) => { setErr(null); setDone(false); setProgress({}); setBusy('file'); try { setXapk(await load()); } catch (e) { setXapk(null); setErr((e as Error).message); } finally { setBusy(null); } }; const pickFile = async (f: File | undefined) => { if (f) await open(() => openXapk(fileSource(f))); }; const pickBuild = async (b: QuestBuild | null) => { if (b) await open(() => openXapk(urlSource(fileUrl(b.file), b.file.name, b.file.size))); }; useEffect(() => { getDownloads().then((m) => { setLatest(questBuild(m)); // Arriving from a Quest card's "Install on Quest" on the Downloads page: // that card's own file, which may not be the newest release's. const wanted = params.get('file'); if (wanted) void pickBuild(questBuild(m, wanted)); }).catch(() => { /* no downloads: only the file picker */ }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const connect = async () => { setErr(null); setBusy('connect'); try { setHeadset(await connectHeadset()); } catch (e) { const m = (e as Error).message; // Closing the browser's device picker isn't an error worth showing. if (!/No device selected|No headset selected/i.test(m)) setErr(m); } finally { setBusy(null); } }; const install = async () => { if (!xapk || !headset) return; setErr(null); setDone(false); setProgress({}); setBusy('install'); try { await installXapk( headset, xapk, (p) => setProgress((prev) => ({ ...prev, [p.phase]: p })), async (pkg) => window.confirm( `An older copy of ${pkg} is installed with a different signing key and has to be removed first.\n\n` + 'Your account is on the server, but local settings on the headset will be lost. Continue?'), ); setDone(true); } catch (e) { const m = String((e as Error)?.message ?? e); setErr(/disconnected|NetworkError|transfer/i.test(m) ? 'The headset disconnected. Keep it plugged in and awake (wear it or cover the sensor), reconnect and try again.' : m); if (/disconnected|NetworkError/i.test(m)) setHeadset(null); } finally { setBusy(null); } }; const name = xapk?.manifest.name || 'DorkNet'; const totalSize = xapk ? xapk.apk.size + xapk.expansions.reduce((s, x) => s + x.entry.size, 0) : 0; return (

Install on Meta Quest

Installs the DorkNet Quest build straight from your browser: no SideQuest or command line. Your headset needs developer mode on (Meta Horizon phone app → your headset → Developer mode).

{!supported && (
This browser can't talk to USB devices. Open this page in Chrome or Edge on a computer (Windows, macOS, Linux or ChromeOS).
)} {latest ? (

Install the latest build straight from this server: nothing to download first, it streams to the headset during the install. Or pick a .xapk you already have.

) : (

Pick the DorkNet-….xapk you were given. It stays on your computer; nothing is uploaded.

)} pickFile(e.target.files?.[0])} />
{latest && ( )}
{xapk && (

Selected: {name} {xapk.manifest.version_name} · {fmtSize(totalSize)} · {xapk.source.name}

)}
  1. Plug the Quest into this computer with a USB cable.
  2. Click Connect and pick the Quest in the browser's list.
  3. Put the headset on and tap Allow on "Allow USB debugging" (tick "Always allow").

If it says the device is in use, close SideQuest / Meta Quest Developer Hub, or run adb kill-server.

{headset && Connected: {headset.model}}

Installs the app and copies the game data. The game data is large, so this takes a few minutes. Keep the cable in and the headset awake (wear it or cover the sensor above the nose).

{(['app', 'data', 'launch'] as const).map((k) => progress[k] && )} {done && (
Installed! The game is starting in the headset. Next time, find it under Library → Unknown Sources → {name}.
)}
{err &&
{err}
}

The app installs alongside the Meta Store version of Rec Room and doesn't replace it. You'll need a DorkNet account to sign in.

); }