mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-05-13 01:46:08 +08:00
Unify bridge auth header and add live telemetry panel
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
import { createError, readBody } from 'h3'
|
||||
import { createError } from 'h3'
|
||||
import { requireUserSession } from '../../utils/auth'
|
||||
import { normalizeBridgeToken } from '../../utils/bridge'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
|
||||
interface ConnectBody {
|
||||
token?: string
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireUserSession(event)
|
||||
const body = await readBody<ConnectBody>(event)
|
||||
const token = normalizeBridgeToken(body.token)
|
||||
const token = getBridgeTokenFromHeader(event)
|
||||
|
||||
if (!token) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Ungültiger Token übergeben.' })
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
console.info(
|
||||
`\x1b[32m[bridge:connect]\x1b[0m token=\x1b[96m${token.slice(0, 6)}...\x1b[0m user=\x1b[92m${String(user._id)}\x1b[0m`,
|
||||
)
|
||||
|
||||
const now = new Date()
|
||||
|
||||
const document = await BridgeToken.findOneAndUpdate(
|
||||
@@ -47,4 +46,3 @@ export default defineEventHandler(async (event) => {
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { defineEventHandler, readBody, getHeader } from 'h3'
|
||||
import { resolveUserFromToken } from '../../utils/auth'
|
||||
import { createError, defineEventHandler, readBody } from 'h3'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import { flightlabTelemetryStore } from '../../utils/flightlabTelemetry'
|
||||
|
||||
/**
|
||||
* Receives MSFS SimConnect telemetry data from an external bridge application.
|
||||
*
|
||||
* The bridge should POST telemetry data with an Authorization header so we
|
||||
* The bridge should POST telemetry data with an x-bridge-token header so we
|
||||
* can route the data to the correct FlightLab WebSocket session.
|
||||
*
|
||||
* ──────────────────────────────────────────
|
||||
@@ -13,7 +14,7 @@ import { flightlabTelemetryStore } from '../../utils/flightlabTelemetry'
|
||||
* ──────────────────────────────────────────
|
||||
*
|
||||
* POST /api/bridge/data
|
||||
* Authorization: Bearer <user-jwt-token>
|
||||
* x-bridge-token: <bridge-token>
|
||||
* Content-Type: application/json
|
||||
*
|
||||
* {
|
||||
@@ -37,17 +38,23 @@ import { flightlabTelemetryStore } from '../../utils/flightlabTelemetry'
|
||||
* ──────────────────────────────────────────
|
||||
*/
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Resolve user from Bearer token (optional — also works with ?userId query param)
|
||||
const user = await resolveUserFromToken(event)
|
||||
const userId = user?._id?.toString()
|
||||
?? new URL(event.node.req.url ?? '', 'http://localhost').searchParams.get('userId')
|
||||
const bridgeToken = getBridgeTokenFromHeader(event)
|
||||
if (!bridgeToken) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
const bridgeDocument = await BridgeToken.findOne({ token: bridgeToken }).select('user')
|
||||
const userId = bridgeDocument?.user?.toString() ?? null
|
||||
if (!userId) {
|
||||
event.node.res.statusCode = 401
|
||||
return { error: 'Authorization required — send Bearer token or ?userId query param' }
|
||||
throw createError({ statusCode: 401, statusMessage: 'Bridge-Token ist nicht mit einem Nutzer verknüpft.' })
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
const telemetryKeys = body && typeof body === 'object' ? Object.keys(body as Record<string, unknown>) : []
|
||||
console.info(
|
||||
`\x1b[35m[bridge:data]\x1b[0m token=\x1b[96m${bridgeToken.slice(0, 6)}...\x1b[0m user=\x1b[92m${userId}\x1b[0m telemetryKeys=\x1b[92m${telemetryKeys.length}\x1b[0m payload=`,
|
||||
body,
|
||||
)
|
||||
|
||||
// Store telemetry and broadcast to WebSocket subscribers
|
||||
flightlabTelemetryStore.update(userId, body)
|
||||
|
||||
33
server/api/bridge/live.get.ts
Normal file
33
server/api/bridge/live.get.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createError } from 'h3'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import { flightlabTelemetryStore } from '../../utils/flightlabTelemetry'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const token = getBridgeTokenFromHeader(event)
|
||||
if (!token) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
const bridgeDocument = await BridgeToken.findOne({ token }).select('user')
|
||||
const userId = bridgeDocument?.user?.toString() ?? null
|
||||
|
||||
if (!userId) {
|
||||
return {
|
||||
connected: false,
|
||||
lastTelemetryAt: null,
|
||||
telemetry: null,
|
||||
}
|
||||
}
|
||||
|
||||
const telemetry = flightlabTelemetryStore.get(userId)
|
||||
const timestamp = telemetry && typeof telemetry.timestamp === 'number'
|
||||
? new Date(telemetry.timestamp).toISOString()
|
||||
: null
|
||||
|
||||
return {
|
||||
connected: true,
|
||||
lastTelemetryAt: timestamp,
|
||||
telemetry,
|
||||
}
|
||||
})
|
||||
@@ -1,16 +1,17 @@
|
||||
import { createError, getQuery } from 'h3'
|
||||
import { createError } from 'h3'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
import { normalizeBridgeToken } from '../../utils/bridge'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import type { UserDocument } from '../../models/User'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const token = normalizeBridgeToken(query.token as string | undefined)
|
||||
const token = getBridgeTokenFromHeader(event)
|
||||
|
||||
if (!token) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Token muss übergeben werden.' })
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
console.info(`\x1b[36m[bridge:me]\x1b[0m token=\x1b[96m${token.slice(0, 6)}...\x1b[0m request received`)
|
||||
|
||||
const document = await BridgeToken.findOne({ token }).populate('user', 'name email')
|
||||
|
||||
if (!document || !document.user) {
|
||||
@@ -41,4 +42,3 @@ export default defineEventHandler(async (event) => {
|
||||
lastStatusAt: document.lastStatusAt ? document.lastStatusAt.toISOString() : null,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { createError, readBody } from 'h3'
|
||||
import { BridgeToken } from '../../models/BridgeToken'
|
||||
import { normalizeBridgeToken } from '../../utils/bridge'
|
||||
import { getBridgeTokenFromHeader } from '../../utils/bridge'
|
||||
import type { UserDocument } from '../../models/User'
|
||||
|
||||
interface StatusBody {
|
||||
token?: string
|
||||
simConnected?: boolean
|
||||
flightActive?: boolean
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await readBody<StatusBody>(event)
|
||||
const token = normalizeBridgeToken(body.token)
|
||||
|
||||
const token = getBridgeTokenFromHeader(event)
|
||||
if (!token) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Token muss übergeben werden.' })
|
||||
throw createError({ statusCode: 401, statusMessage: 'x-bridge-token header fehlt oder ist ungültig.' })
|
||||
}
|
||||
|
||||
const body = await readBody<StatusBody>(event)
|
||||
console.info(
|
||||
`\x1b[33m[bridge:status]\x1b[0m token=\x1b[96m${token.slice(0, 6)}...\x1b[0m simConnected=\x1b[92m${String(body.simConnected)}\x1b[0m flightActive=\x1b[92m${String(body.flightActive)}\x1b[0m`,
|
||||
)
|
||||
|
||||
const updatePayload: Record<string, unknown> = {
|
||||
lastStatusAt: new Date(),
|
||||
}
|
||||
@@ -60,4 +62,3 @@ export default defineEventHandler(async (event) => {
|
||||
lastStatusAt: document.lastStatusAt ? document.lastStatusAt.toISOString() : null,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { getHeader, type H3Event } from 'h3'
|
||||
|
||||
export function normalizeBridgeToken(input: unknown) {
|
||||
if (typeof input !== 'string') {
|
||||
return null
|
||||
@@ -12,3 +14,6 @@ export function normalizeBridgeToken(input: unknown) {
|
||||
return token
|
||||
}
|
||||
|
||||
export function getBridgeTokenFromHeader(event: H3Event) {
|
||||
return normalizeBridgeToken(getHeader(event, 'x-bridge-token'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user