ably-js
ably-js copied to clipboard
React Native: distinguish disconnected state and suspended state for app goes to background
We are trying to handle the state difference for a Native React app that requires going to background frequently. The difference state would lead us to different behaviors as:
- if the app is only disconnected for a short time within 2mins, the app doesn't need to ask server to publish messages for the latest server state, but rely on Ably to buffer based on https://faqs.ably.com/connection-state-recovery
- if the app is disconnected for a longer time that Ably treats it as connection closed, we will have to ask the server to publish the latest server state to the app's Ably channel again
Our current implementation is like this atm:
this.ably?.connection.on('disconnected', () => {
// ask server to send a latest state
});
This is however triggered with logs like this for the app put into background for 15s:
Ably: Transport.onIdleTimerExpire(): No activity seen from realtime in 33964ms; assuming connection has dropped
and triggered disconnected
callback regardless.
For 2mins longer, the behavior is exactly the same as above with disconnected
event, without a suspended
event.
Can we distinguish the difference in the SDK for client going to background longer than 2mins with a suspended
event as the fact that the connection is closed by Ably server?
➤ Automation for Jira commented:
The link to the corresponding Jira issue is https://ably.atlassian.net/browse/SDK-4006
Hi @yibo-long,
Thanks for bringing this to our attention! Let me clarify: you mentioned that you don't receive the suspended
state when the client goes to the background for longer than 2 minutes. Can you double-check that you are subscribed to it, for example, using ably.connection.on('suspended', ...);
?
I did with:
this.ably?.connection.on('suspended', () => {
console.log('connection suspended');
});
this.ably?.connection.on('closed', () => {
console.log('connection closed');
});
this.ably?.connection.on('disconnected', () => {
console.log('connection disconnected');
});
this.ably?.connection.on('connected', () => {
console.log('connection connected');
});
I strictly checked Ably Dev console to wait for Connection Closed
event to bring back app to foreground.
Both suspended and closed were not popped up from logs, and disconnected connected events were logged in sequence in milliseconds gap:
WARN 08:59:38.385 Ably: Transport.onIdleTimerExpire(): No activity seen from realtime in 155098ms; assuming connection has dropped
LOG XXX: connection disconnected
LOG XXX: connection connected
Thank you @yibo-long! We will investigate this internally. Could you, by any chance, provide a reproducible example?
Hello @yibo-long, could you please provide more details? Specifically, in which environment can you reproduce the bug? Is it on an iPhone or Android device? Additionally, could you clarify whether it occurs on an actual device or simulator? Are there any other steps that can help us reproduce the issue?
Hi sorry, I don't have a public reproducible example yet.
I only tested it on ios simulator and real ios debug app this moment, but can definitely try it in more environments.
Let me clarify the steps to reproduce:
- app setup: Have a singleton
AblyService
class that wrapsAbly.Realtime
initialization withauthCallback
and channel subscription. It's initialized in React Native's root app; - Open up app and wait for
AblyService
to create connection, channel subscription and subscribe todisconnected
,suspended
,closed
events; - Checked Ably dev console that there is a log
Channel Opened
event; - Bring app to background;
- In ~20s or a bit longer, checked Ably dev console that there is a log
Transport Closed
event for the app going offline. - Wait for another exact 2min after the
Transport Closed
event, to check theConnection Closed
event for the connection is forced closed by Ably. Between 3.-6., there is no log from the app as it's completely in background without activity. - Bring app back to foreground, only
disconnected
event is triggered. No 'suspend' event to indicate that any messages after the 2mins are lost.
Thank you very much @yibo-long. Could you provide insights into the urgency of this issue? We are currently in the middle of the JS v2 release, and our resources are limited at the moment. Would it be ok if we come back to it in 2 weeks?
it appears that Android has completely different behaviors. The app was not suspended, and the connection was still connected when app is in the background. Still it only received 'disconnected' event when putting to background for too long.
Thank you @ttypic for providing the context! This is not an urgent request but more a question at this moment for clarification.
We want to know how to distinguish the difference Ably mentioned in terms of Transport Closed vs Connection Closed in the client.
@yibo-long The recommended approach is to listen for the 'suspended'
event. However, it appears that this method may not function as intended when the app is in the background. As a workaround, you can consider the following options:
-
Subscribe to the React Native's
AppState
and, when the app transitions to the background, treat it as if the connection has been suspended, prompting the need to refetch data. -
Run Ably in Headless JS. Unfortunately, please note that this solution is applicable only for Android.
Hi @yibo-long, I'm taking a look into this issue. I can confirm that I've reproduced the behaviour you're seeing, when running in the iOS Simulator.
It's worth mentioning here that the behaviour you're seeing is not exclusive to React Native. The same behaviour can be observed by running ably-js in a browser, and putting your computer to sleep for ~2.5 minutes and then waking it up again. You'll observe the same connection state transition sequence (connected
→ disconnected
(after waking up) → connecting
→ connected
).
I believe that not seeing a state transition to suspended
is an expected behaviour here. Our recommended way to discover that channel continuity has been lost is not to listen for the connection becoming suspended
, but to listen for a channel attached
event with resumed == false
. To quote from the support article that you linked to (emphasis mine):
Once the connection is reestablished, the client library will reattach the suspended channels automatically and emit an attached event with the resumed flag set to false. This ensures that as a developer, you can listen for attached events and check the resumed flag to see if a channel resumed fully and no messages were lost (when resumed is true), or the channel attached but could not resume (when resumed is false).
I started writing an example for you of what this would look like in React Native, and wished to demonstrate how to fetch the messages that were not delivered (using the history API), but have run into an issue which I am currently seeking help with internally. I will get back to you once I have an update on this.
Thanks @lawrence-forooghian I wanted to give it a try with attached
event, but so far failed to receive any channel level callbacks like this:
ably?.connection.on('disconnected', () => {
console.log('AblyService: disconnected');
channel.whenState('attached', (changeStateChange) => {
console.log('AblyService: attached to channel', this.channelName, changeStateChange);
});
});
There seems like a .on
callback function at channel level like the one at connection level, so I am not sure if it's subscribing at after connection disconnected already too late.
For 25s Transport Closed, I got logs from above code as:
LOG AblyService: disconnected
LOG AblyService: attached to channel c:19523ec5747b400281f1d0c2dee2a391:5A989E2A-2179-4EC0-A120-E794D267ACE4 undefined
For ~2.5min Connection Closed, I got logs from above code as:
WARN 14:19:07.275 Ably: Transport.onIdleTimerExpire(): No activity seen from realtime in 186150ms; assuming connection has dropped
LOG AblyService: disconnected
LOG AblyService: attached to channel c:19523ec5747b400281f1d0c2dee2a391:5A989E2A-2179-4EC0-A120-E794D267ACE4 undefined
found .on function at channel level, and I think I made it work with following code:
this.ably.connection.whenState('connected', () => {
const channel = this.ably?.channels.get(this.channelName ?? '');
channel?.on('attached', (changeStateChange) => {
console.log('AblyService: attached to channel', this.channelName, changeStateChange);
});
});
found .on function at channel level, and I think I made it work with following code:
this.ably.connection.whenState('connected', () => { const channel = this.ably?.channels.get(this.channelName ?? ''); channel?.on('attached', (changeStateChange) => { console.log('AblyService: attached to channel', this.channelName, changeStateChange); }); });
Yes, channel.on
is the right thing to use. To detect a loss in channel continuity, you should then check the value of changeStateChange.resumed
. If it‘s false
, then channel continuity has been lost and if you need to access these messages then you'll have to fetch them from history (this fetching from history is the thing that I mentioned I was having issues with in https://github.com/ably/ably-js/issues/1559#issuecomment-1908548893).
On another note, I am wondering what was your motivation for wrapping your attached
listener inside .whenState('connected')
? That shouldn’t be necessary.
For 25s Transport Closed, I got logs from above code as:
LOG AblyService: disconnected LOG AblyService: attached to channel c:19523ec5747b400281f1d0c2dee2a391:5A989E2A-2179-4EC0-A120-E794D267ACE4 undefined
I've also noticed from your log that the channel’s whenState
method emitted a state change of undefined
. This is a bug, and I've raised https://github.com/ably/ably-js/issues/1598 for it.
Thanks @lawrence-forooghian ! I verified it fixed our issue.
On another note, I am wondering what was your motivation for wrapping your attached listener inside .whenState('connected')? That shouldn’t be necessary.
We are currently using authCallback with backend sending us the authenticated channelName and token, so can only get a valid channel name to hook with until that. We are refactoring some of those to be based on Ably hooks so hopefully won't need to maintain the callbacks in such way later.
Hi @yibo-long, do you still need help with this issue? I can post the example code that I was referring to in https://github.com/ably/ably-js/issues/1559#issuecomment-1908548893 if that would help (the problem that I encountered was due to a backend bug that's now fixed).
@lawrence-forooghian we are all good now, thanks so much for the help!