aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

No callback/promise returned when subscription is removed and when socket connection is disconnected. #9818

Closed shashika-v closed 2 years ago

shashika-v commented 2 years ago

Before opening, please confirm:

JavaScript Framework

React Native

Amplify APIs

GraphQL API

Amplify Categories

api

Environment information

``` # Put output below this line System: OS: macOS 11.6 CPU: (4) x64 Intel(R) Core(TM) i7-7660U CPU @ 2.50GHz Memory: 3.34 GB / 16.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 14.4.0 - /usr/local/bin/node Yarn: 1.22.4 - /usr/local/bin/yarn npm: 6.14.4 - /usr/local/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman Browsers: Chrome: 100.0.4896.88 Safari: 15.0 npmPackages: @apollo/client: ^3.3.6 => 3.3.6 @apollo/client/cache: undefined () @apollo/client/core: undefined () @apollo/client/errors: undefined () @apollo/client/link/batch: undefined () @apollo/client/link/batch-http: undefined () @apollo/client/link/context: undefined () @apollo/client/link/core: undefined () @apollo/client/link/error: undefined () @apollo/client/link/http: undefined () @apollo/client/link/persisted-queries: undefined () @apollo/client/link/retry: undefined () @apollo/client/link/schema: undefined () @apollo/client/link/utils: undefined () @apollo/client/link/ws: undefined () @apollo/client/react: undefined () @apollo/client/react/components: undefined () @apollo/client/react/context: undefined () @apollo/client/react/data: undefined () @apollo/client/react/hoc: undefined () @apollo/client/react/hooks: undefined () @apollo/client/react/parser: undefined () @apollo/client/react/ssr: undefined () @apollo/client/testing: undefined () @apollo/client/utilities: undefined () @babel/core: ^7.12.9 => 7.12.9 @babel/runtime: ^7.12.5 => 7.12.5 @react-native-async-storage/async-storage: ^1.13.2 => 1.13.2 @react-native-community/art: ^1.2.0 => 1.2.0 @react-native-community/eslint-config: ^2.0.0 => 2.0.0 @react-native-community/hooks: ^2.6.0 => 2.6.0 @react-native-community/masked-view: ^0.1.10 => 0.1.11 @react-native-community/netinfo: ^5.9.10 => 5.9.10 @react-native-community/progress-bar-android: ^1.0.4 => 1.0.4 @react-native-community/progress-view: ^1.2.4 => 1.3.1 @react-navigation/bottom-tabs: ^5.11.10 => 5.11.10 @react-navigation/native: ^5.8.10 => 5.8.10 (3.8.4) @react-navigation/stack: ^5.12.8 => 5.12.8 ContextAPIMixpanel: 0.0.1 HelloWorld: 0.0.1 MixpanelDemo: 0.0.1 PDFExample: 0.0.1 ReactNativeOrientationDemo: 2.0.0 SimpleMixpanel: 0.0.1 accordion-collapse-react-native: ^1.0.1 => 1.0.1 apollo3-cache-persist: ^0.12.1 => 0.12.1 appcenter: 4.3.0 => 4.3.0 appcenter-analytics: 4.3.0 => 4.3.0 appcenter-crashes: 4.3.0 => 4.3.0 aws-amplify: ^4.3.19 => 4.3.19 aws-appsync: ^4.1.4 => 4.1.4 aws-appsync-subscription-link: ^3.0.9 => 3.0.9 (2.2.6) axios: ^0.21.0 => 0.21.0 (0.21.4) babel-jest: ^26.6.3 => 26.6.3 eslint: ^7.14.0 => 7.14.0 graphql: ^15.4.0 => 15.4.0 (15.8.0) hermes-inspector-msggen: 1.0.0 husky: ^7.0.4 => 7.0.4 jest: ^26.6.3 => 26.6.3 jwt-decode: ^3.1.2 => 3.1.2 lint-staged: ^12.3.3 => 12.3.3 metro-react-native-babel-preset: ^0.64.0 => 0.64.0 (0.59.0, 0.58.0) mixpanel-react-native: ^1.3.1 => 1.3.1 moment: ^2.29.1 => 2.29.1 prettier: 2.5.1 => 2.5.1 qs: ^6.9.4 => 6.9.4 (6.5.2) react: 16.13.1 => 16.13.1 react-animated: 0.1.0 react-native: 0.63.3 => 0.63.3 react-native-awesome-alerts: ^1.4.2 => 1.4.2 react-native-base64: ^0.2.1 => 0.2.1 react-native-calendar-picker: ^7.1.2 => 7.1.2 react-native-cookie: ^0.3.0 => 0.3.0 react-native-device-info: ^8.4.9 => 8.4.9 react-native-easy-grid: ^0.2.2 => 0.2.2 react-native-gesture-handler: ^1.9.0 => 1.9.0 react-native-inappbrowser-reborn: 3.6.3 => 3.6.3 react-native-keep-awake: ^4.0.0 => 4.0.0 react-native-keyboard-aware-scroll-view: ^0.9.4 => 0.9.4 react-native-linear-gradient: ^2.5.6 => 2.5.6 react-native-markdown-display: ^7.0.0-alpha.2 => 7.0.0-alpha.2 react-native-media-controls: ^2.2.0 => 2.2.0 react-native-onesignal: ^4.3.1 => 4.3.1 react-native-orientation: ^3.1.3 => 3.1.3 react-native-pdf: ^6.3.0 => 6.3.0 react-native-percentage-circle: ^1.0.7 => 1.0.7 react-native-popover-view: ^5.0.0 => 5.0.1 react-native-progress: github:abhaydee/react-native-progress#feature/imageCirclev3 => 5.0.0 react-native-progress-circle: ^2.1.0 => 2.1.0 react-native-rename: ^2.6.0 => 2.6.0 react-native-responsive-screen: ^1.4.1 => 1.4.1 react-native-safe-area-context: ^3.1.9 => 3.1.9 react-native-screens: ^2.15.0 => 2.16.1 react-native-segmented-control-tab: ^3.4.1 => 3.4.1 react-native-simple-radio-button: ^2.7.4 => 2.7.4 react-native-slider: ^0.11.0 => 0.11.0 react-native-snap-carousel: ^3.9.1 => 3.9.1 react-native-splash-screen: ^3.2.0 => 3.2.0 react-native-svg: ^12.1.1 => 12.1.1 react-native-svg-animated-linear-gradient: ^0.4.0 => 0.4.0 react-native-swiper: ^1.6.0-rc.3 => 1.6.0-rc.3 react-native-track-player: github:abhaydee/react-native-track-player#android-12-fix => 2.1.2 react-native-uuid: ^2.0.1 => 2.0.1 react-native-vector-icons: ^7.1.0 => 7.1.0 react-native-video: ^5.1.1 => 5.1.1 react-native-video-player: ^0.10.1 => 0.10.1 react-native-webview: ^11.2.3 => 11.2.3 react-navigation: ^4.4.4 => 4.4.4 react-test-renderer: 16.13.1 => 16.13.1 responsive-screen: 0.1.0 responsive-screen-orientation-change: 0.1.0 responsive-screen-styled-components: 0.1.0 rn-fetch-blob: ^0.12.0 => 0.12.0 tsutils: 3.20.0 => 3.20.0 url-search-params-polyfill: ^8.1.0 => 8.1.0 uuid: ^8.3.2 => 8.3.2 (3.4.0, 3.3.2) npmGlobalPackages: appcenter-cli: 2.10.0 graphql-schema-utilities: 1.1.3 graphql: 15.6.1 ios-deploy: 1.10.0 npm: 6.14.4 react-native-cli: 2.0.1 serverless: 2.62.0 ```

