diff --git a/app/pages/liveatc.vue b/app/pages/liveatc.vue index 1248a7c..007efc0 100644 --- a/app/pages/liveatc.vue +++ b/app/pages/liveatc.vue @@ -183,11 +183,27 @@ import { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue' import { useAtcEngine } from '~~/shared/atc/engine' import { getPhaseOrder, getPhase } from '~~/shared/atc/phases' import type { Transmission, FlightPlan } from '~~/shared/atc/types' +import { useApi } from '~/composables/useApi' +import { useAuthStore } from '~/stores/auth' + +definePageMeta({ middleware: ['require-auth'] }) // For radio effects (conditional import) let applyRadioEffect: ((audioBuffer: AudioBuffer, ctx: AudioContext, level: number) => AudioBuffer) | null = null -const engine = useAtcEngine() +const api = useApi() +const auth = useAuthStore() + +// Create an authenticated fetch wrapper for the engine +function authFetch(url: string, opts: any = {}): Promise { + const headers = { ...opts.headers } as Record + if (auth.accessToken) { + headers.Authorization = `Bearer ${auth.accessToken}` + } + return $fetch(url, { ...opts, headers }) as Promise +} + +const engine = useAtcEngine({ apiFetch: authFetch as any }) const screen = ref<'setup' | 'session'>('setup') const processing = ref(false) const isSpeaking = ref(false) @@ -316,9 +332,8 @@ async function handleRecordingComplete(payload: { audio: string; format: string processing.value = true try { // 1. STT via ptt endpoint - const sttRes = await $fetch<{ success: boolean; transcription: string }>('/api/atc/ptt', { - method: 'POST', - body: { audio: payload.audio, format: payload.format }, + const sttRes = await api.post<{ success: boolean; transcription: string }>('/api/atc/ptt', { + audio: payload.audio, format: payload.format, }) if (!sttRes.success || !sttRes.transcription) return @@ -369,17 +384,14 @@ async function speakAtc(text: string) { async function speakSingle(text: string) { try { - const res = await $fetch<{ + const res = await api.post<{ success: boolean audio: { base64: string; mime: string } normalized: string }>('/api/atc/say', { - method: 'POST', - body: { - text, - level: settings.value.signalLevel, - speed: settings.value.speechSpeed, - }, + text, + level: settings.value.signalLevel, + speed: settings.value.speechSpeed, }) if (!res.success || !res.audio?.base64) return @@ -431,10 +443,7 @@ function startTelemetryPoll() { stopTelemetryPoll() telemetryInterval = setInterval(async () => { try { - const data = await $fetch('/api/bridge/data', { - method: 'POST', - body: { status: 'poll' }, - }).catch(() => null) + const data = await api.post('/api/bridge/data', { status: 'poll' }).catch(() => null) if (data && typeof data === 'object') { telemetryConnected.value = true engine.updateTelemetry({ diff --git a/server/middleware/auth.global.ts b/server/middleware/auth.global.ts index 5656cdc..3c8f7ac 100644 --- a/server/middleware/auth.global.ts +++ b/server/middleware/auth.global.ts @@ -6,7 +6,7 @@ export default defineEventHandler(async (event) => { if (!url.pathname.startsWith('/api/')) { return } - if (url.pathname.startsWith('/api/atc/')) { + if (url.pathname.startsWith('/api/atc/say')) { return } if (url.pathname.startsWith('/api/service/')) { @@ -15,7 +15,6 @@ export default defineEventHandler(async (event) => { if (url.pathname.startsWith('/api/bridge/')) { return } - // /api/decision-flows/ was removed in Live ATC v2 if (event.node.req.method === 'OPTIONS') { return } diff --git a/shared/atc/engine.ts b/shared/atc/engine.ts index 2d62d92..003fa18 100644 --- a/shared/atc/engine.ts +++ b/shared/atc/engine.ts @@ -42,7 +42,13 @@ function makeDefaultState(): EngineState { } } -export function useAtcEngine() { +export interface AtcEngineOptions { + /** Custom fetch function (e.g. with auth headers). Defaults to $fetch. */ + apiFetch?: typeof $fetch +} + +export function useAtcEngine(options: AtcEngineOptions = {}) { + const fetchFn = options.apiFetch ?? $fetch const state = reactive(makeDefaultState()) const currentPhase = computed(() => getPhase(state.currentPhase)) @@ -178,7 +184,7 @@ export function useAtcEngine() { recentTransmissions: recentTx, } - const res = await $fetch('/api/atc/route', { + const res = await fetchFn('/api/atc/route', { method: 'POST', body: req, }) @@ -201,7 +207,7 @@ export function useAtcEngine() { const destName = interaction.id === 'request_taxi' ? state.vars.runway : state.vars.arrival_stand const airport = interaction.id === 'request_taxi' ? state.vars.dep : state.vars.dest if (originName && destName && airport) { - const taxiRes = await $fetch('/api/service/tools/taxiroute', { + const taxiRes = await fetchFn('/api/service/tools/taxiroute', { params: { airport, origin_name: originName, dest_name: destName }, }) if (taxiRes?.names_collapsed?.length) {