Never hang on "connecting": time out the socket and fall back

iOS Safari can leave a blocked wss:// upgrade hanging without firing
onclose/onerror, so the badge sat on "connecting". The socket now times out a
slow handshake after 5s, reports at most one failure per attempt, and the app
commits to polling after a 6s grace period if the socket never opens.
This commit is contained in:
matitos committed 2026-09-20 23:43:12 +00:00
1 parent 5af879bc2f
commit ed14019052
2 files changed
+43 -11

No files matched your search

+13 -5
View File
@@ -38,15 +38,17 @@ const busy = ref(false)
const connected = ref(false)
const socketError = ref('')
const socketFailures = ref(0)
// Some mobile browsers (notably iOS Safari with an auth-gated page) abort the
// wss:// upgrade even when the server accepts it. After a few failures we stop
// retrying, rely on the REST poll, and present it as a normal live state rather
// than a degraded "reconnecting".
const usePolling = computed(() => socketFailures.value >= 3)
const pollingFallback = ref(false)
// Some mobile browsers (notably iOS Safari with an auth-gated page) abort or
// silently hang the wss:// upgrade even when the server accepts it. We fall
// back to the REST poll after a few failures, or after a short grace period if
// the socket never opens at all, and present it as a normal live state.
const usePolling = computed(() => pollingFallback.value || socketFailures.value >= 3)
let socket
let pollTimer
let statusTimer
let graceTimer
async function refreshAll() {
const [p, o, e, q] = await Promise.all([
@@ -151,6 +153,11 @@ onMounted(async () => {
else socketError.value = `socket closed (code ${code})`
},
})
// If the socket hasn't opened shortly after load, commit to polling so the
// badge never hangs on "connecting".
graceTimer = setTimeout(() => {
if (!connected.value) pollingFallback.value = true
}, 6000)
// Poll fast enough that the UI feels live even when it is the only channel.
pollTimer = setInterval(refreshAll, 3000)
statusTimer = setInterval(() => api.getStatus().then((s) => (status.value = s)), 3000)
@@ -160,6 +167,7 @@ onUnmounted(() => {
socket?.close()
clearInterval(pollTimer)
clearInterval(statusTimer)
clearTimeout(graceTimer)
})
</script>