Closed tienifr closed 4 days ago
@cubuspl42 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]
Currently I found no way to manually block specific network requests on native apps the way we did on web with the support of DevTools. My only solution was to hard-code the reachability URL to make it fail.
@tienifr If there's no better way, we can test it this way on Native. In rare cases, we test things by applying a small code change. Please specify this technique in the "Tests" steps.
@cubuspl42 I updated all the comments and replied to your feedbacks above.
@cubuspl42 I extracted local subscribeToBackendReachability
and updated minor comments as suggested.
I started testing, it's working very well so far 🙂
Okey, we've got an issue.
Steps:
Expected result (observed on main
):
"You appear to be offline"
Actual result: "We might have a problem (...)"
I think that "offline" means for us:
The second check doesn't seem to work now on mobile Native.
@tienifr Bump!
Normally net-info
should use the default config as per here to check for internet connectivity but not sure why it didn't work. I'm taking a look.
reachabilityUrl
The URL to call to test if the internet is reachable. Only used on platforms which do not supply internet reachability natively or if useNativeReachability is false.
You could dig more here. Does "native reachability" include any effective Web endpoints, or does it rely on user-triggered Wi-Fi/Cellular switches only? Maybe iOS uses some apple.com
endpoint and tries to do some "effective Internet reachability", but it failed in my case? What about Android? Maybe it's an emulator-related thing?
Also, maybe we could change our definition of "online" on mobile Native, so "effectively offline" (being in a train with close-to-zero cellular reception, being connected to a LAN-only Wi-Fi router, etc.) is considered "online" by Expensify. But this would require Slack discussion and I think we should consider it plan B (or plan C).
This is what I found on Apple Developer here.
That means as long as the device is connected to any network, it's considered "reachable".
I searched over the Internet and people all suggested to write a custom "ping" to a high-availability endpoint. Thus I think we should disable the useNativeReachability
config so it will always use the lib's default reachabilityUrl
.
Give me some time to investigate if disabling it caused any side-effects.
should disable the useNativeReachability
I'm not excited by that!
You can give it a try, but I'm afraid we'll miss big potential of immediate system feedback when we know that the device is offline (e.g. wifi = off
, cellular = off
).
It appears that NetInfo gives us a choice: either use a built-in endpoint reachability loop or use the built-in native reachability integration.
I believe that the latter (native reachability integration) is a higher-value functionality.
I can see two viable options, I'll leave the decision to you:
When I say "home-made Google endpoint reachability loop", I mean re-using the code we created for the backend endpoint reachability loop.
Start a Slack discussion, mentioning the pragmatic user-observable behavior difference on Native
But remember that that means we'll blame Expensify in the "offline in a train" scenario!
The user will be effectively offline (very low reception), the system will report "user is online", the backend will be unavailable, so we'll go with "Expensify has a problem" communication.
@cubuspl42 I spent time digging into react-native-netinfo
source code and tried reproducing the internet reachability issue many times. Here're what I found:
You appear to be offline
. Also note that you cannot disable Mac's wifi to simulate no internet condition because iOS simulator shares the same network instance/info with your Mac. If you disabled Mac's wifi, the NetInfo's isConnected
in the simulator would be false
. I was a bit subjective when I received your feedback about the bug so I did not try to reproduce it.react-native-netinfo
always uses the default NetInfoConfiguration
to check for internet reachability when the platform does not support it (internet reachability) natively (iOS
in this case and iOS
only, Android
can check for internet access itself). Highlighted in the docs here. I also saw the high-availability endpoint being fetched in Network debugger.In conclusion, I have proof that netinfo
still run the reachability check interval on iOS
. The issue, if it may happen, can be because the interval time of the lib's and our own check are the same (i.e. 60 seconds) and thus may cause some kind of race condition.
So can you please give it another try? If the issue was no longer reproducible we could continue.
Thank you for diving deep into this! I'm sorry if I provided some misleading information. I misunderstood the "which do not supply internet reachability natively" part of the NetInfo docs, which might've biased me.
Android emulator, Native build, after macOS Wi-Fi is turned off:
Expected: "You are offline" Actual: "We might have a problem"
Please correct me if I'm wrong.
Check today.
I tested on Android device and here's what I found: Android only checks for internet reachability on initiation. That means if the network lost internet access anytime after the connection initialization, we wouldn't know.
I think that we should add another check for a highly available endpoint when the backend is unreachable. OR we can set another interval polling for that highly avaiable endpoint as your suggestion. Let me do some tests.
@cubuspl42 This is ready for review again.
Android only checks for internet reachability on initiation. That means if the network lost internet access anytime after the connection initialization, we wouldn't know.
I'm a bit lost... But didn't we depend on NetInfo regular URL endpoint checks in the old solution (the current main
), when we provided it with a custom endpoint URL? Also on Android?
Android supports internet reachability natively, it won't use the URL test. So the issue also happens on main
(turning off the Internet does not show You appear to be offline
).
Okey, I gave it a lot of thought. I have some reservations about the current code, but I like the direction we're going in.
I sketched something that I'd consider an improvement over the current code, both in the terms of code organization and behavior in corner cases. I haven't tested it though, it's just a dump of my idea.
type Subscription = () => void;
type InternetReachabilityStatus =
'backendReachable' | // Backend is reachable, which must mean the Internet is reachable
'backendUnreachable' | // Backend is unreachable, but the Internet is otherwise reachable
'internetUnreachable'; // Backend is unreachable, neither is the whole Internet
type NetworkConnectivityStatus =
'disconnected' | // Network is disconnected, which implies the Internet is unreachable
'connected'; // Network is connected, but the Internet might still be unreachable
type AppNetworkStatus =
'offline' | // We assume the device is not effectively connected to the Internet
'backendDown' | // We assume that the device is connected, but the backend is down
'online'; // We assume that the network connection is healthy and the backend can be reached
// Cross-platform
function subscribeToInternetReachability(
callback: (status: InternetReachabilityStatus) => void,
): Subscription {
throw new Error("TODO: Implement using custom loop and one-off requests");
// Basically what we have now, but let's keep the fallback logic to check high-availability endpoint when backend is
// unreachable _on all platforms_. I'm suggesting making this the only source of truth about Internet being fully
// unreachable. This way, we'll have no race conditions between the backend loop and the Internet loop, also
// we'll minimize the number of unnecessary requests, as backend being reachable implies Internet being reachable.
}
// Web
function subscribeToNetworkConnectivityStatus(
callback: (status: NetworkConnectivityStatus) => void,
): Subscription {
// No-op, as we don't have any access to the network connectivity status on the Web
return () => {
};
}
// Native
function subscribeToNetworkConnectivityStatus(
callback: (status: NetworkConnectivityStatus) => void,
): Subscription {
throw new Error("TODO: Implement based on NetInfo subscriptions / isConnected");
// Let's drop any NetInfo reasoning about the Internet reachability (as opposed to local network connectivity status,
// like cellular / Wi-Fi being on/off). Why? Well, it doesn't work well on Android (it seems), and although it might
// work on iOS, it still won't be performed at the perfect moment. So maybe let's just ignore it, and assume that
// our source of truth for this information is as good as the one in the system?
}
// Cross-platform
// Subscribe to the real app network status, the offline forcing can be implemented on top of this
function subscribeToAppNetworkStatus(
callback: (status: AppNetworkStatus) => void,
): Subscription {
let internetReachabilityStatus: InternetReachabilityStatus = 'backendReachable';
let networkConnectivityStatus: NetworkConnectivityStatus = 'connected';
const establishAppNetworkStatus = (): AppNetworkStatus => {
switch (networkConnectivityStatus) {
case "disconnected": {
return 'offline';
}
case "connected": {
switch (internetReachabilityStatus) {
case "backendReachable":
return 'online';
case "backendUnreachable":
return 'backendDown';
case "internetUnreachable":
return 'offline';
}
}
}
};
const triggerCallback = () => {
callback(establishAppNetworkStatus());
};
const unsubscribeFromInternetReachability = subscribeToInternetReachability((status) => {
internetReachabilityStatus = status;
triggerCallback();
});
const unsubscribeFromNetworkConnectivity = subscribeToNetworkConnectivityStatus((status) => {
networkConnectivityStatus = status;
triggerCallback();
});
return () => {
unsubscribeFromInternetReachability();
unsubscribeFromNetworkConnectivity();
};
}
What do you think?
@tienifr Bump! I know it's a tough task, and there are some hairy corner cases, but please provide some update 🙂
subscribeToInternetReachability ... let's keep the fallback logic to check high-availability endpoint when backend is unreachable on all platforms
subscribeToNetworkConnectivityStatus ... Implement based on NetInfo subscriptions / isConnected
By these points, do you mean we implement our own logic for internet, connectivity and backend reachability and ignore netinfo
logic?
I myself think the current logic is more readable and understandable. It's just simple: Backend check >> Internet check (if backend failed). We don't have redundant requests, only one Ping
every 60 seconds and one high-availability fetch if Ping
failed. But I appreciate your suggestion to use enumeration for the states anyway 👍. I'll update it.
Taking over as C+
Conflicts to resolve here! Assigned you as the reviewer @DylanDylann.
~@aldo-expensify The change looks good to me. Could you help to add "ready to build" label for testing?~
Whoops, I can test on the emulator
@tienifr BUG: Flicker when going online. When going online the indicator message turns into We might have a problem. Check out status.expensify.com
a moment before disappearing
https://github.com/Expensify/App/assets/141406735/d1923f36-d326-408b-8a40-311bcdcfcd91
Thanks @DylanDylann I found the root cause and solution for this, but need time to retest the flow on all platforms. I'll push the update today.
@DylanDylann That issue happened when the BE was previously being unreachable, then when we turned back online, the networkStatus
is unknown
causing isOffline
to be false
:
Now isOffline = false
and isBackendReachable = false
causing the We might have a problem.
to show. Later, we had the logic to set isBackendReachable
to true
when we turned back online here and that message disappeared.
My solution is that if network status is unknown, we should treat it as if we're online and backend is reachable here. That's what we already did with isOffline
as mentioned above.
Reviewing today
### Fixed Issues
section aboveTests
sectionQA steps
sectiontoggleReport
and not onIconClick
).myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components using Avatar
have been tested & I retested again)/** comment above it */
this
properly so there are no scoping issues (i.e. for onClick={this.submit}
the method this.submit
should be bound to this
in the constructor)this
are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this);
if this.submit
is never passed to a component event handler like onClick
)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG
)Avatar
is modified, I verified that Avatar
is working as expected in all cases)Design
label and/or tagged @Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test
steps.Hi @aldo-expensify, merge freeze is over, I think we're good to proceed this.
:hand: This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.
🚀 Deployed to staging by https://github.com/aldo-expensify in version: 1.4.74-0 🚀
platform | result |
---|---|
🤖 android 🤖 | success ✅ |
🖥 desktop 🖥 | success ✅ |
🍎 iOS 🍎 | success ✅ |
🕸 web 🕸 | success ✅ |
Details
When user is offline, we display offline message (
You appear to be offline.
), when user is online but our backend is unreachable, we displayWe might have a problem. Check out status.expensify.com
message.Fixed Issues
$ https://github.com/Expensify/App/issues/37565 PROPOSAL: https://github.com/Expensify/App/issues/37565#issuecomment-1976463123
Tests
Network Devtools
**Web**: Open DevTools >> Network **Native**: Toggle RN dev menu by `CMD + D` >> Open Element Inspector >> NetworkBlock network request
**Chrome** Open DevTools >> More tools >> Network request blocking >> Enable network request blocking >> Add network request blocking pattern as `https://dev.new.expensify.com:8082/api` **Safari** 1. Open DevTools >> Sources >> + >> Local Override... 2. Press + next to Local Overrides >> Select `Block` type and URL as `https://dev.new.expensify.com:8082/api` with Regular Expression enabled **Native** Hard-code the reachability URL [here](https://github.com/Expensify/App/blob/f3be10027c4d786662253794c2cc7831170baf2a/src/libs/NetworkConnection.ts#L88) to an invalid URL.Ping
command is called every 60 seconds (see Network Devtools)https://dev.new.expensify.com:8082/api
request to make backend unreachable (see Block network request)Verify that after a while,
We might have a problem. Check out status.expensify.com.
message appears and the status page URL can be openedYou appear to be offline.
message appearsVerify that after a while, the unreachability message appears
Offline tests
Same as Tests
QA Steps
NA
PR Author Checklist
### Fixed Issues
section aboveTests
sectionOffline steps
sectionQA steps
sectiontoggleReport
and notonIconClick
)myBool && <MyComponent />
.src/languages/*
files and using the translation methodSTYLE.md
) were followedAvatar
, I verified the components usingAvatar
are working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
)Avatar
is modified, I verified thatAvatar
is working as expected in all cases)Design
label and/or tagged@Expensify/design
so the design team can review the changes.ScrollView
component to make it scrollable when more elements are added to the page.main
branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTest
steps.Screenshots/Videos
Android: Native
https://github.com/Expensify/App/assets/113963320/d59975d5-4940-453d-b3e7-01e1dde6a50aAndroid: mWeb Chrome
https://github.com/Expensify/App/assets/113963320/237f3e56-4f0b-49e4-8f9e-bb24edc1a854iOS: Native
https://github.com/Expensify/App/assets/113963320/27b329da-ba37-4b48-8498-de2cc2f09fe0iOS: mWeb Safari
https://github.com/Expensify/App/assets/113963320/ce09149a-4a2b-49c7-8d87-0874fd00ff92MacOS: Chrome / Safari
https://github.com/Expensify/App/assets/113963320/f53f2698-2099-4778-adbd-0bf657381198MacOS: Desktop
https://github.com/Expensify/App/assets/113963320/f53f2698-2099-4778-adbd-0bf657381198