meteorrn / meteor-react-native

Meteor client for React Native matching Meteor Spec
https://guide.meteor.com/react-native.html
Other
59 stars 31 forks source link

Method call is not working after resuming from the background #49

Closed midhunevolvier closed 1 year ago

midhunevolvier commented 3 years ago

Describe the bug

Unable to do a method call after resuming from the background after 10-15 mins. Meteor connection status(meteor.status()=true) is true. But no responsce from server. Can anyone help me?

To Reproduce

Steps to reproduce the behavior:

  1. Open the app
  2. Minimize the app
  3. Open the app after 10-15 mins

Expected behavior

Should be able to call the methods and get the results from the server even after resuming the app from the background.

TheRealNate commented 3 years ago

Hi @midhunchandran97,

Could you check the following:

midhunevolvier commented 3 years ago

Hi @TheRealNate,

Thanks for the quick response, We have checked all three items, all are working fine. We are calling a method call just after resuming the app. There was no response from the server. After sometime DDP connected and working fine except for the first method call. No response to the first method call.

What could be the problem?

TheRealNate commented 3 years ago

I'm wondering if this could be the issue: https://github.com/TheRealNate/meteor-react-native/blob/85924c2e32793198c4d5d1601ff726f187460ec3/lib/queue.js#L21-L25

Basically, the way the methods queue was designed, if the socket is connected the method is called and the next one is processed. I'm wondering if when the app gets resumed, if the socket was disconnected when Meteor.call was called, it does not retry it.

To test this theory, can you try another method call a bit after the first one? That should cause the queue to restart, and you original method should return as well.

Thanks!

TheRealNate commented 3 years ago

Hey @midhunchandran97, any updates on this?

midhunevolvier commented 3 years ago

@TheRealNate ,