Describe the bug

This is an issue with the subscription socket connection with the Amplify SDK. The issue happens on iOS build when the app goes to the inactive state and resume back it throws out an error message "AWSAppSyncRealTimeProvider - Disconnect error: Timeout disconnect". This happens in android when there is very long duration of inactivity.

When we tried multiple implementation setup including unsubscribing from all the subscriptions on the error to disconnecting the socket and tried resubscribing, which still throws out the error.

Currently, after unsubscribing, giving a timeout of 10 seconds to reestablish the connection works but again that is just a work-around which can fail on network issues etc. We need to exactly know when the connection is disconnected or at least when the unsubscribing is successful. There is no callback/promise for either unsubscribing or when connection ends.

Expected behavior

Callback present or promise returned on unsubscribing or when socket connection ends.

Reproduction steps

  1. Install the package aws-amplify

  2. Configure it to connect to GraphQL endpoints.

  3. We are following the pattern for subscribing/unsubscribing as per official Amplify documentation( API and graphqlOperation are imported from "aws-amplify" package

After unsubscribing, there is no promise/callback. Also, we are unsubscribing from all subscriptions to end the connection. But again, there is no way to get notified/callback when the connection ends currently.

Code Snippet

// Put your code below this line.

For configuring amplify, 
const appSyncConfig = {
  aws_appsync_graphqlEndpoint: ***********,
  aws_appsync_region: ************,
  aws_appsync_authenticationType: ************,
};
Amplify.configure(appSyncConfig);

For subscribing, 
 const subscription = API.graphql(
      graphqlOperation(<name>, { <params> })
    ).subscribe({
      next: ({ provider, value }) => {
       // calling some internal methods
        }
      },
      error: (error) => console.error(error),
    });

For unsubscribing,
subscription.unsubscribe(); 

Log output

