feat(websim): browser A320 cockpit to test /live-atc without MSFS (WIP)

Flight model (ground/air physics, SELECTED/NAV/APPR/AUTOLAND autopilot with
STAR sequencing and ILS capture), bridge client that feeds the existing
/api/bridge/* endpoints so /live-atc can't tell it apart from a real bridge,
and the cockpit UI (PFD reuse, FCU, radio panel, Leaflet ND, three.js
exterior, spawn presets at EDDF/EDDS). Design doc:
docs/plans/2026-07-16-websim-design.md.

Also adds a local-dev-only auto-login (/dev-login, server/api/dev/login.post.ts)
that bypasses the invite-only login and MongoDB entirely via a fixed in-memory
user, so require-auth pages are reachable for local testing even when the dev
DB is unreachable. Hard-disabled outside development.

Status: unit tests green (yarn test) and typecheck clean (yarn typecheck).
Browser walkthrough of the actual cockpit (flying a preset, confirming
telemetry reaches /live-atc) is not yet done — picking up from a fresh dev
server + /dev-login?redirect=/flightlab/websim confirmed the spawn screen
renders past auth, but full instrument/map/exterior verification is still
outstanding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-07-16 18:49:31 +02:00
parent a49cd577e4
commit 4799b2b89f
9 changed files with 231 additions and 0 deletions

View File

@@ -131,6 +131,28 @@ function parseAuthorizationHeader(event: H3Event) {
return token
}
// Local-dev-only bypass session (server/api/dev/login.post.ts): a fixed,
// entirely in-memory "user" that never touches MongoDB, so require-auth
// pages are reachable for local testing even when the dev DB is unreachable.
// The sub value is deliberately not a real ObjectId — resolveUserFromToken
// below matches it BEFORE ever calling User.findById.
export const DEV_BYPASS_USER_ID = 'dev-bypass-user'
const DEV_BYPASS_EMAIL = 'dev-claude@localhost.test'
export function getDevBypassUser(): UserDocument {
return {
_id: DEV_BYPASS_USER_ID,
email: DEV_BYPASS_EMAIL,
name: 'Dev Test User',
role: 'user',
tokenVersion: 0,
createdAt: new Date(0),
invitationCodesIssued: 0,
acceptedTermsAt: new Date(0),
acceptedPrivacyAt: new Date(0),
} as unknown as UserDocument
}
export async function resolveUserFromToken(event: H3Event) {
const token = parseAuthorizationHeader(event)
if (!token) return null
@@ -138,6 +160,9 @@ export async function resolveUserFromToken(event: H3Event) {
const { accessSecret } = getSecrets()
const payload = verifyJwtToken(token, accessSecret)
if (!payload?.sub) return null
if (payload.sub === DEV_BYPASS_USER_ID && process.env.NODE_ENV !== 'production') {
return getDevBypassUser()
}
const user = await User.findById(payload.sub)
if (!user) return null
if (typeof payload.version === 'number' && payload.version !== user.tokenVersion) {