Sorry for the late reply, I have tried all steps as you told me. But there is no response from the first method call after resuming the app from a minimized state. After a successful DDP connection, I called a method and I got a response. But no response for the first method call even after the successful DDP connection. Whats next ? :(

midhunevolvier commented 3 years ago

@TheRealNate , Any updates?

midhunevolvier commented 3 years ago

@FaridSafi @spencercarli @sandeepjain @TheRealNate Can anyone help me?

TheRealNate commented 3 years ago

Hey @midhunchandran97. Sorry about the delayed reply, notifications seem to be running a little weird lately.

Could you try wrapping your method call in waitDdpConnected? E.g.:

Meteor.waitDdpConnected(() => {
    Meteor.call(...);
});
midhunevolvier commented 3 years ago

Hi @TheRealNate, this didn't work for me.

the meteor connection status is 'connected' while resuming the app. So I think Meteor.waitDdpConnected() has no use here.

Can we have any time out for a method call? Waiting for your reply. Thanks

TheRealNate commented 3 years ago

Hey @midhunchandran97,

For a method call timeout I could add a add-on package (we try not to deviate from the meteor web spec on the core package), but I'd like to see if I can fix your issue.

So from what I understand, after reconnecting you've tried this:

- make a method call (Call 1)
- make another method call (Call 2)
- Call 2 gets a response
- Call 1 never gets a response

Is that right?

TheRealNate commented 3 years ago

Sorry for the double replies. I've noticed that the current ddp implementation doesn't handle websocket errors. Could you try doing this:

Meteor.getData().ddp.socket.rawSocket.onerror = e => console.error(e);

And see if it is logging any errors

midhunevolvier commented 3 years ago

Hey @midhunchandran97,

For a method call timeout I could add a add-on package (we try not to deviate from the meteor web spec on the core package), but I'd like to see if I can fix your issue.

So from what I understand, after reconnecting you've tried this:

- make a method call (Call 1)
- make another method call (Call 2)
- Call 2 gets a response
- Call 1 never gets a response

Is that right?

Yes, but there is a change

  • make a method call (Call 1)-just after resuming the app from minimized state.
  • make another method call (Call 2)- after 4-5 sec resuming the app from minimized state. (meteor will reconnect automatically)
  • Call 2 gets a response
  • Call 1 never gets a response
TheRealNate commented 3 years ago

Hey @midhunchandran97,

I see you marked this issue as closed. Were you able to fix this issue?

Thanks

midhunevolvier commented 3 years ago

Hi @TheRealNate,

Sorry, I accidentally closed the issue

TheRealNate commented 3 years ago

@midhunchandran97 I see, no problem.

Were you able to try this:

Meteor.getData().ddp.socket.rawSocket.onerror = e => console.error(e);
midhunevolvier commented 3 years ago

@midhunchandran97 I see, no problem.

Were you able to try this:

Meteor.getData().ddp.socket.rawSocket.onerror = e => console.error(e);

I have tried this, but nothing is consoled.

TheRealNate commented 3 years ago

Hey @midhunchandran97,

Sorry for the delayed reply. I think there may be a flaw in the way the package is handling reconnecting. I'm going to do some more digging into it.

Meanwhile, could you try looking at:

Meteor.getData().ddp.status

At the exact moment of your first method call. In fact if you can get a log of the whole Meteor.getData().ddp object and post it that would be helpful.

Thanks

midhunevolvier commented 3 years ago

Hi @TheRealNate

Sorry for the delayed reply. I have checked the code

  1. Result of 'Meteor.getData().ddp.status' is "connected"
  2. Log of 'Meteor.getData().ddp' is attached below.
Screenshot 2021-03-03 at 11 18 17 AM
TheRealNate commented 3 years ago

Hey @midhunchandran97, is this right before or right after running Meteor.call.

It looks like the socket is definitely open. Could you check on the server if the first method call reached the server or not? It's possible that the first method call is reaching the server but the response isn't making it back. This could help us narrow down the issue.

Also, are you using localhost? It should work, but are you experiencing the same issue with production (or any server not on your lan)?

midhunevolvier commented 3 years ago

Hi @TheRealNate

  1. The result is right after running the first Meteor.call.
  2. The first method call never reached the server. I have checked it with a console. nothing shows up.
  3. We are facing the same issue in localhost and in the production server
midhunevolvier commented 3 years ago

@TheRealNate Is there any way to solve this issue?

TheRealNate commented 3 years ago

Hi @midhunchandran97, sorry about the delay in replying.

Is the method call being triggered by an automatic process as soon as the app returns from background, or is it triggered by a button press a little bit after (is there enough time for the connection to be reestablished).

Could you also try seeing if the socket is opening and closing:

Meteor.getData().ddp.socket.addListener("open", () => console.log("open"));
Meteor.getData().ddp.socket.addListener("close", () => console.log("close"));
midhunevolvier commented 3 years ago

Hi @TheRealNate ,

TheRealNate commented 3 years ago

Hi @midhunchandran97,

Thanks for the info. I think we're getting closer to a fix. Could you try running your Meteor call after "open" is called?

midhunevolvier commented 3 years ago

Hi @TheRealNate

I tried running the Meteor call after "open" is called, Meteor call worked successfully.

TheRealNate commented 3 years ago

Great!

For a fix, looks like all that needs to be done is:

I'll start on a fix for this. I'll keep this issue open till it is released.

TheRealNate commented 3 years ago

Updates:

So it looks like when the socket is closed, all pending messages are dropped. This could be the root cause of your issue (the method was coming in before the close event was fired), however before I change this I want to check the Meteor spec and see how Meteor handles ddp connection drops. I believe if you start a meteor call while the connection is offline, it will execute once connection is restored, but I wan't to confirm the behavior.

In the meantime, here's a somewhat simple workaround (please excuse any typos as I whipped this up quickly):

let isOpen = true, onOpen = [];
Meteor.getData().ddp.socket.addListener("close", () => {
  isOpen = false;
});
Meteor.getData().ddp.socket.addListener("open", () => {
  isOpen = true;
  onOpen.forEach(f => f());
});
const whenConnected = func => {
  if(isOpen) {
    func();
  }
  else {
    onOpen.push(func);
  }
}

Basically, monitoring the websocket status, and queueing any calls if the socket is closed. Usage:

whenConnected(() => {
    Meteor.call("abc", ...);
});

Let me know if there are any issues with the workaround.

midhunevolvier commented 3 years ago

@TheRealNate Sorry for the delayed reply,

This solution is not worked for me.

TheRealNate commented 3 years ago

Hey @midhunevolvier, so you're saying the close event is firing a short time after the app resumes, but not immediately; which is causing your call which executes immediately to run before the close event is fired. Is that right?

midhunevolvier commented 3 years ago

Hey @midhunevolvier, so you're saying the close event is firing a short time after the app resumes, but not immediately; which is causing your call which executes immediately to run before the close event is fired. Is that right?

Yes

midhunevolvier commented 3 years ago

@TheRealNate Is there any solution? We are stuck.

TheRealNate commented 3 years ago

Hi @midhunevolvier,

I'm looking into a built in solution for this package, but for now a quick fix for you may be to use the AppState API to detect a resume from background and wait before sending the call.

midhunevolvier commented 3 years ago

@TheRealNate Ok. But how can I understand whether the Meteor is connected or not? Then only I can wait before sending the call. When I resume from background meteor connection status is 'CONNECTED'.

TheRealNate commented 3 years ago

Hey @midhunevolvier, sorry again for the delayed reply. I don't know why GitHub doesn't seem to be sending me notifications for this issue.

Could you try directly reading the socket object's status (Meteor.getData().ddp.socket) and let me know if the socket displays as closed upon resume (even if Meteor's connection status hasn't been updated)? If not, I'll need to see why react native has a delay in flagging the socket as closed.

midhunevolvier commented 3 years ago

@TheRealNate I have checked the status after resuming the application and attaching the screenshots for your reference. I can't find any difference in the result of application on 'NORMAL' and 'RESUMING' stages

  1. Result of 'Meteor.getData().ddp' (NORMAL)

    Screenshot 2021-04-17 at 11 41 45 AM
  2. Result of 'Meteor.getData().ddp' (RESUMING)

    Screenshot 2021-04-17 at 11 29 44 AM
  3. Result of 'Meteor.getData().ddp.socket (NORMAL)

    Screenshot 2021-04-17 at 11 41 54 AM
  4. Result of 'Meteor.getData().ddp.socket (RESUMING)

    Screenshot 2021-04-17 at 11 29 10 AM

    '

TheRealNate commented 3 years ago

Hi @midhunevolvier,

Could you try Meteor.getData().ddp.socket.rawSocket as this should be the socket implementation at the React Native level? If you're not seeing any change here then we may have to look at other options for a fix.

Thanks!

midhunevolvier commented 3 years ago

@TheRealNate I have checked 'Meteor.getData().ddp.socket.rawSocket', I can't find any differences.I think the same issue will be there even if we keep the application idle for some time without minimizing it.

  1. Result of 'Meteor.getData().ddp.socket.rawSocket' (NORMAL)
Screenshot 2021-04-19 at 11 49 47 AM
  1. Result of 'Meteor.getData().ddp.socket.rawSocket' (RESUMING)
Screenshot 2021-04-19 at 11 50 13 AM
midhunevolvier commented 3 years ago

@TheRealNate Is there any solution? We are stuck.

copleykj commented 3 years ago

@midhunevolvier do you possibly have a minimal reproduction that shows this issue that I could clone and explore the issue?

midhunevolvier commented 3 years ago

@copleykj Sorry for the delayed reply. I am busy with my project release. I will upload a sample project as soon as possible.

copleykj commented 3 years ago

@midhunevolvier no apology necessary. Delays are in the nature of our work. When ever you get around to it just let me know and I'll see about digging into this. If I'm not responsive here, feel free to get in touch on the Meteor Community Slack.

jankapunkt commented 2 years ago

I will tackle this, once the PR #83 is merged, which is close to completion (currently User, Accounts and Meteor files remain to be tested).

mmelk commented 1 year ago

Any update?

jankapunkt commented 1 year ago

@mmelk do you have a minimal code example that I can use to work on / use as test for a potential solution?

mmelk commented 1 year ago

Yes, I have. This problem is only for iOS side. In my case, I received notification (with @react-native-firebase/messaging and react-native-push-notification), in background, and I have 2 situations:

  1. When I immediately open Notification, there is no problem with meteor call
  2. When I open Notification after some time (~5 min and more), meteor call does not work. I have no response from server. I checked Meteor.status() and Meteor.ddp.status before Meteor call and it returns true.

Example:

import PushNotificationIOS from '@react-native-community/push-notification-ios';
import PushNotification from 'react-native-push-notification';
import { getRequestDetails } from '../../store/drs/actions/drsActions';
import Meteor from '@meteorrn/core';

export const configurePushNotification = (AppState) => {
    PushNotification.configure({
        onNotification: function (notification) {
            const drId = notification?.data?.id || '';
            if (drId) {
                console.log('Notification_issue Meteor.status()', Meteor.status());
                console.log('Notification_issue Meteor ddp status', Meteor.ddp.status);
                if (Meteor.userId()) {
                    // this is my Meteor call, also Meteor.userId() is true
                    getRequestDetails(drId, (err, data) => {
                        if (!err) {
                            // do some stuff
                        }
                    }, notification?.data?.section === 'chat');
                } else {
                    // navigate to login screen
                }
                notification.finish(PushNotificationIOS.FetchResult.NoData);
            }
        },
        permissions: {
            alert: true,
            badge: true,
            sound: true,
        },
        popInitialNotification: true,
        requestPermissions: true,
    });
}

And my function where I perform Meteor call

export const getRequestDetails = async (id, callback, isDisabledNewDrUpdate?) => {
    Meteor.waitDdpConnected(async () => {
        await Meteor.call('clinicians-drs.getOne', { id }, (err, val) => {
            if (err) {
                // crashlytics
                return callback(err.reason, null);
            }
            return callback(null, val);
        });
    })
};
mmelk commented 1 year ago

@jankapunkt have you any update on this?

jankapunkt commented 1 year ago

@mmelk I have no firebase account nor do we use firebase in any of our projects so I can only test the Meteor.waitDdpConnected part. Is there a way I can make your issue reproducible without firebase?

mmelk commented 1 year ago

@jankapunkt Can you test on the other notification support library? OneSignal maybe? The issue reproduced when I came from background, tapping on the notification from the Notification Center on iOS device. I do meteor call, and in callback I navigate user to corresponding screen, but meteor call is not working, no any response, and the call not reached the server.

mmelk commented 1 year ago

Which concerns Meteor.waitDdpConnected, I logged Meteor.status() and Meteor.ddp.status before Meteor call. It says that Meteor is connected successfully.

jankapunkt commented 1 year ago

@mmelk if played a few options here. First, please check your Meteor.connect in your code and make sure it looks similar to this one:

Meteor.enableVerbose() // more logs in dev mode

Meteor.connect(serverurl, {
  AsyncStorage: AsyncStorage,
  autoConnect: true,
  autoReconnect: true,
  reconnectInterval: 500, // try a short value here, the default is 10 Seconds!
})

Then please try the following change to your getRequestDetails:

export const getRequestDetails = (id, callback) => {
  const onError = err => {
    cleanup()
    return callback(err)
  }
  const onConnected = () => {
    Meteor.call('dead-request', () => {
      Meteor.call('clinicians-drs.getOne', { id }, (err, val) => {
        cleanup()
        if (err) {
          // crashlytics here?
          return callback(err)
        }
        return callback(null, val)
      });
    })
  }
  const onDisconnected = () => {
    console.debug('still disconnected...')
    Meteor.reconnect() // force reconnect
  }
  const cleanup = () => {
    // make sure we have no mem-leaks
    Meteor.ddp.off('error', onError)
    Meteor.ddp.off('connected', onConnected)
    Meteor.ddp.off('disconnected', onDisconnected)
  }

  // finally listen to the events
  Meteor.ddp.on('error', onError)
  Meteor.ddp.on('connected', onConnected)
  Meteor.ddp.on('disconnected', onDisconnected)
}

Now have in your server a Meteor Method named dead-request that simply does nothing but return null or something:

Meteor.methods({
  'dead-request': function () {
    return null
  }
})

Here is my observation: as already mentioned in this topic the call is not cached and we have no quick fix for that right now (as I have other PRs in the pipeline but after that I will take a look at this) and the chance is high, that with the default reconnect intervals there will be the first request be missed even if we are reconnected (that's at least my impression from the above conversation).

We therefore need to

I know this might be not 100% accurate, especially for production but maybe it helps to get closer to a solution in the meantime