realtime
realtime copied to clipboard
Realtime connection unable to reconnect after TIMED_OUT
Bug report
- [x] I confirm this is a bug with Supabase, not with my own application.
- [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
When the realtime connection is lost on a native iOS/Android device [using Expo], attempting to auto reconnect always results in a loop. Keeps disconnecting after a reconnect.
To Reproduce
After a successful SUBSCRIBED subscription status, minimise the mobile app and lock the phone for 3 seconds. After reverting back to the app you should get two CHANNEL_ERROR subscription statuses [If you stay in the app, supabase manages to reconnect most of the time automatically]
But if you repeat the process before this reconnection and minimise the mobile app and lock the phone for 3 seconds. Reverting back to the app results in two more CHANNEL_ERROR subscription statuses. Followed by a CLOSED. After 10 seconds this results in a TIMED_OUT. if you start the reconnection process within this 10 seconds - the subscription fluctuates between SUBSCRIBED and CLOSED in a loop
Expected behavior
After CLOSED, the user defined reconnection strategy should result in a successful SUBSCRIBED state. This works if i manually disconnect [the disconnect button at the top in the screenshot triggers a removeChannel call for the active subscription ]
Screenshots
[see status log in the black]
System information
- OS: iOS / Android native mobile applications [using Expo]
- Version of supabase-js: ^2.42.5
Additional context
The basic version of my reconnection strategy, i have tested this with various methods [like reconnectingFlag] etc etc but still failing to avoid the loop described above. I've also used a flag for channel status and to only reconnect when status is not 'joined' but the TIMED_OUT forces a CLOSED after it and i'm getting strange race conditions.
const subscriptionRef = useRef(null)
const reconnectAttemptsRef = useRef(0)
useEffect(() => {
if (user_id) {
subscribeToChannel()
}
return () => {
if (subscriptionRef.current) {
supabase.removeChannel(subscriptionRef.current)
}
}
}, [user_id])
const subscribeToChannel = () => {
subscriptionRef.current = supabase
.channel(`XXX:${user_id}`)
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'table',
filter: `user_id=eq.${user_id}`,
},
(payload) => {
// Reset reconnect attempts on successful message
reconnectAttemptsRef.current = 0;
}
)
.subscribe((status) => {
console.log({ status });
if (status === "SUBSCRIBED") {
console.log("Successfully subscribed");
reconnectAttemptsRef.current = 0;
} else if (status === "CLOSED") {
console.log("Subscription closed");
reconnectWithBackoff();
}
})
}
const reconnectWithBackoff = () => {
if (
reconnectAttemptsRef.current < 5
) {
const delay = 3000 * Math.pow(2, reconnectAttemptsRef.current)
console.log(`Reconnecting in ${delay}ms...`)
setTimeout(() => {
console.log(`Reconnect attempt ${reconnectAttemptsRef.current + 1}`)
subscribeToChannel()
reconnectAttemptsRef.current += 1
}, delay)
} else {
console.log('Max reconnection attempts reached. Please try again later.')
}
}