This document records the current architecture, the target architecture, and a phased migration plan so that the three strategic goals can be added additively rather than as rewrites.
Strategic goals (see also CLAUDE.md):
Single Node process (server/server.js, ~640 lines)
├── HTTP JSON API (/api/*, cookie-session auth)
├── WebSocket signaling (/ws — SDP/ICE relay, consent, share codes)
├── Static file serving (public/*.html, single-file pages, no build)
└── In-process state liveSessions / onlineAgents / pendingShares (Maps)
Data: node:sqlite single file (server/data.db)
teams, users, sessions_auth, machines, audit_log, sessions_log
Media: WebRTC P2P (1:1). STUN + managed TURN. Media never traverses the server.
Auth: scrypt passwords, opaque session token in an HttpOnly `sid` cookie. TOTP code exists but
login currently marks sessions MFA-passed directly.
Integrations: outbound webhook (single env URL, `session.ended`, HMAC-signed);
inbound SSO (`/sso`, custom HMAC token).
Recordings/transcripts: written to local disk (server/recordings, server/transcripts).
/ws signaling — standards-based; reused as-is by native apps and an SFU.| # | Constraint | Blocks |
|---|---|---|
| C1 | Auth is cookie-only (parseCookies(req).sid); no Authorization: Bearer, no API keys |
Mobile, Integrations |
| C2 | No API versioning (/api/...) |
Mobile (shipped clients pin a contract) |
| C3 | team is a thin tenant (id,name,created_at); app assumes one team |
Licensing |
| C4 | Session state in process memory (Maps) | Horizontal scale, Meetings |
| C5 | SQLite single-writer, queries inline at ~100 call-sites | Scale, multi-tenant isolation |
| C6 | P2P mesh only — no SFU | Multi-party meetings (Zoom-like) |
| C7 | Recordings on local disk | Multi-instance, per-org storage quotas |
| C8 | Monolithic server.js mixes HTTP/WS/static/logic/DB |
All (maintainability) |
org_id. Billing, seats, plan, and feature flags hang off the Organization./api/v1; auth accepted via cookie
(web) or Authorization: Bearer (mobile) or scoped API key (integrations). ┌──────────── clients ────────────┐
│ web (HTML) mobile (native) 3rd-party (API key) │
└───────┬───────────┬───────────────┬──────────────┘
│ cookie │ Bearer │ API key
┌───────▼───────────▼───────────────▼──────────────┐
│ API v1 (routes → services → repository) │
│ authN (cookie/Bearer/key) · authZ (RBAC) │
│ entitlements middleware (plan/seat/feature) │
└───────┬───────────────────────┬──────────────────┘
│ │
┌─────────▼────────┐ ┌─────────▼─────────┐
│ Repository layer │ │ Signaling (ws) │──── Redis (shared state,
│ (SQLite→Postgres)│ │ + SFU for meetings│ pub/sub across instances)
└─────────┬────────┘ └───────────────────┘
│
Postgres · Object storage (recordings) · usage-metering
/api/v1 (C2) — stable, documented contract./ws via react-native-webrtc/native SDKs; native screen capture
(ReplayKit / MediaProjection) for the phone-can’t-share gap./sso.plan, seats, status, trial_ends_at, feature flags.Priority (set by the user 2026-06-11): mobile + integration first; licensing last. Principle unchanged: do the shared groundwork first, so later work is additive. Because licensing is last, the full Organization entity moves to Phase 3 with it — Phase 1 keeps only a tenant-id abstraction (mapping to today’s
team_id) so Phase-2 auth/keys don’t need reworking when the tenant is later elevated to a full Organization.
repos.js) — all SQL moved out of server.js.server.js → config / lib / session / presence / routes / static / signaling
(plus repos data layer and bizgaze service). server.js is now a thin entry point.tenantId, == team_id today);
every query is tenant-scoped. No Organization entity / plan-seats yet — that’s Phase 3.test/e2e.js (21/21) before and after.Target clients: web (cookie), native Android/iOS, a native Windows desktop app where the
viewer CONTROLS the remote screen (reuses the WebRTC inputChannel + OS input injection like
agent/), and third-party systems (API keys). All authenticate through this one access layer.
/api/v1 — every /api/* route aliased under /api/v1/* (routes.js); web keeps unversioned paths.Authorization: Bearer <token> accepted in currentUser() across HTTP + WS, alongside the
cookie (session.js tokenFromReq); /api/login now also returns the token for native clients.
WS upgrades carry the token in the Authorization header (native) or ?access_token= (browser fallback).organization (additive migration: add organizations, keep team_id
as alias/FK); add plan, seats, status, features columns.liveSessions/onlineAgents/pendingShares + cross-instance pub/sub.sessions_auth already works that way.