awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
921 stars 267 forks source link

Subscription network error #501

Open wimvdb opened 4 years ago

wimvdb commented 4 years ago

I use OPENID_CONNECT to authenticate and create a subscription which works fine until after a while the accessToken expires and I get a network error.

How should I configure my subscriptions to automatically refresh the token?

My configuration:

appSyncConfig: {
        auth: {
            type: 'OPENID_CONNECT',
            jwtToken: async () => {
                const result = await getToken();
                if (result) {
                    return result.accessToken;
                }
                return undefined;

            },
        },
}

The subscription:

export const CardSubscriptionGraphQL = gql`
    subscription cardSubscription($ownerId: String!) {
        onCard(ownerId: $ownerId) {
            ...card
        }
    }
    ${card}
`;

The subscription invocation:

export default graphql(CardSubscriptionGraphQL,
        () => {
            return {
                options: (props) => {
                    return {
                        variables: {ownerId: props.ownerId},
                    };
                },
            };
        }
    )(Cards);
wimvdb commented 4 years ago

It might be unrelated to the token. The error I'm getting is "errorMessage": "AMQJS0007E Socket error:undefined." similar as in issue #286

But I don't have ws as a dependency in my package.json:


"dependencies": {
    "@react-native-community/datetimepicker": "^1.0.0",
    "@react-native-community/netinfo": "^4.6.1",
    "AsyncStorage": "^0.1.5",
    "apollo-cache-inmemory": "^1.6.3",
    "apollo-link": "^1.2.13",
    "apollo-link-context": "^1.0.19",
    "apollo-link-error": "^1.1.12",
    "apollo-link-state": "^0.4.2",
    "apollo-utilities": "^1.3.2",
    "array.prototype.flatmap": "^1.2.2",
    "aws-appsync": "^2.0.2",
    "aws-appsync-react": "^2.0.2",
    "buffer": "^5.4.3",
    "email-validator": "^2.0.4",
    "graphql": "^14.5.8",
    "graphql-tag": "^2.10.1",
    "i18n-js": "^3.5.0",
    "jest-extended": "^0.11.2",
    "jest-mock-console": "^1.0.0",
    "json-server": "^0.15.1",
    "libphonenumber-js": "^1.7.29",
    "mitt": "^1.2.0",
    "nanoid": "^2.1.7",
    "react": "^16.12.0",
    "react-apollo": "^2.5.8",
    "react-fast-compare": "^2.0.4",
    "react-native": "0.60.4",
    "react-native-app-auth": "^4.4.0",
    "react-native-app-settings": "^2.0.1",
    "react-native-contacts": "^5.0.4",
    "react-native-device-info": "^2.3.2",
    "react-native-elements": "^1.2.7",
    "react-native-exception-handler": "^2.10.8",
    "react-native-gesture-handler": "^1.5.2",
    "react-native-iap": "^4.3.0",
    "react-native-image-picker": "^1.1.0",
    "react-native-mixpanel": "^1.1.10",
    "react-native-mock-render": "^0.1.7",
    "react-native-permissions": "^2.0.6",
    "react-native-phone-input": "^0.2.4",
    "react-native-picker-select": "^6.3.3",
    "react-native-simple-toast": "^0.1.1",
    "react-native-splash-screen": "^3.2.0",
    "react-native-swipeout": "^2.3.6",
    "react-native-vector-icons": "^6.6.0",
    "react-native-version": "^3.2.0",
    "react-navigation": "^3.13.0",
    "redux": "^4.0.4",
    "redux-persist": "^4.10.2",
    "rn-fetch-blob": "^0.10.16",
    "sinon": "^7.5.0"
  }

npm ls ws

├─┬ jest-environment-enzyme@7.1.2
│ └─┬ jest-environment-jsdom@24.8.0
│   └─┬ jsdom@11.12.0
│     └── ws@5.2.2 
└─┬ react-native@0.60.4
  ├─┬ @react-native-community/cli@2.8.3
  │ ├─┬ metro@0.54.1
  │ │ ├─┬ metro-inspector-proxy@0.54.1
  │ │ │ └── ws@1.1.5 
  │ │ └── ws@1.1.5 
  │ └── ws@1.1.5 
  └─┬ react-devtools-core@3.6.3
    └── ws@3.3.3 
wimvdb commented 4 years ago

I was able to work around the network error by upgrading to aws-appsync 3.0.2 but I'm still have troubles establishing a stable subscription my updated code now looks like this:

export function Cards(props) {

    React.useEffect(() => {
        Cards.unsubscribeNetWorkChanges = subscribeToNetWorkChanges(subscribeToCardUpdatesFunction);
        return () => {
            if (Cards.unsubscribeCardUpdates) {
                Cards.unsubscribeCardUpdates();
            }
            if (Cards.unsubscribeNetWorkChanges) {
                Cards.unsubscribeNetWorkChanges();
            }
        }
    }, []);

    const subscribeToCardUpdatesFunction = () => {
        if (props.accountId) {
            if (Cards.unsubscribeCardUpdates) {
                Cards.unsubscribeCardUpdates()
            }
            Cards.unsubscribeCardUpdates = props.subscribeToMore({
                document: CardSubscriptionGraphQL,
                variables: {
                    ownerId: props.accountId,
                },
                onError: (error) => {
                    console.log(error)
                    trackEvent('Subscription error', error);
                }
            });
        } else {
            trackEvent('accountId not set');
        }
    }

    return (
        <CardsPage cards={props.cards} />
    );
}

