Amplify-js: "AMQJS0007E Socket error:undefined" on wifi connection loss while trying to subscribe for topics

Created on 28 Jan 2019  路  7Comments  路  Source: aws-amplify/amplify-js

Describe the bug
I am trying to simulate a sudden connection loss, in between query and subscribe.
Even though adding a try/catch block for subscribe(), still there's an unhandled promise rejection.

To Reproduce
Using an iOS simulator, after all query operations are successful, disconnect simulator from WiFi connection.

Expected behavior
Since there was a network loss after query AND before or during the subscription process, observable's error callback should be able to handle this socket connection error.

Screenshots
screen shot 2019-01-28 at 4 29 35 pm

Desktop (please complete the following information):

  • OS: iOS
  • Browser Safari
  • Version 12.0.2

Smartphone (please complete the following information):

  • Device: iPhone 8 simulator
  • OS: iOS 12

Additional context
I am using React Native. Using iPhone 8 simulator with debug mode enabled.

You can turn on the debug mode to provide more info for us by setting window.LOG_LEVEL = 'DEBUG'; in your app.

screen shot 2019-01-28 at 4 49 35 pm

API React Native bug

Most helpful comment

Any update on this? I always get this error when trying to subscribe to a topic after internet is restored.

     "errorCode":7,
     "errorMessage":"AMQJS0007E Socket error:undefined."

All 7 comments

I get the same error in the same context.
Except that I use API.graphql(...).subscribe() and I also tried with mobile data (same issue)...
I've only tested on an Android real device.

I run a query to retrieve data, then I subscribe to these data.
If the first query fails (because of a Network Error), I don't try to subscribe.
When the connection is lost, I implemented a loop which try to fetch data and resubscribe each 5 seconds...

Sometimes the error is well catched by the observable error callback :