``` // Put your logs below this line [DEBUG] 36:09.646 AWSAppSyncRealTimeProvider - Disconnect error: Timeout disconnect ERROR {"error": {"errors": [[Object]]}, "provider": {"_config": {"aws_appsync_authenticationType": "OPENID_CONNECT", "aws_appsync_graphqlEndpoint": "*****", "aws_appsync_region": "us-east-1"}, "awsRealTimeSocket": {"CLOSED": 3, "CLOSING": 2, "CONNECTING": 0, "OPEN": 1, "_eventEmitter": [NativeEventEmitter], "_socketId": 2, "_subscriptions": [Array], "protocol": "graphql-ws", "readyState": 1}, "keepAliveTimeout": 300000, "keepAliveTimeoutId": 2707, "promiseArray": [], "socketStatus": 1, "subscriptionObserverMap": Map {"*****" => [Object], "*****" => [Object], "*****" => [Object]}}} LOG [DEBUG] 36:11.293 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.299 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.303 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.308 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.342 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.343 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.352 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.354 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.357 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.358 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.372 AWSAppSyncRealTimeProvider - subscription message from AWS AppSync RealTime: {"type":"ka"} LOG [DEBUG] 36:11.374 AWSAppSyncRealTimeProvider {"id": "", "observer": null, "query": "", "variables": {}} LOG [DEBUG] 36:11.397 AsyncStorageCache - Get item: key is federatedInfo with options undefined LOG [DEBUG] 36:11.677 AsyncStorageCache - Set item: key is federatedInfo, value is [object Object] with options: undefined LOG [DEBUG] 36:11.872 AsyncStorageCache - Get item: key is federatedInfo with options undefined LOG [DEBUG] 36:11.879 RestClient - POST *****graphql_endpoint***** LOG [DEBUG] 36:12.274 AWSAppSyncRealTimeProvider - closing WebSocket... ```

aws-exports.js

No response

Manual configuration

const appSyncConfig = { aws_appsync_graphqlEndpoint: ***, aws_appsync_region: ****, aws_appsync_authenticationType: ****, }; Amplify.configure(appSyncConfig);

Additional configuration

No response

Mobile Device

All iOS for less time of inactivity and android for longer time of inactivity

Mobile Operating System

iOS 15, Android 10

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

vyomr13 commented 2 years ago

@shashika-v .Thank you for opening this issue. Could you give us a sample code of how you are using this in your App?

vyomr13 commented 2 years ago
Screen Shot 2022-04-19 at 1 31 29 PM

Hi @shashika-v You should be able to get the timeout message and use check for the error message. You can use the error callback from the subscription (observer) to respond to the timeout appropriately.

shashika-v commented 2 years ago

@vyomr13 - As quoted earlier, we are getting the callback for error and based on that, we are removing all the subscriptions and after 10 seconds of that, trying to re-establish the connection. But our issue is we aren't getting any callback/promise after unsubscribing is successful or connection is lost. You can read through the entire description and the expected behaviour too.

chrisbonifacio commented 2 years ago

@shashika-v This sounds more like a feature request than a bug, so I'm going to label it as such. Can you provide an example of what that functionality might look like for you ideally?

EDIT: I've added this issue to a feature request we're going to use for tracking issues related to subscriptions and WebSockets to consolidate them and see where we can focus our efforts to improve developer experience.

Jordan-Eckowitz commented 2 years ago

I came across this in the docs for error handling (relevant code snippet below): https://docs.amplify.aws/lib/graphqlapi/offline/q/platform/js/#error-handling

I'm not using AWSAppSyncClient to initialize the client, Amplify is handling that, but it looks like being able to access offlineConfig/callback would resolve this issue.

Is there a way to do that currently via Amplify or would I need to rewire things so that I initialize the client directly?

const client = new AWSAppSyncClient({
  url: awsconfig.aws_appsync_graphqlEndpoint,
  region: awsconfig.aws_appsync_region,
  auth: {
    type: AUTH_TYPE.API_KEY,
    apiKey: awsconfig.aws_appsync_apiKey,
  },
  offlineConfig: {
    callback: (err, succ) => {
      if(err) {
        const { mutation, variables } = err;

        console.warn(`ERROR for ${mutation}`, err);
      } else {
        const { mutation, variables } = succ;

        console.info(`SUCCESS for ${mutation}`, succ);
      }
    },
  },
});
chrisbonifacio commented 2 years ago

@Jordan-Eckowitz This is not exposed through Amplify's API library so you would have to use the AppSync client manually. We are currently working on improving handling for socket connection status in Amplify. You can track the work the team is doing on this PR: https://github.com/aws-amplify/amplify-js/pull/10063

chrisbonifacio commented 2 years ago

@shashika-v @Jordan-Eckowitz We recently implemented some improvements to our subscriptions. Please upgrade to the latest version of aws-amplify and let us know if you are still experiencing this issue.

Jordan-Eckowitz commented 2 years ago

@shashika-v @Jordan-Eckowitz We recently implemented some improvements to our subscriptions. Please upgrade to the latest version of aws-amplify and let us know if you are still experiencing this issue.

Thanks @chrisbonifacio, I upgraded a few weeks back and all seems to be working now!