export const mapCardsProps = (props) => {
    return {
        cards: props.data?.cards?.items,
        accountId: props.data?.account?.id,
        subscribeToMore: props.data.subscribeToMore,
    };
};

export default graphql(AccountAndCardsGraphQL, selectConfig(mapCardsProps))(Cards);
export const subscribeToNetWorkChanges = (functionToExecute) => {
    return NetInfo.addEventListener((netState) => handleSubscribeOnNetworkChange(netState, functionToExecute));
};

const handleSubscribeOnNetworkChange = (netState, functionToExecute) => {
    if (netState.isConnected && netState.isInternetReachable) {
        console.log('netState.isConnected && internet')
        try {
            functionToExecute()
        } catch (error) {
            trackEvent('Error in handleSubscribeOnNetworkChange', error);
        }
    }
};

What i'm trying to do is if the network drops I want to unsubscribe and re-subscribe once the internet connection becomes stable again. This works on the simulator. If I disable the internet and re-enable it again I can see the subscription working again when the internet is back. I was expecting the onError to trigger at some point by that does not seem to happen.

When I try to do the same thing on a real device by enabling "flight mode" and then disabling "flight mode" again the re-subscribe is not working. The subscription is not receiving updates. It usually fails quietly but sometimes I get this error:

Cannot read property 'subscriptionFailedCallback' of undefined

And another time the app crashed with:

undefined is not an object (evaluating 'n.observer')

Does anyone have an example of a working subscription setup?

wimvdb commented 4 years ago

I can reproduce this error now consistently on a real device by toggling the wifi on/off and unsubscribing/resubscribing.

IMG_5670

bensie commented 4 years ago

We are seeing something very similar (perhaps the same) with v3.0.2 where subscriptions cause a crash on this line. As @wimvdb mentioned, it happens consistently on real devices when resubscribing. I've struggled to get it to happen in the simulator.

Here's our trace, note it's from dSYM mappings so s.error === observer.error.

image
bensie commented 4 years ago

Update after bumping some dependencies and attempting to further isolate the problem, I'm seeing the same error as @wimvdb on the same line.

image

And again, it is very straight-forward to reproduce on a real device. Happens within a few seconds of re-launching the app (background to foreground app starts only - does not happen on fresh app starts).

bensie commented 4 years ago

@elorzafe Is this something you could help with? Looks like this whole file came along with your work in #484 with the addition of the new real-time native websockets stuff in AppSync (which is awesome btw). Thanks so much for your work on this!

jonixj commented 4 years ago

I am getting this a lot too using 3.0.2

wimvdb commented 4 years ago

I was able to resolve the problem by updating my velocity templates to the latest version from:

"version": "2017-02-28", to "version": "2018-05-29".

After changing this the connection reconnects automatically after the device goes offline and comes back online. It does take a few minutes before it's ready to receive new messages and messages that were sent while the connection was not yet established are lost.

roni-frantchi commented 4 years ago

Wow @wimvdb thanks for sharing. I've been struggling with this one.
I actually don't have velocity templates set up for my subscription. Haven't had a need for one.
I haven't seen an example of a subscription resolver that would basically only set the version like you did... can you share yours?..

wimvdb commented 4 years ago

@roni-frantchi sure request.vtl: { "version": "2018-05-29", "payload": {} }

response.vtl: `#if(${context.identity.sub} != ${context.arguments.ownerId}) $utils.unauthorized()

else

null

end`

bensie commented 4 years ago

@wimvdb Sadly I can't say the same. We've been on 2018-05-29 from the beginning for all templates and are still unable to work around this constant crash. There are clearly client-side library issues here where native application scenarios - intermittent connections, suspended/backgrounded app processes, etc - were not considered/tested.

roni-frantchi commented 4 years ago

Thanks @wimvdb .
Using that though, I'm getting:

{"errorType":"MappingTemplate","message":"Value for field '$[operation]' not found."}
plus- commented 4 years ago

Do any of you use a failover strategy for their subscription (to handle socket timeouts for example) which tries to unsub/resub agressivelly?

If so I think it could be the issue and related to https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/515

bogan27 commented 4 years ago

I'm also facing this bug. I upgraded my velocity template versions to "2018-05-29", but that didn't help (note that I don't have any subscription resolvers, just Query and Mutation resolvers).

Tried with 3.0.2, 3.0.3, and 3.0.4 and am still getting the error in the screenshot above ("Undefined is not an object evaluating '_a.observer'). Downgrading to aws-appsync and aws-appsync-react to v2.0.2 fixed the issue and my app no longer routinely crashes.

I'm using Expo SDK v36 which uses React 16.9 and react-native 0.61.

veloware commented 4 years ago

also using 2018-05-29, also having this issue

KvNGCzA commented 3 years ago

These PR1 & PR2 have been opened with related fixes.

tmjordan commented 3 years ago

Same problem for me