{"provider":{"_config":{"Auth":{"identityPoolId":"********","region":"********","userPoolId":"********","userPoolWebClientId":"********","mandatorySignIn":true},"aws_appsync_graphqlEndpoint":"********","aws_appsync_region":"********,"aws_appsync_authenticationType":"AMAZON_COGNITO_USER_POOLS","endpoints":[],"clientId":"********"},"_clientsQueue":{"promises":{}},"_topicObservers":{},"_topicClient":{},"_topicAlias":{}},"error":{"errorMessage":"AMQJS0007E Socket error:undefined.","uri":"********"},"errors":[{"message":"Network Error"}]}

And sometimes I get the yellow warn, same as @joseph-d-p

@joseph-d-p can you try adding error on the observer object of the subscriber like this.

subscription = API.graphql(
      graphqlOperation(subscriptions.onCreateTodo)
    ).subscribe({
      next: (todoData) => this.setState({data: todoData}),
      error: ({error}) => this.setState({data: error}),
    });

@elorzafe, here's a snippet I'm using with some redux-saga coating.
The core is the same for what you've indicated.

function onUpdateReceived(subscription) {
  return eventChannel((emit) => {
    const sub = subscription.subscribe({
      next: (data) => emit(hasReceivedUpdate(data)),
      error: (error) => emit(hasError(error)),
    });

    return () => {
      sub.unsubscribe();
    };
  });
}

function* subscribeForUpdates() {
  try {
    const subscribeQuery = graphqlOperation(onCreateTodo);
    const subscription = yield call([API, graphql], subscribeQuery);

    let channel = yield call(onUpdateReceived, subscription);
    ...
  } finally {
    ...
  }
}

Tried the ({ error }) but still saw the issue. I think the unhandled Promise is not about a deconstructed error parameter.

Hello everyone, did you guys find a solution or a workaround for this issue? I am getting the same problem using, Amplify, React-Native on an Android device.

Same as everyone mentioned queries and mutations seems to work fine, only in subscription this error occurs. If its of value to anyone, I need to wait for few mins after the mutation before my subscription fails and this error comes up.

Any help will be greatly appreciated, I am stuck with this issues since last 4 days.

@pranavgawri, so what I did as a workaround was let the React Native component listen for network connectivity change. If the network changed from offline to wifi, I subscribe again. Previous subscription channels with disconnected socket under the hood were closed.

Any update on this? I always get this error when trying to subscribe to a topic after internet is restored.

     "errorCode":7,
     "errorMessage":"AMQJS0007E Socket error:undefined."

I fixed this by creating this class and using it in place of AWSIoTProvider. Read below to see how this fixes the issue.

import { AWSIoTProvider } from '@aws-amplify/pubsub/lib/Providers'
import Paho from 'paho-mqtt';
import { MqttProvidertOptions } from '@aws-amplify/pubsub/lib/Providers/MqttOverWSProvider'

export class FixedAWSIoTProvider extends AWSIoTProvider {
   public async newClient({
        url,
        clientId,
    }: MqttProvidertOptions): Promise<any> {
        console.log('FixedAWSIoTProvider - Creating new MQTT client', clientId);

        // @ts-ignore
        const client = new Paho.Client(url, clientId);
        // client.trace = (args) => logger.debug(clientId, JSON.stringify(args, null, 2));
        client.onMessageArrived = ({
            destinationName: topic,
            payloadString: msg,
        }) => {
         // Don't care!!!
         //@ts-ignore
            this._onMessage(topic, msg);
        };
        client.onConnectionLost = ({ errorCode, ...args }) => {
            this.onDisconnect({ clientId, errorCode, ...args });
        };

        await new Promise((resolve, reject) => {
            client.connect({
                useSSL: this.isSSLEnabled,
                mqttVersion: 3,
                onSuccess: () => resolve(client),
                onFailure: () => resolve(client),
            });
        });

        return client;
    }
}

I "fixed" this by changing the following code in MqttOverWSProvider from

await new Promise((resolve, reject) => {
            client.connect({
                useSSL: this.isSSLEnabled,
                mqttVersion: 3,
                onSuccess: () => resolve(client),
                onFailure: reject,
            });
        });

to

await new Promise((resolve, reject) => {
            client.connect({
                useSSL: this.isSSLEnabled,
                mqttVersion: 3,
                onSuccess: () => resolve(client),
                onFailure: () => resolve(client), // changed
            });
        });

This causes the following try block to fail during the subscribe step, not the connect step.

(async () => {
                const { url = await this.endpoint } = options;

                try {
                    client = await this.connect(clientId, { url }); // I don't fail here anymore!
                    targetTopics.forEach(topic => { // I fail here now. 
                        client.subscribe(topic);
                    });
                } catch (e) {
                    observer.error(e);
                }
            })();

The big difference is that client becomes defined before the fail occurs, which allows all the unsubscribe code directly below to run.

return () => {
                logger.debug('Unsubscribing from topic(s)', targetTopics.join(','));

                if (client) { // HAHA, I don't run unless client is defined!!!
                    this._clientIdObservers.get(clientId).delete(observer);
                    // No more observers per client => client not needed anymore
                    if (this._clientIdObservers.get(clientId).size === 0) {
                        this.disconnect(clientId);
                        this._clientIdObservers.delete(clientId);
                    }

                    targetTopics.forEach(topic => {
                        const observersForTopic =
                            this._topicObservers.get(topic) ||
                            (new Set() as Set<SubscriptionObserver<any>>);

                        observersForTopic.delete(observer);

                        // if no observers exists for the topic, topic should be removed
                        if (observersForTopic.size === 0) {
                            this._topicObservers.delete(topic);
                            if (client.isConnected()) {
                                client.unsubscribe(topic);
                            }
                        }
                    });
                }

When this code is NOT allowed to run, MqttOverWSProvider will re-use the client that just failed to connect, and somehow this leads to more fails to connect. When the code is allowed to run, MqttOverWsProvider will call newClient() again on the next reconnect attempt, which leads to successful reconnection.

The question now is: how do I get this behavior to occur without having to touch the source file?

Was this page helpful?
0 / 5 - 0 ratings