Closed objectiveSee closed 1 month ago
Hi @objectiveSee 👋🏻 there are several different protocols that can handle GraphQL subscriptions, some of which are handled by modules we officially support. AWS AppSync packages are provided by a third party, however. The core Apollo Client API itself is not opinionated about how to handle Websockets connections, so I recommend looking at the aws-appsync-subscription-link
docs for guidance on how to handle disconnect events in your applications.
@bignimbus do you have any perspective on this from Apollo's standpoint? I am curious how Apollo client handles re-connecting the underlying socket(s) that support the subscription. Does any link you have in mind handle this or is there additional code needed to support reconnecting? I am looking for alternatives in case AppSync doesn't work. It appear that the topic of re-connecting subscriptions is not discussed a lot.
We'd like to make the experience around reconnecting more clear for users while accounting for the fact that different protocols/libraries will expose different and sometimes not completely analogous interfaces. Expect to see some improvements to the developer experience along these lines in future releases, but we have no concrete plans yet.
The action could be simply be a "hard refresh" and restart the subscription, independent of the underlying protocol or library. Just like useSubscription
is implementation independent. Something like this:
const { reconnect } = useSubscription(
onUserUpdatedSubscription,
{
variables: { id: userId },
onError: (error: ApolloError) => {
if (error === 'something specific') {
reconnect()
}
}
}
);
Maybe it should be called restart
or reconnect
or resubscribe
. The result should be a new subscription, as if useSubscription
was called for the first time.
I think what I would like to achieve is what the shouldResubscribe
option does, but manually.
To quote the docs:
Determines if your subscription should be unsubscribed and subscribed again when an input to the hook (such as subscription or variables) changes.
So onError
or onComplete
I could resubscribe again. The skip might do for now, but a more standardised way would be preferred of course.
Thank you for the response @jamiter. I agree that adding a reconnect property to the hook would be really helpful. What would it take to get this added into the Apollo library? In the meantime, I wrote a custom hook that handles the reconnect logic through the skip hack that I mentioned. I added the ability to automatically reconnect as well as exposing a reconnect function. Here is the code for anyone who is interested.
Separately, I am dealing with an issue where the websocket implementation that I am using does not support the onComplete callback which is causing its own issues 🤯
import { ApolloError, DocumentNode, OperationVariables, SubscriptionHookOptions, TypedDocumentNode, useSubscription } from '@apollo/client';
import { useCallback, useState } from 'react';
import { delay } from '../utilities/delay';
// ms between reconnect attempts
const RECONNECT_TIME = 10000;
const DELAY_2 = 1000; // ms skip is set to true before reconnecting
const DELAY_1 = RECONNECT_TIME - DELAY_2; // ms before skip is set to false pre-skip
export function useReconnectingSubscription<TData = any, TVariables extends OperationVariables = OperationVariables>(
subscription: DocumentNode | TypedDocumentNode<TData, TVariables>,
options?: SubscriptionHookOptions<TData, TVariables>
) {
const [internalSkip, setInternalSkip] = useState(false);
const reconnect = useCallback(async () => {
// Toggle the value of `skip` to force the subscription to reconnect.
// The value must be true then false for the reconnect to work.
await delay(DELAY_1); // allow 1sec for the error to show up. Once skip:true is set, the error will be cleared.
setInternalSkip(true);
console.log('🔌⭐️ Delaying reconnect');
await delay(DELAY_2);
console.log('🔌⭐️ Reconnecting');
setInternalSkip(false);
}, []);
// Internally handle errors by reconnecting and calling the user-provided onError callback
const onError = useCallback(async (error: ApolloError) => {
console.log(`[useReconnectingSubscription] Subscription Failed: ${JSON.stringify(error)}`);
if ( options.onError ) options.onError(error);
return reconnect();
}, [options, reconnect]);
// NOTE: AWS AppSync subscription implementation doesn't call onComplete
// See: https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/759
const onComplete = useCallback(() => {
// TODO: Implement exponential backoff of retries once we can determine when a
// successful subscription has been established. (eg. to reset the exponential backoff)
console.log('😵😵😵😵😵😵😵😵😵 This never happends');
if ( options.onComplete ) {
options.onComplete();
}
}, [options]);
// Merge customized options with the user-provided options
const mergedOptions = {
...options,
skip: options?.skip || internalSkip,
onError,
onComplete
};
const { data, loading, error } = useSubscription<TData, TVariables>(subscription, mergedOptions);
return {
data,
loading,
error,
reconnect
};
}
Sadly I just recreate instance of apollo client for reconnect.
This will be added in 3.11 with #11927
Version 3.11 has been released with this feature.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. For general questions, we recommend using StackOverflow or our discord server.
I am testing my app when the internet is bad. My subscription fails and I get an error, but there is no function exported by the subscription hook that let's me retry the connection. Similar to the refetch property in
useQuery
. Currently, I am unable to have my application reconnect the subscription once it has failed. This seems to be a pretty major oversight in how subscriptions work, so hoping that there's a workaround.Is there some way to trigger a re-subscription attempt?
I am using: