What is the recommended approach for capturing an event where the users WIFI/ network disconnects?
Currently onDisconnected captures when the server resets the connection but not when the users internet connection drops. In my case I would like to completely reload my initial query and subscribe again to new queries if a users connection on their device drops out. The purpose of this would be to keep subscription items in sync should the users connection drop out.
onDisconnect we are checking the connection status (React Native) and updating store. When we came online again we re-subscribe.
This logic handled in onConnected & onReconnected
also having this problem and not sure how to handle it. @schavaLogi can you show some sample code of how you disconnect => update the store, reconnect => resubscribe?
We have saga middleware to handle these events and update store. Along with that, we implemented offline queue based on the socket connection state.
Subscriptions: the only subscription must be re-initialized
Mutation/Query: No need to trigger it be taken care offline queue
===========>
Note: connection params and reconnect
reconnect: true,
reconnectionAttempts: (MAX_CONNECTION_ATTEMPTS),
timeout: (wsTimeout * TIME_FACTOR) + 5,
<===========
let initSubscriptions = false;
function wsListener(client) {
return eventChannel((emit) => {
client.subscriptionClient.onConnected(() => {
if (!initSubscriptions) {
// Initialize only once at the time of connection init
initAllSubscriptions(emit);
initSubscriptions = true;
}
wsClient.updateStatus(WebSocket.OPEN);
emit(ActionCreators.connectedWebSocket());
});
client.subscriptionClient.onReconnecting(() => {
wsClient.updateStatus(WebSocket.CONNECTING);
});
client.subscriptionClient.onReconnected(() => {
if (!initSubscriptions) {
// Initialize only once at the time of connection init
initAllSubscriptions(emit);
initSubscriptions = true;
}
Logger.debug("websocket-saga", "onDisconnected", "State changed to Disconnected -> Connected");
wsClient.updateStatus(WebSocket.OPEN);
emit(ActionCreators.connectedWebSocket());
});
/**
* Very tricky - Library give only this callback when trying to re-connect and fails
* closedByUser - No Retry internally -> Send Disconnect immediately
* closedByUser Will be False when Network/Server disconnect -> Wait for timeout and send Disconnect
*
* Update State(Disconnected) only when we exceed RetryCount/Timeout or Closed By User
*/
client.subscriptionClient.onDisconnected(() => {
Logger.debug("websocket-saga", "onDisconnected", "Started");
if (wsClient.getWebSocketLink() != null && wsClient.isConnected()) {
Logger.debug("websocket-saga", "onDisconnected", "State changed to Connected->Disconnected");
// Connected->Disconnected log the stable connection time information
const wsTimer = Timer.stop(WS_CONNECTION_MEASURE);
const action = ActionCreators.reConnectingWebSocket();
emit(ActionCreators.makeSkipOfflineQueue(action, true));
// Log event only when Internet present but socket disconnected
updateInterNetConnectionStatus(emit).then((isConnected) => {
if (isConnected) {
// TODO
}
});
// Close gate and queue all offline requests to be safe
wsClient.updateStatus(WebSocket.CLOSED);
} else if (wsClient.canTryInternally()) {
wsClient.incrementRetryCount();
} else {
// Trigger network check to confirm due internet down or server not reachable
updateInterNetConnectionStatus(emit).then((isConnected) => {
if (isConnected) {
// TODO
}
// Send action to show error info UI layer
const action = ActionCreators.remoteDisconnectedWebSocket();
emit(ActionCreators.makeSkipOfflineQueue(action, true));
});
}
});
return () => {
wsClient.close(true, true);
};
});
}
const socketChannel = yield call(wsListener, wsClient);
// Run all other saga's which are created for API's
const { cancel } = yield race({
task: all([
call(rootWebSocketSaga),
call(watchReconnectConnection),
call(onMessageReceived, socketChannel),
]),
cancel: take(WsActionTypes.WS_CLIENT_CLOSE),
});
// Send disconnected action as channel is closed
if (cancel) {
initSubscriptions = false;
socketChannel.close();
yield put(ActionCreators.disconnectedWebSocket());
}
In case the client disconnected to the internet, the onDisconnected event will not be fired on the server. In my opinion, the reason is the client doesn't send the keep-alive packet to the server. The keep-alive message should be sent on both client and server sides.
Hmm, this has been bugging me as well. When I implemented subscription support in React-Native my error monitoring systems screams because of all disconnects. What seems to work is unmounting (when backgrounding) and remounting, it works for now but is not a good solution really.
I think it should be gracefully handled by the library. Or at the very least there should be a FAQd way to do this in one place for all active subscriptions on one ApolloClient - for example how to make ApolloClient disconnect all connections on backgrounding - and reconnect when foregrounding (without changing the component tree like in my example). Cause this really makes Subscriptions in React-Native hell..
Basic example:
const MyComponentWithSubscription = ({ children }) => {
useSubscription(MY_SUBSCRIPTION);
return children;
}
const WrappingComponent = ({ children }) => {
const appState = useAppState();
return appState !== 'background'
? <MyComponentWithSubscription>{children}</MyComponentWithSubscription>
: children;
}
Update: So turns out this works for iOS. For Android I'm still getting the errors.
I'm having this problem on React, disconnected is called every minute, but I need it to be called when WIFI/ network disconnects.
Any news about this ? I am having this problem too and it does not seem to be linked to any particular framework or environment but rather to the way the lib handles some network disconnections.
Will leave this here in case anyone finds it helpful (:
The way we got it to work is:
Environment.js:
/**
* Relay Subscription client - https://relay.dev/docs/en/subscriptions#configure-network
*/
const subscriptionClient = new SubscriptionClient(WSURL, {
/** automatic reconnect in case of connection error */
reconnect: true,
/** onReconnected */
onReconnected: () => {
/** Update the subscription command timestamp redux value to re-trigger the subscriptions effect resulting in global subscriptions resubscribing upon connection */
setSubscriptionCmdTimestamp(Date.now()); // redux variable that is only updated upon reconnection success
},
});
App.js
/**
* Global subscriptions effect
*
* note: Can be retriggered by changing the subscriptionCmdTimestamp value, resulting in unsubscribing and resubscribing
*/
useEffect(() => {
const { dispose } = xxSubscribe();
return dispose;
}, [
subscriptionCmdTimestamp, // redux variable that is only updated upon reconnection success
]);
I'm having this problem on React,
disconnectedis called every minute, but I need it to be called when WIFI/ network disconnects.
Our solution to this was two-fold. First, the Client (SubscriptionClient) needs to have reconnect enabled.
const subClient = new SubscriptionClient(wsUri, {
reconnect: true,
...
});
But, reconnect alone sadly does not hinder automatic disconnects. To make the connection persist, it needs to be confirmed before the automatic disconnect. In subscriptions-transport-ws, this is done on the serverside with the keepAlive valuie (in ms).
const server = new ApolloServer({
schema,
...
subscriptions: {
keepAlive: 10000,
onConnect: ...
}
}
With a raw SubscriptionServer, I believe the following should do the same:
const subServer = new SubscriptionServer({
keepAlive: 10000,
...
});
Any updates on this. My use case also requires listening to internet connection drops at the server?
Most helpful comment
I'm having this problem on React,
disconnectedis called every minute, but I need it to be called when WIFI/ network disconnects.