feat: Introduce mobile simulator UI with new routes, screens, shared application chrome, and updated dependencies.
This commit is contained in:
@@ -179,6 +179,9 @@ Split-page entrypoints are also available:
|
||||
- `GET /sim/mobile-sim-activity.html`
|
||||
- `GET /sim/mobile-sim-settings.html`
|
||||
|
||||
Architecture reference page:
|
||||
- `GET /sim/backend-architecture.html`
|
||||
|
||||
All simulator pages support the same flow:
|
||||
- Register as `camera` or `client`
|
||||
- Connect Socket.IO with bearer device token
|
||||
|
||||
934
Backend/public/backend-architecture.html
Normal file
934
Backend/public/backend-architecture.html
Normal file
@@ -0,0 +1,934 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Backend Architecture Deep Dive</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
:root {
|
||||
--bg: #071015;
|
||||
--panel: #0e1b22;
|
||||
--panel-2: #132731;
|
||||
--text: #dbe7ef;
|
||||
--muted: #94adbb;
|
||||
--line: #255061;
|
||||
--accent: #22c55e;
|
||||
--accent-2: #06b6d4;
|
||||
--warn: #f59e0b;
|
||||
--danger: #f97316;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at 0% 0%, #113141 0%, transparent 45%),
|
||||
radial-gradient(circle at 100% 0%, #17303a 0%, transparent 45%),
|
||||
var(--bg);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
max-width: 1320px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
border: 1px solid var(--line);
|
||||
background: linear-gradient(165deg, #122734 0%, #0b171e 60%);
|
||||
border-radius: 18px;
|
||||
padding: 24px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin: 0 0 10px 0;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(1.5rem, 2vw, 2.2rem);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: clamp(1.2rem, 1.6vw, 1.6rem);
|
||||
margin-top: 26px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
color: #d2effb;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
p, li {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
strong, b {
|
||||
color: #eaf7ff;
|
||||
}
|
||||
|
||||
code, .mono {
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-size: 0.9em;
|
||||
background: #0a151b;
|
||||
border: 1px solid #1b3744;
|
||||
padding: 2px 6px;
|
||||
border-radius: 6px;
|
||||
color: #b7f5ff;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: 0.88rem;
|
||||
color: #8aa1af;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.g2 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
}
|
||||
|
||||
.g3 {
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(180deg, var(--panel-2), var(--panel));
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: inline-block;
|
||||
border: 1px solid #2e6074;
|
||||
color: #9ed4ea;
|
||||
border-radius: 999px;
|
||||
padding: 2px 10px;
|
||||
font-size: 0.8rem;
|
||||
margin: 0 4px 6px 0;
|
||||
background: #10232d;
|
||||
}
|
||||
|
||||
.chip.warn {
|
||||
border-color: #7a5b1c;
|
||||
color: #ffd58a;
|
||||
background: #20190c;
|
||||
}
|
||||
|
||||
.chip.ok {
|
||||
border-color: #1f6a42;
|
||||
color: #8df0bc;
|
||||
background: #0b1d15;
|
||||
}
|
||||
|
||||
.chip.alt {
|
||||
border-color: #23617b;
|
||||
color: #8cd7f8;
|
||||
background: #0d1f29;
|
||||
}
|
||||
|
||||
.svg-box {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: #0a161d;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-width: 840px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9rem;
|
||||
background: #0b171e;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 9px 10px;
|
||||
border-bottom: 1px solid #1f3c4b;
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #132833;
|
||||
color: #d9f4ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
details {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
background: #0b171e;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
color: #cde9f8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.seq {
|
||||
white-space: pre;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
background: #081117;
|
||||
color: #b4d4e2;
|
||||
font-family: "IBM Plex Mono", monospace;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.toc {
|
||||
position: sticky;
|
||||
top: 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #0a161d;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
color: #9dc9dd;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
padding: 3px 2px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.toc a:hover {
|
||||
color: #d4f3ff;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 260px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.toc {
|
||||
position: static;
|
||||
}
|
||||
|
||||
svg {
|
||||
min-width: 760px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<section class="hero">
|
||||
<div class="small">SecureCam Backend Architecture</div>
|
||||
<h1>Backend Architecture Deep Dive (Single-Page Reference)</h1>
|
||||
<p>
|
||||
This document explains how the backend is built, how requests and realtime events move through the system,
|
||||
how data is stored, and where each concern lives in code.
|
||||
It is based on the current implementation in this repository (<span class="mono">index.ts</span>,
|
||||
<span class="mono">routes/*</span>, <span class="mono">realtime/gateway.ts</span>,
|
||||
<span class="mono">services/*</span>, <span class="mono">workers/*</span>, <span class="mono">db/schema.ts</span>).
|
||||
</p>
|
||||
<div>
|
||||
<span class="chip ok">Express 5 API</span>
|
||||
<span class="chip alt">Socket.IO Realtime</span>
|
||||
<span class="chip">Better Auth + Drizzle</span>
|
||||
<span class="chip">PostgreSQL</span>
|
||||
<span class="chip">MinIO / S3-compatible</span>
|
||||
<span class="chip warn">Mock Media Provider</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="layout">
|
||||
<aside class="toc">
|
||||
<strong>Contents</strong>
|
||||
<a href="#system">1. System Context</a>
|
||||
<a href="#startup">2. Startup Sequence</a>
|
||||
<a href="#pipeline">3. HTTP Pipeline</a>
|
||||
<a href="#auth">4. Auth + Identity</a>
|
||||
<a href="#realtime">5. Realtime Gateway</a>
|
||||
<a href="#stream">6. Stream Lifecycle</a>
|
||||
<a href="#data">7. Data Model</a>
|
||||
<a href="#routes">8. Route Surface</a>
|
||||
<a href="#workers">9. Workers + Reliability</a>
|
||||
<a href="#security">10. Security Controls</a>
|
||||
<a href="#config">11. Configuration</a>
|
||||
<a href="#code-map">12. Code Ownership Map</a>
|
||||
<a href="#constraints">13. Current Constraints</a>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<section id="system">
|
||||
<h2>1) System Context</h2>
|
||||
<div class="svg-box">
|
||||
<svg viewBox="0 0 1220 560" role="img" aria-label="System context architecture diagram">
|
||||
<defs>
|
||||
<marker id="arr" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<polygon points="0 0, 8 4, 0 8" fill="#6ec9e8" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect x="20" y="20" width="280" height="170" rx="12" fill="#132833" stroke="#2d5d71"/>
|
||||
<text x="36" y="52" fill="#d7f2ff" font-size="18" font-weight="600">Clients</text>
|
||||
<text x="36" y="84" fill="#95bacb" font-size="15">- Browser simulator (camera/client)</text>
|
||||
<text x="36" y="109" fill="#95bacb" font-size="15">- Future mobile apps</text>
|
||||
<text x="36" y="134" fill="#95bacb" font-size="15">- Admin browser</text>
|
||||
<text x="36" y="159" fill="#95bacb" font-size="15">- Swagger/OpenAPI consumers</text>
|
||||
|
||||
<rect x="20" y="230" width="280" height="140" rx="12" fill="#1f2a20" stroke="#3d7249"/>
|
||||
<text x="36" y="262" fill="#dcffe5" font-size="18" font-weight="600">Identity</text>
|
||||
<text x="36" y="292" fill="#a8d8b6" font-size="15">Better Auth session cookies</text>
|
||||
<text x="36" y="317" fill="#a8d8b6" font-size="15">Custom HMAC device bearer tokens</text>
|
||||
<text x="36" y="342" fill="#a8d8b6" font-size="15">Role-aware device auth</text>
|
||||
|
||||
<rect x="360" y="20" width="530" height="390" rx="14" fill="#0f1e27" stroke="#2f6176"/>
|
||||
<text x="380" y="52" fill="#def6ff" font-size="20" font-weight="700">Backend Process (Bun + Express + Socket.IO)</text>
|
||||
|
||||
<rect x="380" y="70" width="235" height="120" rx="10" fill="#13303d" stroke="#2e647a"/>
|
||||
<text x="396" y="98" fill="#d7f2ff" font-size="16" font-weight="600">HTTP Layer</text>
|
||||
<text x="396" y="122" fill="#9fc8da" font-size="14">Helmet + CORS + rate limits</text>
|
||||
<text x="396" y="143" fill="#9fc8da" font-size="14">requestContext metrics + logs</text>
|
||||
<text x="396" y="164" fill="#9fc8da" font-size="14">REST routes + OpenAPI docs</text>
|
||||
|
||||
<rect x="640" y="70" width="230" height="120" rx="10" fill="#13303d" stroke="#2e647a"/>
|
||||
<text x="658" y="98" fill="#d7f2ff" font-size="16" font-weight="600">Realtime Gateway</text>
|
||||
<text x="658" y="122" fill="#9fc8da" font-size="14">Socket.IO device rooms</text>
|
||||
<text x="658" y="143" fill="#9fc8da" font-size="14">command / stream / webrtc signals</text>
|
||||
<text x="658" y="164" fill="#9fc8da" font-size="14">presence + retry loop</text>
|
||||
|
||||
<rect x="380" y="210" width="235" height="180" rx="10" fill="#1d2e23" stroke="#447551"/>
|
||||
<text x="396" y="238" fill="#e3ffe8" font-size="16" font-weight="600">Service / Worker Layer</text>
|
||||
<text x="396" y="262" fill="#b8dfc3" font-size="14">push queue worker</text>
|
||||
<text x="396" y="282" fill="#b8dfc3" font-size="14">recording timeout reconciler</text>
|
||||
<text x="396" y="302" fill="#b8dfc3" font-size="14">audit logging</text>
|
||||
<text x="396" y="322" fill="#b8dfc3" font-size="14">health + metrics endpoints</text>
|
||||
<text x="396" y="342" fill="#b8dfc3" font-size="14">media provider adapter</text>
|
||||
|
||||
<rect x="640" y="210" width="230" height="180" rx="10" fill="#2f271d" stroke="#7a5c2f"/>
|
||||
<text x="658" y="238" fill="#fff1da" font-size="16" font-weight="600">Media Control Plane</text>
|
||||
<text x="658" y="262" fill="#f2d7aa" font-size="14">stream session orchestration</text>
|
||||
<text x="658" y="282" fill="#f2d7aa" font-size="14">mock credential issuance</text>
|
||||
<text x="658" y="302" fill="#f2d7aa" font-size="14">optional SFU scaffold (noop)</text>
|
||||
<text x="658" y="322" fill="#f2d7aa" font-size="14">recording row lifecycle</text>
|
||||
|
||||
<rect x="940" y="40" width="260" height="160" rx="12" fill="#192b35" stroke="#39677d"/>
|
||||
<text x="958" y="70" fill="#daf4ff" font-size="18" font-weight="600">PostgreSQL</text>
|
||||
<text x="958" y="98" fill="#9fc5d8" font-size="14">users, devices, links, commands</text>
|
||||
<text x="958" y="118" fill="#9fc5d8" font-size="14">streams, recordings, events</text>
|
||||
<text x="958" y="138" fill="#9fc5d8" font-size="14">videos, notifications, audit</text>
|
||||
<text x="958" y="158" fill="#9fc5d8" font-size="14">Better Auth tables</text>
|
||||
|
||||
<rect x="940" y="240" width="260" height="160" rx="12" fill="#2b2318" stroke="#7c6039"/>
|
||||
<text x="958" y="270" fill="#fff0d4" font-size="18" font-weight="600">MinIO / Object Storage</text>
|
||||
<text x="958" y="298" fill="#ebd3aa" font-size="14">presigned PUT / GET URLs</text>
|
||||
<text x="958" y="318" fill="#ebd3aa" font-size="14">video objects + recordings</text>
|
||||
<text x="958" y="338" fill="#ebd3aa" font-size="14">bucket bootstrap on startup</text>
|
||||
|
||||
<line x1="300" y1="108" x2="360" y2="108" stroke="#6ec9e8" stroke-width="2" marker-end="url(#arr)"/>
|
||||
<line x1="300" y1="298" x2="360" y2="138" stroke="#7ee2aa" stroke-width="2" marker-end="url(#arr)"/>
|
||||
<line x1="890" y1="120" x2="940" y2="120" stroke="#6ec9e8" stroke-width="2" marker-end="url(#arr)"/>
|
||||
<line x1="890" y1="290" x2="940" y2="300" stroke="#f1bf73" stroke-width="2" marker-end="url(#arr)"/>
|
||||
<line x1="700" y1="190" x2="700" y2="210" stroke="#6ec9e8" stroke-width="2" marker-end="url(#arr)"/>
|
||||
<line x1="500" y1="190" x2="500" y2="210" stroke="#6ec9e8" stroke-width="2" marker-end="url(#arr)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="grid g3" style="margin-top:10px;">
|
||||
<div class="card"><h3>Core Role</h3><p>Acts primarily as a <strong>control plane</strong> for auth, command routing, stream state, credential issuance, and recording metadata. It is not yet a full production media plane.</p></div>
|
||||
<div class="card"><h3>Transport Split</h3><p><strong>HTTP</strong> handles CRUD/state endpoints; <strong>Socket.IO</strong> handles realtime command delivery, acknowledgements, and WebRTC signaling relay.</p></div>
|
||||
<div class="card"><h3>Persistence Split</h3><p><strong>Postgres</strong> stores state + metadata. <strong>MinIO</strong> stores binary objects. Routes often coordinate both.</p></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="startup">
|
||||
<h2>2) Startup Sequence</h2>
|
||||
<div class="seq">Process start
|
||||
-> load module graph (auth, db, minio, routes)
|
||||
-> create Express app + OpenAPI doc
|
||||
-> mount middleware and routes
|
||||
-> create HTTP server
|
||||
-> start() called
|
||||
-> ensureMinioBucket()
|
||||
- checks bucket existence
|
||||
- creates bucket if missing
|
||||
- exits process on failure
|
||||
-> setupRealtimeGateway(server)
|
||||
- Socket.IO auth middleware
|
||||
- event handlers
|
||||
- command retry interval init (if required tables exist)
|
||||
-> startRecordingsWorker()
|
||||
- interval scans stale awaiting_upload rows -> failed
|
||||
-> startPushWorker()
|
||||
- interval dispatches queued push rows
|
||||
-> server.listen(PORT)
|
||||
</div>
|
||||
<p class="small">If MinIO initialization fails, process exits with code 1 by design (<span class="mono">index.ts</span>).</p>
|
||||
</section>
|
||||
|
||||
<section id="pipeline">
|
||||
<h2>3) HTTP Request Pipeline (Express)</h2>
|
||||
<div class="svg-box">
|
||||
<svg viewBox="0 0 1220 360" role="img" aria-label="HTTP middleware pipeline diagram">
|
||||
<defs>
|
||||
<marker id="arr2" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<polygon points="0 0, 8 4, 0 8" fill="#90ddff" />
|
||||
</marker>
|
||||
</defs>
|
||||
<rect x="20" y="128" width="130" height="80" rx="10" fill="#122733" stroke="#2b5e74"/>
|
||||
<text x="42" y="173" fill="#d6f2ff" font-size="16">Client</text>
|
||||
|
||||
<rect x="190" y="70" width="160" height="190" rx="10" fill="#132a35" stroke="#2f6074"/>
|
||||
<text x="205" y="100" fill="#ddf6ff" font-size="15" font-weight="600">helmet()</text>
|
||||
<text x="205" y="124" fill="#9ec5d6" font-size="13">CSP, headers</text>
|
||||
<text x="205" y="154" fill="#ddf6ff" font-size="15" font-weight="600">cors()</text>
|
||||
<text x="205" y="178" fill="#9ec5d6" font-size="13">trusted origins</text>
|
||||
<text x="205" y="208" fill="#ddf6ff" font-size="15" font-weight="600">global rate limit</text>
|
||||
<text x="205" y="232" fill="#9ec5d6" font-size="13">memory buckets</text>
|
||||
|
||||
<rect x="390" y="70" width="200" height="190" rx="10" fill="#132a35" stroke="#2f6074"/>
|
||||
<text x="408" y="100" fill="#ddf6ff" font-size="15" font-weight="600">requestContext</text>
|
||||
<text x="408" y="124" fill="#9ec5d6" font-size="13">x-request-id</text>
|
||||
<text x="408" y="144" fill="#9ec5d6" font-size="13">counter increment</text>
|
||||
<text x="408" y="164" fill="#9ec5d6" font-size="13">JSON finish log</text>
|
||||
<text x="408" y="198" fill="#ddf6ff" font-size="15" font-weight="600">express.json()</text>
|
||||
<text x="408" y="222" fill="#9ec5d6" font-size="13">body parsing</text>
|
||||
|
||||
<rect x="630" y="52" width="270" height="226" rx="10" fill="#1d2e23" stroke="#447551"/>
|
||||
<text x="648" y="82" fill="#e6ffea" font-size="15" font-weight="600">Route layer</text>
|
||||
<text x="648" y="106" fill="#badfc5" font-size="13">/api/auth/* (Better Auth)</text>
|
||||
<text x="648" y="126" fill="#badfc5" font-size="13">/videos /devices /commands ...</text>
|
||||
<text x="648" y="146" fill="#badfc5" font-size="13">route-level auth + zod validation</text>
|
||||
<text x="648" y="166" fill="#badfc5" font-size="13">DB + MinIO + realtime side effects</text>
|
||||
<text x="648" y="200" fill="#e6ffea" font-size="15" font-weight="600">Static + docs</text>
|
||||
<text x="648" y="224" fill="#badfc5" font-size="13">/sim, /docs, /openapi.json</text>
|
||||
|
||||
<rect x="940" y="90" width="260" height="150" rx="10" fill="#2a2118" stroke="#7d613a"/>
|
||||
<text x="958" y="120" fill="#fff2d9" font-size="15" font-weight="600">Response + Error Handler</text>
|
||||
<text x="958" y="146" fill="#edd6ad" font-size="13">successful JSON / HTML / static file</text>
|
||||
<text x="958" y="168" fill="#edd6ad" font-size="13">or fallback 500 JSON</text>
|
||||
<text x="958" y="190" fill="#edd6ad" font-size="13">{ message: "Internal server error" }</text>
|
||||
|
||||
<line x1="150" y1="168" x2="190" y2="168" stroke="#90ddff" stroke-width="2" marker-end="url(#arr2)"/>
|
||||
<line x1="350" y1="168" x2="390" y2="168" stroke="#90ddff" stroke-width="2" marker-end="url(#arr2)"/>
|
||||
<line x1="590" y1="168" x2="630" y2="168" stroke="#90ddff" stroke-width="2" marker-end="url(#arr2)"/>
|
||||
<line x1="900" y1="168" x2="940" y2="168" stroke="#90ddff" stroke-width="2" marker-end="url(#arr2)"/>
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="auth">
|
||||
<h2>4) Authentication and Identity Model</h2>
|
||||
<div class="grid g2">
|
||||
<div class="card">
|
||||
<h3>A) Session auth (<span class="mono">requireAuth</span>)</h3>
|
||||
<ul>
|
||||
<li>Used by user-facing REST routes like <span class="mono">/videos</span>, <span class="mono">/devices/register</span>, <span class="mono">/device-links</span>.</li>
|
||||
<li>Reads Better Auth session from request headers/cookies via <span class="mono">auth.api.getSession()</span>.</li>
|
||||
<li>Attaches session object to <span class="mono">req.auth</span>.</li>
|
||||
<li>Backed by Better Auth tables: <span class="mono">users</span>, <span class="mono">account</span>, <span class="mono">session</span>, <span class="mono">verification</span>.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>B) Device auth (<span class="mono">requireDeviceAuth</span>)</h3>
|
||||
<ul>
|
||||
<li>Used by device-to-backend routes and Socket.IO auth.</li>
|
||||
<li>Bearer token format: <span class="mono">base64url(payload).hmac</span>.</li>
|
||||
<li>Payload fields: <span class="mono">userId</span>, <span class="mono">deviceId</span>, <span class="mono">role</span>, <span class="mono">exp</span>.</li>
|
||||
<li>Signed with HMAC-SHA256 using <span class="mono">BETTER_AUTH_SECRET</span>.</li>
|
||||
<li>Token role is verified against device role in realtime handshake.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small">This dual model separates user session identity from per-device identity and permissions.</p>
|
||||
</section>
|
||||
|
||||
<section id="realtime">
|
||||
<h2>5) Realtime Gateway (Socket.IO)</h2>
|
||||
<div class="grid g2">
|
||||
<div class="card">
|
||||
<h3>Connection model</h3>
|
||||
<ul>
|
||||
<li>Devices authenticate with token in <span class="mono">handshake.auth.token</span> or <span class="mono">Authorization</span> header.</li>
|
||||
<li>Each device joins room <span class="mono">device:{deviceId}</span>.</li>
|
||||
<li>Presence updates <span class="mono">devices.status</span> + <span class="mono">lastSeenAt</span>.</li>
|
||||
<li>Disconnect applies a 500ms delay to reduce status flapping on fast reconnect.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Gateway responsibilities</h3>
|
||||
<ul>
|
||||
<li><span class="mono">command:received</span> delivery to target room.</li>
|
||||
<li><span class="mono">command:ack</span> validation + DB update + source notification.</li>
|
||||
<li><span class="mono">webrtc:signal</span> relay with same-owner target validation.</li>
|
||||
<li><span class="mono">stream:frame</span> relay fallback (base64 image snapshots).</li>
|
||||
<li>Retry worker for stale sent commands every 5s, max 3 retries.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Command dispatch and ack sequence</h3>
|
||||
<div class="seq">Client Device API /commands DB Socket.IO Gateway Camera Device
|
||||
| | | | |
|
||||
1) POST /commands -------->| validate/link ---->| insert queued command | |
|
||||
| | dispatchCommandById() | |
|
||||
| |-----------------------------------------------> emit command:received --->|
|
||||
| | | status sent/queued | |
|
||||
|<--------------------| command payload | | |
|
||||
|
||||
2) camera emits command:ack --------------------------------------------------------------->|
|
||||
| | | | validate + update DB |
|
||||
| | | command status + ack time | emit command:status -->| (to source room)
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="stream">
|
||||
<h2>6) Stream Lifecycle and Media Control</h2>
|
||||
<div class="card">
|
||||
<h3>State machine (stream session)</h3>
|
||||
<div class="seq">requested --> streaming --> completed|cancelled|failed
|
||||
| | |
|
||||
| | +-- create recording placeholder row on end
|
||||
| +-- media session created (provider + endpoints)
|
||||
+-- command start_stream queued/dispatched to camera
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>On-demand stream end-to-end sequence</h3>
|
||||
<div class="seq">Client Device /streams/request Camera Device /streams/:id/accept Media Provider
|
||||
| | | | |
|
||||
1) request stream -------------->| validate roles/link -> | | |
|
||||
| | create stream_session | | |
|
||||
| | create start_stream cmd | | |
|
||||
| | dispatch via socket ----+-------------------------> | command:received |
|
||||
|<--------------------------| stream:requested event | | |
|
||||
|
||||
2) accept command (camera side)
|
||||
| | | POST /accept -----------> | createSession() ------>|
|
||||
| | | | status=streaming |
|
||||
|<--------------------------| stream:started event ---+ | |
|
||||
|
||||
3) credential issuance
|
||||
camera -> GET /publish-credentials -> mediaProvider.issuePublishCredentials()
|
||||
viewer -> GET /subscribe-credentials -> mediaProvider.issueSubscribeCredentials()
|
||||
|
||||
4) stream end
|
||||
camera/requester -> POST /end -> mark ended + optional sfuService.endSession() + createRecordingForStream()
|
||||
-> emit stream:ended to camera and requester (push fallback if offline)
|
||||
</div>
|
||||
|
||||
<div class="grid g2">
|
||||
<div class="card">
|
||||
<h3>Media provider abstraction</h3>
|
||||
<ul>
|
||||
<li>Current provider: <span class="mono">mock</span> (<span class="mono">media/providers/mock.ts</span>).</li>
|
||||
<li>Creates deterministic mock media session IDs.</li>
|
||||
<li>Issues signed publish/subscribe tokens with TTL.</li>
|
||||
<li>Uses <span class="mono">BETTER_AUTH_SECRET</span> for HMAC signing.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>SFU mode status</h3>
|
||||
<ul>
|
||||
<li><span class="mono">MEDIA_MODE=single_server_sfu</span> enables SFU endpoints.</li>
|
||||
<li>Current implementation is a <strong>noop scaffold</strong> with in-memory session registry + synthetic transport IDs.</li>
|
||||
<li>No full server-side RTP forwarding pipeline implemented yet.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="data">
|
||||
<h2>7) Data Model (Core Tables and Relationships)</h2>
|
||||
<div class="svg-box">
|
||||
<svg viewBox="0 0 1280 880" role="img" aria-label="ER-style architecture data diagram">
|
||||
<defs>
|
||||
<marker id="arr3" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<polygon points="0 0, 8 4, 0 8" fill="#8bcbe3" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect x="30" y="30" width="260" height="170" rx="10" fill="#132833" stroke="#2f6074"/>
|
||||
<text x="48" y="58" fill="#ddf6ff" font-size="17" font-weight="700">users</text>
|
||||
<text x="48" y="82" fill="#a5c9da" font-size="13">id (PK), email, name</text>
|
||||
<text x="48" y="102" fill="#a5c9da" font-size="13">passwordHash, emailVerified</text>
|
||||
<text x="48" y="122" fill="#a5c9da" font-size="13">createdAt, updatedAt</text>
|
||||
|
||||
<rect x="340" y="30" width="290" height="210" rx="10" fill="#132833" stroke="#2f6074"/>
|
||||
<text x="358" y="58" fill="#ddf6ff" font-size="17" font-weight="700">devices</text>
|
||||
<text x="358" y="82" fill="#a5c9da" font-size="13">id (PK), userId (FK -> users)</text>
|
||||
<text x="358" y="102" fill="#a5c9da" font-size="13">role, status, isCamera</text>
|
||||
<text x="358" y="122" fill="#a5c9da" font-size="13">platform, appVersion, pushToken</text>
|
||||
<text x="358" y="142" fill="#a5c9da" font-size="13">lastSeenAt, timestamps</text>
|
||||
|
||||
<rect x="680" y="30" width="300" height="190" rx="10" fill="#153329" stroke="#3f7351"/>
|
||||
<text x="698" y="58" fill="#e4ffea" font-size="17" font-weight="700">device_links</text>
|
||||
<text x="698" y="82" fill="#b8dfc4" font-size="13">ownerUserId -> users</text>
|
||||
<text x="698" y="102" fill="#b8dfc4" font-size="13">cameraDeviceId -> devices</text>
|
||||
<text x="698" y="122" fill="#b8dfc4" font-size="13">clientDeviceId -> devices</text>
|
||||
<text x="698" y="142" fill="#b8dfc4" font-size="13">unique(cameraDeviceId, clientDeviceId)</text>
|
||||
|
||||
<rect x="1020" y="30" width="230" height="210" rx="10" fill="#2f271d" stroke="#7a5c2f"/>
|
||||
<text x="1038" y="58" fill="#fff1da" font-size="17" font-weight="700">device_commands</text>
|
||||
<text x="1038" y="82" fill="#f0d7ac" font-size="13">sourceDeviceId -> devices</text>
|
||||
<text x="1038" y="102" fill="#f0d7ac" font-size="13">targetDeviceId -> devices</text>
|
||||
<text x="1038" y="122" fill="#f0d7ac" font-size="13">commandType, payload</text>
|
||||
<text x="1038" y="142" fill="#f0d7ac" font-size="13">status, retryCount, ackAt</text>
|
||||
|
||||
<rect x="340" y="300" width="300" height="210" rx="10" fill="#132833" stroke="#2f6074"/>
|
||||
<text x="358" y="328" fill="#ddf6ff" font-size="17" font-weight="700">stream_sessions</text>
|
||||
<text x="358" y="352" fill="#a5c9da" font-size="13">ownerUserId -> users</text>
|
||||
<text x="358" y="372" fill="#a5c9da" font-size="13">cameraDeviceId -> devices</text>
|
||||
<text x="358" y="392" fill="#a5c9da" font-size="13">requesterDeviceId -> devices</text>
|
||||
<text x="358" y="412" fill="#a5c9da" font-size="13">status, reason, mediaProvider</text>
|
||||
<text x="358" y="432" fill="#a5c9da" font-size="13">mediaSessionId, streamKey</text>
|
||||
|
||||
<rect x="680" y="300" width="300" height="210" rx="10" fill="#153329" stroke="#3f7351"/>
|
||||
<text x="698" y="328" fill="#e4ffea" font-size="17" font-weight="700">recordings</text>
|
||||
<text x="698" y="352" fill="#b8dfc4" font-size="13">streamSessionId -> stream_sessions</text>
|
||||
<text x="698" y="372" fill="#b8dfc4" font-size="13">cameraDeviceId, requesterDeviceId</text>
|
||||
<text x="698" y="392" fill="#b8dfc4" font-size="13">status awaiting_upload/ready/failed</text>
|
||||
<text x="698" y="412" fill="#b8dfc4" font-size="13">objectKey, bucket, duration, size</text>
|
||||
|
||||
<rect x="1020" y="300" width="230" height="190" rx="10" fill="#2f271d" stroke="#7a5c2f"/>
|
||||
<text x="1038" y="328" fill="#fff1da" font-size="17" font-weight="700">events</text>
|
||||
<text x="1038" y="352" fill="#f0d7ac" font-size="13">userId -> users</text>
|
||||
<text x="1038" y="372" fill="#f0d7ac" font-size="13">deviceId -> devices</text>
|
||||
<text x="1038" y="392" fill="#f0d7ac" font-size="13">startedAt, endedAt, status</text>
|
||||
<text x="1038" y="412" fill="#f0d7ac" font-size="13">triggeredBy, videoUrl</text>
|
||||
|
||||
<rect x="30" y="560" width="300" height="220" rx="10" fill="#132833" stroke="#2f6074"/>
|
||||
<text x="48" y="588" fill="#ddf6ff" font-size="17" font-weight="700">videos (legacy upload metadata)</text>
|
||||
<text x="48" y="612" fill="#a5c9da" font-size="13">userId, deviceId, eventId</text>
|
||||
<text x="48" y="632" fill="#a5c9da" font-size="13">objectKey, bucket, uploadUrl</text>
|
||||
<text x="48" y="652" fill="#a5c9da" font-size="13">downloadUrl, status, expiresAt</text>
|
||||
|
||||
<rect x="370" y="560" width="300" height="220" rx="10" fill="#153329" stroke="#3f7351"/>
|
||||
<text x="388" y="588" fill="#e4ffea" font-size="17" font-weight="700">push_notifications</text>
|
||||
<text x="388" y="612" fill="#b8dfc4" font-size="13">ownerUserId -> users</text>
|
||||
<text x="388" y="632" fill="#b8dfc4" font-size="13">recipientDeviceId -> devices</text>
|
||||
<text x="388" y="652" fill="#b8dfc4" font-size="13">type, payload, status</text>
|
||||
<text x="388" y="672" fill="#b8dfc4" font-size="13">attempts, nextAttemptAt, sentAt</text>
|
||||
|
||||
<rect x="710" y="560" width="270" height="220" rx="10" fill="#2f271d" stroke="#7a5c2f"/>
|
||||
<text x="728" y="588" fill="#fff1da" font-size="17" font-weight="700">audit_logs</text>
|
||||
<text x="728" y="612" fill="#f0d7ac" font-size="13">ownerUserId -> users</text>
|
||||
<text x="728" y="632" fill="#f0d7ac" font-size="13">actorDeviceId -> devices</text>
|
||||
<text x="728" y="652" fill="#f0d7ac" font-size="13">action, targetType, targetId</text>
|
||||
<text x="728" y="672" fill="#f0d7ac" font-size="13">metadata, ipAddress, createdAt</text>
|
||||
|
||||
<rect x="1020" y="560" width="230" height="220" rx="10" fill="#1f2d36" stroke="#4a6f80"/>
|
||||
<text x="1038" y="588" fill="#daf4ff" font-size="17" font-weight="700">Better Auth tables</text>
|
||||
<text x="1038" y="612" fill="#a9cfdf" font-size="13">account</text>
|
||||
<text x="1038" y="632" fill="#a9cfdf" font-size="13">session</text>
|
||||
<text x="1038" y="652" fill="#a9cfdf" font-size="13">verification</text>
|
||||
|
||||
<line x1="290" y1="96" x2="340" y2="96" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="290" y1="130" x2="680" y2="130" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="630" y1="140" x2="1020" y2="140" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="490" y1="240" x2="490" y2="300" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="640" y1="404" x2="680" y2="404" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="1135" y1="240" x2="1135" y2="300" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="190" y1="200" x2="190" y2="560" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="490" y1="510" x2="520" y2="560" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
<line x1="490" y1="510" x2="845" y2="560" stroke="#8bcbe3" stroke-width="2" marker-end="url(#arr3)"/>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="small">Note: <span class="mono">notifications</span> table exists for event notification tracking; push delivery queue is modeled separately by <span class="mono">push_notifications</span>.</p>
|
||||
</section>
|
||||
|
||||
<section id="routes">
|
||||
<h2>8) Route Surface and Responsibilities</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Area</th>
|
||||
<th>Auth</th>
|
||||
<th>Main tables/resources</th>
|
||||
<th>Side effects</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="mono">/api/auth/*</span></td>
|
||||
<td>Better Auth</td>
|
||||
<td>users, account, session, verification</td>
|
||||
<td>session cookie lifecycle</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/devices</span></td>
|
||||
<td>session + device token for heartbeat</td>
|
||||
<td>devices, device_links</td>
|
||||
<td>auto-link opposite-role devices on register; stale-status projection on list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/device-links</span></td>
|
||||
<td>session</td>
|
||||
<td>device_links, devices</td>
|
||||
<td>enforces camera/client role pairing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/commands</span></td>
|
||||
<td>session + device token ack fallback</td>
|
||||
<td>device_commands, devices, device_links</td>
|
||||
<td>dispatch to Socket.IO, ack/reject propagation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/events</span></td>
|
||||
<td>device token (start/end), session (list)</td>
|
||||
<td>events, device_links, notifications</td>
|
||||
<td>realtime motion fanout, push fallback, audit log</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/streams</span></td>
|
||||
<td>device token</td>
|
||||
<td>stream_sessions, device_commands, recordings</td>
|
||||
<td>stream command dispatch, media credentials, stream realtime events, push fallback, optional SFU calls</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/recordings</span></td>
|
||||
<td>device token</td>
|
||||
<td>recordings</td>
|
||||
<td>storage object validation, presigned download URL, audit log</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/videos</span></td>
|
||||
<td>session</td>
|
||||
<td>videos, devices, MinIO</td>
|
||||
<td>presigned PUT/GET generation, object listing/deletion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/push-notifications</span></td>
|
||||
<td>device token</td>
|
||||
<td>push_notifications</td>
|
||||
<td>manual worker dispatch trigger</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/audit</span></td>
|
||||
<td>device token</td>
|
||||
<td>audit_logs</td>
|
||||
<td>none (read only)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/ops</span></td>
|
||||
<td>none</td>
|
||||
<td>DB, MinIO, in-memory metrics, SFU service</td>
|
||||
<td>readiness checks, metric export</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="mono">/admin</span></td>
|
||||
<td>HTTP Basic auth</td>
|
||||
<td>MinIO</td>
|
||||
<td>embedded admin UI + object operations</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Detailed endpoint groups</h3>
|
||||
<details>
|
||||
<summary>Devices and Links</summary>
|
||||
<ul>
|
||||
<li><span class="mono">POST /devices/register</span>: creates device, sets initial online status, auto-creates links with existing opposite-role devices, returns device token.</li>
|
||||
<li><span class="mono">GET /devices</span>: lists user devices with computed effective presence status using <span class="mono">DEVICE_ONLINE_STALE_SECONDS</span>.</li>
|
||||
<li><span class="mono">PATCH /devices/:id</span>: updates mutable metadata and role.</li>
|
||||
<li><span class="mono">POST /devices/:id/heartbeat</span>: token-authenticated presence update for exact device token/device match.</li>
|
||||
<li><span class="mono">/device-links</span>: ensures one active camera-client pair and ownership checks.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Commands, Events, Streams</summary>
|
||||
<ul>
|
||||
<li><span class="mono">POST /commands</span>: only client -> camera, only for active links.</li>
|
||||
<li><span class="mono">POST /events/motion/start</span>: camera-only; sends realtime to linked clients, queues push if offline.</li>
|
||||
<li><span class="mono">POST /streams/request</span>: creates stream session + start_stream command + realtime notification.</li>
|
||||
<li><span class="mono">POST /streams/:id/accept</span>: camera transitions stream to streaming; creates media session and optional SFU bootstrap.</li>
|
||||
<li><span class="mono">GET /streams/:id/publish-credentials</span>: camera-only credential issuance.</li>
|
||||
<li><span class="mono">GET /streams/:id/subscribe-credentials</span>: participant credential issuance.</li>
|
||||
<li><span class="mono">POST /streams/:id/end</span>: closes session, ends SFU (if enabled), creates recording placeholder, notifies both parties.</li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Storage and Recordings</summary>
|
||||
<ul>
|
||||
<li><span class="mono">POST /videos/upload-url</span>: session route to mint presigned PUT + metadata row.</li>
|
||||
<li><span class="mono">POST /recordings/:id/finalize</span>: camera marks recording ready once object exists, or creates simulator placeholder if object key starts with <span class="mono">sim/</span>.</li>
|
||||
<li><span class="mono">GET /recordings/:id/download-url</span>: requester/camera only, ready-only, verifies object exists before presigning.</li>
|
||||
</ul>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
<section id="workers">
|
||||
<h2>9) Workers and Reliability Mechanisms</h2>
|
||||
<div class="grid g3">
|
||||
<div class="card">
|
||||
<h3>Command retry loop</h3>
|
||||
<p>Inside realtime gateway. Scans <span class="mono">device_commands</span> where status is <span class="mono">sent</span> and stale by >10s. Re-dispatches every 5s. Fails after 3 retries.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Push worker</h3>
|
||||
<p>Interval (default 10s) dispatches queued notifications with <span class="mono">nextAttemptAt <= now</span>. Missing push token triggers retry backoff; max attempts configurable.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Recording worker</h3>
|
||||
<p>Interval (default 30s) marks stale <span class="mono">awaiting_upload</span> recordings as <span class="mono">failed</span> after timeout window (default 30 min).</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small">Workers perform startup guards using <span class="mono">hasRequiredTables()</span> so they do not run before migrations are applied.</p>
|
||||
</section>
|
||||
|
||||
<section id="security">
|
||||
<h2>10) Security Controls and Guardrails</h2>
|
||||
<div class="grid g2">
|
||||
<div class="card">
|
||||
<h3>Implemented</h3>
|
||||
<ul>
|
||||
<li><strong>Helmet CSP</strong> with explicit script/style/font/connect/media/image directives.</li>
|
||||
<li><strong>CORS</strong> tied to <span class="mono">BETTER_AUTH_TRUSTED_ORIGINS</span> (or permissive fallback).</li>
|
||||
<li><strong>Rate limiting</strong> globally and on high-traffic route groups.</li>
|
||||
<li><strong>Ownership checks</strong> on almost all queries (user-scoped data access).</li>
|
||||
<li><strong>Role constraints</strong> (for example client->camera command direction).</li>
|
||||
<li><strong>Token integrity</strong> via timing-safe HMAC verification.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Important caveats</h3>
|
||||
<ul>
|
||||
<li>Rate limits are in-memory; not shared across replicas.</li>
|
||||
<li>Metrics are in-memory counters only (no persistence/export protocol).</li>
|
||||
<li>Mock push provider treats presence of push token as delivery success.</li>
|
||||
<li>Mock media provider + SFU scaffold are not production media infrastructure.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="config">
|
||||
<h2>11) Configuration Map (Key Env Variables)</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<th>Variables</th>
|
||||
<th>Architectural effect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Core server</td>
|
||||
<td><span class="mono">PORT</span>, <span class="mono">DATABASE_URL</span></td>
|
||||
<td>listener + DB connectivity</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Auth</td>
|
||||
<td><span class="mono">BETTER_AUTH_SECRET</span>, <span class="mono">BETTER_AUTH_BASE_URL</span>, <span class="mono">BETTER_AUTH_TRUSTED_ORIGINS</span></td>
|
||||
<td>session signing, base URL, trusted origins, device token signing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Presence</td>
|
||||
<td><span class="mono">DEVICE_ONLINE_STALE_SECONDS</span></td>
|
||||
<td>effective online/offline projection in device listings</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Storage</td>
|
||||
<td><span class="mono">MINIO_ENDPOINT</span>, <span class="mono">MINIO_PORT</span>, <span class="mono">MINIO_USE_SSL</span>, <span class="mono">MINIO_ACCESS_KEY</span>, <span class="mono">MINIO_SECRET_KEY</span>, <span class="mono">MINIO_BUCKET</span>, <span class="mono">MINIO_PRESIGNED_EXPIRY_SECONDS</span></td>
|
||||
<td>object I/O, presign TTL, startup bucket bootstrap</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Media</td>
|
||||
<td><span class="mono">MEDIA_MODE</span>, <span class="mono">MEDIA_PROVIDER</span>, <span class="mono">TURN_URLS</span>, <span class="mono">TURN_USERNAME</span>, <span class="mono">TURN_CREDENTIAL</span></td>
|
||||
<td>control plane mode and transport descriptor generation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Workers</td>
|
||||
<td><span class="mono">PUSH_WORKER_INTERVAL_MS</span>, <span class="mono">PUSH_MAX_ATTEMPTS</span>, <span class="mono">RECORDING_WORKER_INTERVAL_MS</span>, <span class="mono">RECORDING_STALE_SECONDS</span></td>
|
||||
<td>queue throughput, retry/failure timing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Admin</td>
|
||||
<td><span class="mono">ADMIN_USERNAME</span>, <span class="mono">ADMIN_PASSWORD</span></td>
|
||||
<td>required to mount admin dashboard route logic</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section id="code-map">
|
||||
<h2>12) Code Ownership Map (Where to Modify What)</h2>
|
||||
<div class="grid g2">
|
||||
<div class="card">
|
||||
<h3>Server composition</h3>
|
||||
<p><span class="mono">index.ts</span>: middleware stack, route mounting, startup ordering, workers, realtime setup.</p>
|
||||
<h3>Identity</h3>
|
||||
<p><span class="mono">auth.ts</span>, <span class="mono">middleware/auth.ts</span>, <span class="mono">middleware/device-auth.ts</span>, <span class="mono">utils/device-token.ts</span>.</p>
|
||||
<h3>Persistence schema</h3>
|
||||
<p><span class="mono">db/schema.ts</span> + <span class="mono">drizzle/*</span> migrations.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Realtime + command delivery</h3>
|
||||
<p><span class="mono">realtime/gateway.ts</span> and <span class="mono">routes/commands.ts</span>.</p>
|
||||
<h3>Streaming control</h3>
|
||||
<p><span class="mono">routes/streams.ts</span>, <span class="mono">media/*</span>, <span class="mono">routes/recordings.ts</span>.</p>
|
||||
<h3>Operational views</h3>
|
||||
<p><span class="mono">routes/ops.ts</span>, <span class="mono">observability/metrics.ts</span>, <span class="mono">routes/admin.ts</span>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="constraints">
|
||||
<h2>13) Current Constraints and Scaling Boundaries</h2>
|
||||
<div class="grid g3">
|
||||
<div class="card">
|
||||
<h3>State locality</h3>
|
||||
<p>Presence, rate-limit counters, metrics counters, and SFU registry are process-local. Horizontal scaling requires external shared state.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Media realism</h3>
|
||||
<p>Media provider is mock; SFU service is scaffold/noop. Production deployment needs real media infrastructure for reliability and scale.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>Queue semantics</h3>
|
||||
<p>Push and command retries are interval-based polling workers. Throughput, ordering guarantees, and dead-letter handling are minimal.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small">For load-bearing evolution, the natural next architecture step is extracting shared state (Redis/queue), production media plane, and distributed rate/metrics telemetry.</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user