Fall back to REST polling when the WebSocket cannot connect
CI/CD / backend (pull_request) Successful in 6m6s
CI/CD / frontend (pull_request) Successful in 5m15s
CI/CD / build (pull_request) Skipped
CI/CD / deploy (pull_request) Skipped
CI/CD / cleanup (pull_request) Skipped

On some mobile networks/browsers the wss:// upgrade is blocked even though the
page loads (the server side is verified working: a browser-style handshake gets
101). After three socket failures the header now shows "polling (5s)" instead
of a misleading perpetual "reconnecting", since the 5s REST poll keeps all data
correct. The socket error handler also surfaces the browser's message.
This commit is contained in:
matitos committed 2026-09-20 12:48:10 +00:00
1 parent 07c17fbd2c
commit bf7eda3f6a
2 files changed
+16 -4

No files matched your search

+10 -3
View File
@@ -1,5 +1,5 @@
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import * as api from './api'
import BacktestPanel from './components/BacktestPanel.vue'
import EquityChart from './components/EquityChart.vue'
@@ -37,6 +37,10 @@ const equityPoints = ref([])
const busy = ref(false)
const connected = ref(false)
const socketError = ref('')
const socketFailures = ref(0)
// After a few failed socket attempts (some mobile browsers/DPIs refuse wss://),
// stop advertising "reconnecting" and rely on the 5s REST poll instead.
const usePolling = computed(() => socketFailures.value >= 3)
let socket
let pollTimer
@@ -139,6 +143,7 @@ onMounted(async () => {
},
onClose: () => (connected.value = false),
onError: (code) => {
socketFailures.value += 1
if (code === 1008) socketError.value = 'rejected by the server (token/auth)'
else socketError.value = `socket closed (code ${code})`
},
@@ -166,9 +171,11 @@ onUnmounted(() => {
<span class="flex items-center gap-1.5 text-xs text-slate-400">
<span
class="h-2 w-2 rounded-full"
:class="connected ? 'bg-emerald-400' : 'bg-slate-500'"
:class="connected ? 'bg-emerald-400' : usePolling ? 'bg-sky-400' : 'bg-slate-500'"
></span>
{{ connected ? 'live' : 'reconnecting' }}
<template v-if="connected">live</template>
<template v-else-if="usePolling">polling (5s)</template>
<template v-else>reconnecting</template>
<span v-if="!connected && socketError" class="text-amber-300">· {{ socketError }}</span>
</span>
</div>
+6 -1
View File
@@ -58,7 +58,12 @@ export function openSocket({ onMessage, onOpen, onClose, onError }) {
onError?.(event.code, event.reason)
schedule()
}
socket.onerror = () => socket.close()
socket.onerror = (event) => {
// The browser hides details here; surface what it does give us.
const detail = event?.message || 'connection error'
onError?.(0, detail)
socket.close()
}
}
connect()