diff --git a/app/pages/bridge/connect.vue b/app/pages/bridge/connect.vue index 22bc2e8..40047fa 100644 --- a/app/pages/bridge/connect.vue +++ b/app/pages/bridge/connect.vue @@ -1,7 +1,11 @@ @@ -340,12 +283,6 @@ interface BridgeStatusPayload { lastStatusAt: string | null } -interface BridgeLivePayload { - connected: boolean - lastTelemetryAt: string | null - telemetry: Record | null -} - useHead({ title: 'Link Bridge · OpenSquawk' }) const route = useRoute() @@ -364,11 +301,6 @@ const statusInitialized = ref(false) const statusError = ref('') const statusLoading = ref(false) const statusRequestActive = ref(false) -const liveTelemetry = ref(null) -const liveTelemetryOpen = ref(false) -const liveTelemetryLoading = ref(false) -const liveTelemetryError = ref('') -const liveTelemetryRequestActive = ref(false) const copiedToken = ref(false) const manualTokenInput = ref('') @@ -388,7 +320,13 @@ const token = computed(() => { }) const hasToken = computed(() => token.value.length >= 6) -const manualTokenSlots = computed(() => Array.from({ length: 6 }, (_unused, index) => manualTokenInput.value[index] || '')) +const hasQueryToken = computed(() => { + const value = route.query.token + if (Array.isArray(value)) { + return Boolean(value[0]) + } + return typeof value === 'string' && value.trim().length > 0 +}) const manualTokenRemaining = computed(() => Math.max(0, 6 - manualTokenInput.value.length)) const manualTokenReady = computed(() => manualTokenRemaining.value === 0) const loginTarget = computed(() => `/login?redirect=${encodeURIComponent(route.fullPath || '/bridge')}`) @@ -399,30 +337,17 @@ const authDisplayName = computed(() => { }) const connectionLabel = computed(() => { + if (!hasToken.value) { + return 'Pairing code missing' + } if (!connectionStatus.value?.connected) { - return 'Waiting for the desktop app' + return 'Waiting for confirmation' } const name = connectionStatus.value.user?.name || connectionStatus.value.user?.email - return name ? `Linked as ${name}` : 'Linked' -}) - -const connectionSubLabel = computed(() => { - if (!connectionStatus.value?.connected) return null - return connectionStatus.value.user?.email && connectionStatus.value.user.email !== connectionStatus.value.user?.name - ? connectionStatus.value.user.email - : null + return name ? `Linked as ${name}` : 'Linked successfully' }) const formattedConnectedAt = computed(() => formatTimestamp(connectionStatus.value?.connectedAt ?? null)) -const formattedLastStatusAt = computed(() => formatTimestamp(connectionStatus.value?.lastStatusAt ?? null)) -const formattedLastTelemetryAt = computed(() => formatTimestamp(liveTelemetry.value?.lastTelemetryAt ?? null)) -const liveTelemetryJson = computed(() => { - if (!liveTelemetry.value?.telemetry) { - return null - } - return JSON.stringify(liveTelemetry.value.telemetry, null, 2) -}) - const successBannerVisible = computed(() => connectSuccess.value || Boolean(connectionStatus.value?.connected)) const alreadyLinked = computed(() => Boolean(connectionStatus.value?.connected)) const successSparks = Array.from({ length: 14 }, (_unused, index) => index) @@ -469,6 +394,18 @@ async function applyPairingCode() { }) } +async function clearPairingCode() { + manualTokenInput.value = '' + manualTokenError.value = '' + connectError.value = '' + disconnectError.value = '' + + const query = { ...route.query } + delete query.token + + await router.replace({ query }) +} + function formatTimestamp(value: string | null) { if (!value) { return '—' @@ -491,6 +428,7 @@ async function connectBridge() { connectLoading.value = true connectError.value = '' disconnectError.value = '' + try { await $fetch('/api/bridge/connect', { method: 'POST', @@ -499,6 +437,7 @@ async function connectBridge() { 'x-bridge-token': token.value, }, }) + connectSuccess.value = true await fetchStatus(true) } catch (err: any) { @@ -529,6 +468,7 @@ async function disconnectBridge() { 'x-bridge-token': token.value, }, }) + connectSuccess.value = false autoConnectAttemptKey.value = `${token.value}:${accessToken.value}` await fetchStatus(true) @@ -547,14 +487,17 @@ async function maybeAutoConnect() { if (!hasToken.value || !initialized.value || !isAuthenticated.value || !accessToken.value || connectLoading.value) { return } + const attemptKey = `${token.value}:${accessToken.value}` if (autoConnectAttemptKey.value === attemptKey) { return } + if (connectionStatus.value?.connected) { autoConnectAttemptKey.value = attemptKey return } + autoConnectAttemptKey.value = attemptKey await connectBridge() } @@ -578,13 +521,14 @@ async function fetchStatus(force = false) { 'x-bridge-token': token.value, }, }) + connectionStatus.value = response statusError.value = '' statusInitialized.value = true + if (response.connected) { connectSuccess.value = true } - await fetchLiveTelemetry(force) } catch (err: any) { statusError.value = err?.data?.statusMessage || @@ -597,50 +541,18 @@ async function fetchStatus(force = false) { } } -async function fetchLiveTelemetry(force = false) { - if (!hasToken.value) { - return - } - if (liveTelemetryRequestActive.value) { - return - } - - liveTelemetryRequestActive.value = true - if (force || !liveTelemetry.value) { - liveTelemetryLoading.value = true - } - - try { - const response = await $fetch('/api/bridge/live', { - headers: { - 'x-bridge-token': token.value, - }, - }) - liveTelemetry.value = response - liveTelemetryError.value = '' - } catch (err: any) { - liveTelemetryError.value = - err?.data?.statusMessage || - err?.response?._data?.statusMessage || - err?.message || - 'We could not fetch live telemetry.' - } finally { - liveTelemetryRequestActive.value = false - liveTelemetryLoading.value = false - } -} - async function copyToken() { if (!hasToken.value) return if (typeof navigator === 'undefined' || !navigator.clipboard) return + try { await navigator.clipboard.writeText(token.value) copiedToken.value = true setTimeout(() => { copiedToken.value = false - }, 2000) + }, 1800) } catch { - // Ignore copy errors – clipboard access is optional + // Ignore clipboard errors. } } @@ -655,9 +567,11 @@ function stopPolling() { function startPolling() { stopPolling() + if (!hasToken.value) { return } + fetchStatus(!statusInitialized.value) poller = setInterval(() => { fetchStatus() @@ -672,14 +586,13 @@ watch(token, () => { connectionStatus.value = null statusInitialized.value = false statusError.value = '' - liveTelemetry.value = null - liveTelemetryError.value = '' - liveTelemetryOpen.value = false + if (successBlastTimer) { clearTimeout(successBlastTimer) successBlastTimer = null } successBlastVisible.value = false + startPolling() }) @@ -718,6 +631,7 @@ onMounted(() => { onBeforeUnmount(() => { stopPolling() + if (successBlastTimer) { clearTimeout(successBlastTimer) successBlastTimer = null @@ -726,8 +640,131 @@ onBeforeUnmount(() => {