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:
1 parent
5af879bc2f
commit
ed14019052
2 files changed
+43
-11
No files matched your search
+30
-6
@@ -45,23 +45,47 @@ export function openSocket({ onMessage, onOpen, onClose, onError }) {
|
||||
|
||||
const connect = () => {
|
||||
if (closed) return
|
||||
let opened = false
|
||||
let reported = false
|
||||
socket = new WebSocket(url)
|
||||
|
||||
// Report at most one failure per attempt (Safari often fires error *and*
|
||||
// close for the same bad handshake).
|
||||
const reportFailure = (code, detail) => {
|
||||
if (reported) return
|
||||
reported = true
|
||||
onError?.(code, detail)
|
||||
}
|
||||
|
||||
// Some mobile browsers leave a blocked upgrade hanging without firing
|
||||
// onclose/onerror. Treat a slow handshake as a failure so the caller can
|
||||
// fall back to polling instead of waiting forever.
|
||||
const openTimeout = setTimeout(() => {
|
||||
if (!opened) {
|
||||
reportFailure(0, 'handshake timed out')
|
||||
socket.close()
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
socket.onopen = () => {
|
||||
opened = true
|
||||
clearTimeout(openTimeout)
|
||||
attempt = 0
|
||||
onOpen?.()
|
||||
}
|
||||
socket.onmessage = (event) => onMessage?.(JSON.parse(event.data))
|
||||
socket.onclose = (event) => {
|
||||
clearTimeout(openTimeout)
|
||||
onClose?.()
|
||||
// 1008 = policy violation (token rejected); 1006 = abnormal close, which
|
||||
// is what an auth/proxy rejection looks like to the browser.
|
||||
onError?.(event.code, event.reason)
|
||||
if (!opened) {
|
||||
// 1008 = policy violation (token rejected); 1006 = abnormal close.
|
||||
reportFailure(event.code, event.reason)
|
||||
}
|
||||
schedule()
|
||||
}
|
||||
socket.onerror = (event) => {
|
||||
// The browser hides details here; surface what it does give us.
|
||||
const detail = event?.message || 'connection error'
|
||||
onError?.(0, detail)
|
||||
clearTimeout(openTimeout)
|
||||
if (!opened) reportFailure(0, event?.message || 'connection error')
|
||||
socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user