-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Resync data when the app reconnects #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Oh, I also renamed the action files. Something @iwiznia requested. Sorry to pollute the PR :D |
| // Two things will bring the app online again... | ||
| // 1. Pusher reconnecting (see registerSocketEventCallback at the top of this file) | ||
| // 2. Getting a 200 response back from the API (happens right below) | ||
| Ion.get(IONKEYS.NETWORK, 'isOffline') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change actually removes the need to rely on a local variable for knowing if the app is offline or not
src/lib/Network.js
Outdated
| return; | ||
| } | ||
| // Make a simple request every second to see if the API is online again | ||
| fetch(`${CONFIG.EXPENSIFY.API_ROOT}command=Get`, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where I changed request() to fetch()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use request or xhr instead? They have logic like catch that this doesn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can try it again, yes. That catch() logic doesn't actually do anything to help what we're trying to do here (ie. we KNOW this is going to fail until the API is back online).
|
@marcaaron I'm adding you here to take a look at this as well |
marcaaron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just thought of something and I'd like to get your thoughts before we commit to this solution. We don't really need to add a "connect" listener to Network or an extra callback property to the Ion mapping because:
- There is only one situation where we must refetch keys (
isOfflinegoing fromfalse>true) - Ion already knows when we are going online or offline because it is responsible for watching these changes.
- We should just tell
Ionwhich actions it needs to fetch and haveNetworkonly worry about updatingisOffline.
|
|
||
| // If the request failed, we need to put the request object back into the queue as long as there is no | ||
| // doNotRetry option set in the data | ||
| if (data.doNotRetry !== true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should still keep this right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, my logic was that there was only one thing using it (the processWriteQueue method), and if that method was no longer going to call request() then it didn't need to stay here. However, if we really want to avoid using fetch() in processWriteQueue then this will probably come back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, but does that mean that requests that go through the network request queue won't be re-tried?
| || nextProps.displayAsGroup !== this.props.displayAsGroup | ||
| || nextProps.authToken !== this.props.authToken; | ||
| || nextProps.authToken !== this.props.authToken | ||
| || !_.isEqual(nextProps.historyItem, this.props.historyItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this line makes nextProps.historyItem.sequenceNumber !== this.props.historyItem.sequenceNumber redundant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes it does 👍
src/lib/Ion.js
Outdated
| * @param {string} mapping.statePropertyName the name of the property in the state to connect the data to | ||
| * @param {boolean} [mapping.addAsCollection] rather than setting a single state value, this will add things to an array | ||
| * @param {string} [mapping.collectionID] the name of the ID property to use for the collection | ||
| * @param {object} [mapping.callback] An alternative to using mapping.reactComponent so that a method can be bound to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to using mapping.reactComponent , what does this mean? I don't see it anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe reactComponent is withIonInstance now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yep, it was renamed.
src/lib/Ion.js
Outdated
| [mappedComponent.statePropertyName]: newValue, | ||
| }); | ||
| // If there is a callback attached to the mapping, then trigger the callback and pass it the new value | ||
| if (mappedComponent.callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (_.isFunction(mappedComponent.callback)) {
src/lib/Network.js
Outdated
| return; | ||
| } | ||
| // Make a simple request every second to see if the API is online again | ||
| fetch(`${CONFIG.EXPENSIFY.API_ROOT}command=Get`, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use request or xhr instead? They have logic like catch that this doesn't
|
All right, updated! |
marcaaron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I can't actually get these test steps to work because when I go offline in Chrome Dev Tools web sockets still work 🤣
src/lib/Ion.js
Outdated
| [mappedComponent.statePropertyName]: newValue, | ||
| }); | ||
| // If there is a withIonInstance, then the state needs to be set on that instance with the new value | ||
| if (mappedComponent.withIonInstance) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we leaving this in case we want to call this without a withIonInstance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove this and just keep it as a required param for now.
| _.each(reconnectionCallbacks, cb => cb()); | ||
| } | ||
| isAppOffline = isCurrentlyOffline; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we check the store value instead? If I land on this file for the first time it's not clear whether I should use the local variable or Ion to get the offline status. I think we can remove the local variable entirely now.
My recommendation would be to change this to...
async function setNewOfflineStatus(isCurrentlyOffline) {
const previousOfflineStatus = await Ion.get(IONKEYS.NETWORK, 'isOffline');
Ion.merge(IONKEYS.NETWORK, {isOffline: isCurrentlyOffline});
if (previousOfflineStatus && !isCurrentlyOffline) {
_.each(reconnectionCallbacks, cb => cb());
}
}![]()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK. That makes sense, yeah. That will help clean this up a little.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and I see what you did there ... ![]()
|
Hmm tried with ngrok and it still doesn't seem to be working |
No comment shows up until a page refresh and the comment that was queued to send on the report that went offline never shows up again. |
|
You should still be able to test this with just the offline browser setting. In step 3, when you try to post the comment in offline mode, the failed API call will trigger the Here is a video of me testing it: |
|
Updated |
|
Alright lemme give it another try. |
|
I watched your video and noticed that the comment you are leaving on the online tab appears on the offline tab before you set it to online. It is being handled by Pusher somehow and replaces the optimistic comment. Once you come back online everything does seem to work. I tested it again and got the same result so happy to move forward on this. Especially since I don't think it's possible for an offline comment to get overwritten by Pusher in production. |
marcaaron
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me!
|
Ah, cool. Glad you got it to work! |
|
@cead22 All you! |
| */ | ||
| function setNewOfflineStatus(isCurrentlyOffline) { | ||
| Ion.get(IONKEYS.NETWORK, 'isOffline') | ||
| .then((prevWasOffline) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB but suggestions for better var names: wasPreviouslyOffline, wasOffline or previouslyWasOffline
| } | ||
| for (let i = 0; i < networkRequestQueue.length; i++) { | ||
| // Take the request object out of the queue and make the request | ||
| const queuedRequest = networkRequestQueue.shift(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fyi and @cead22 looking at this again, I think this is a bug since we are modifying the size of the queue on each iteration?
e.g.
iteration 1: i = 0; i < size 3
iteration 2: i = 1; i < size 2
iteration 3: i = 2; i < size 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do this instead
networkRequestQueue.forEach(queuedRequest => {
request(queuedRequest.command, queuedRequest.data)
.then(queuedRequest.callback);
});
networkRequestQueue = [];There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. We need to make sure that if one request fails that we pause, and then resume from the same spot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make them synchronous then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, no I don't think so, but to rephrase, we need to make sure we don't lose any of these requests in the process of queueing them / firing them off / ignoring them when offline or reauthenticating
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused about what that console code is showing or what the possible bug here is. Can you restate what the bug is that you think is happening?
I don't think we should assume that they can happen in any order, and we should try to preserve the order that they were queued in when possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Problem: Given an array of queued network requests with a size greater than 1 we will never process the entire queue in one call of this method with this existing logic.
I don't know if this has any super obvious bug-like effects just yet. But it seems to force the requests that did not get processed on one call to be processed in the next call of this function which happens once per second? The queue will eventually empty. But is that what we want?
Mostly, I'm pointing out that this logic is tricky, doesn't do what it looks like it would do, and doesn't do what we initially wanted... which is to empty the entire queue.
Let me know if that makes sense!
I don't think we should assume that they can happen in any order, and we should try to preserve the order that they were queued in when possible.
To clarify, are you suggesting that the requests should not complete in any order? Or that they need only be requested in a certain order?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK. That makes sense, and you're right. Though I don't think it's causing a bug, it is delaying requests unnecessarily and the code isn't very clear. I think using a do/while is probably better. Like this:
const array = [1,2,3];
let i = 0;
do {
const item = array.shift();
console.log(i);
i++;
} while (array.length > 0)
To clarify, are you suggesting that the requests should not complete in any order? Or that they need only be requested in a certain order?
Only that they be requested in a certain order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! In that case, yes this will work. Tbh not too sure I see any clear advantage to using a do/while vs. a for loop or forEach, but maybe you are seeing something I'm not.

Fixes #253
Tests
5show up