Skip to content

Commit

Permalink
Merge pull request #12135 from Expensify/neil-switch-offline
Browse files Browse the repository at this point in the history
Add a switch on dev and staging to force offline mode
  • Loading branch information
neil-marcellini authored Dec 1, 2022
2 parents 76bfce3 + 7960bdc commit 3342c39
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 6 deletions.
10 changes: 9 additions & 1 deletion src/components/TestToolMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ const TestToolMenu = props => (
/>
</TestToolRow>

{/* When toggled the app will be forced offline. */}
<TestToolRow title="Force offline">
<Switch
isOn={Boolean(props.network.shouldForceOffline)}
onToggle={() => Network.setShouldForceOffline(!props.network.shouldForceOffline)}
/>
</TestToolRow>

{/* When toggled all network requests will fail. */}
<TestToolRow title="Simulate failing network requests">
<Switch
isOn={props.network.shouldFailAllRequests || false}
isOn={Boolean(props.network.shouldFailAllRequests)}
onToggle={() => Network.setShouldFailAllRequests(!props.network.shouldFailAllRequests)}
/>
</TestToolRow>
Expand Down
3 changes: 3 additions & 0 deletions src/components/networkPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ export default PropTypes.shape({
/** Is the network currently offline or not */
isOffline: PropTypes.bool,

/** Should the network be forced offline */
shouldForceOffline: PropTypes.bool,

/** Whether we should fail all network requests */
shouldFailAllRequests: PropTypes.bool,
});
11 changes: 9 additions & 2 deletions src/libs/HttpUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@ Onyx.connect({
});

let shouldFailAllRequests = false;
let shouldForceOffline = false;
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: val => shouldFailAllRequests = (val && _.isBoolean(val.shouldFailAllRequests)) ? val.shouldFailAllRequests : false,
callback: (network) => {
if (!network) {
return;
}
shouldFailAllRequests = Boolean(network.shouldFailAllRequests);
shouldForceOffline = Boolean(network.shouldForceOffline);
},
});

// We use the AbortController API to terminate pending request in `cancelPendingRequests`
Expand All @@ -40,7 +47,7 @@ function processHTTPRequest(url, method = 'get', body = null, canCancel = true)
})
.then((response) => {
// Test mode where all requests will succeed in the server, but fail to return a response
if (shouldFailAllRequests) {
if (shouldFailAllRequests || shouldForceOffline) {
throw new HttpsError({
message: CONST.ERROR.FAILED_TO_FETCH,
});
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Network/NetworkStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Onyx.connect({
triggerReconnectCallback();
}

offline = network.isOffline;
offline = Boolean(network.shouldForceOffline) || network.isOffline;
},
});

Expand Down
29 changes: 29 additions & 0 deletions src/libs/NetworkConnection.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import NetInfo from '@react-native-community/netinfo';
import AppStateMonitor from './AppStateMonitor';
import Log from './Log';
import * as NetworkActions from './actions/Network';
import CONFIG from '../CONFIG';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';

let isOffline = false;
let hasPendingNetworkCheck = false;
Expand Down Expand Up @@ -39,6 +41,29 @@ function setOfflineStatus(isCurrentlyOffline) {
isOffline = isCurrentlyOffline;
}

// Update the offline status in response to changes in shouldForceOffline
let shouldForceOffline = false;
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: (network) => {
if (!network) {
return;
}
const currentShouldForceOffline = Boolean(network.shouldForceOffline);
if (currentShouldForceOffline === shouldForceOffline) {
return;
}
shouldForceOffline = currentShouldForceOffline;
if (shouldForceOffline) {
setOfflineStatus(true);
} else {
// If we are no longer forcing offline fetch the NetInfo to set isOffline appropriately
NetInfo.fetch()
.then(state => setOfflineStatus(state.isInternetReachable === false));
}
},
});

/**
* Set up the event listener for NetInfo to tell whether the user has
* internet connectivity or not. This is more reliable than the Pusher
Expand All @@ -65,6 +90,10 @@ function subscribeToNetInfo() {
// whether a user has internet connectivity or not.
NetInfo.addEventListener((state) => {
Log.info('[NetworkConnection] NetInfo state change', false, state);
if (shouldForceOffline) {
Log.info('[NetworkConnection] Not setting offline status because shouldForceOffline = true');
return;
}
setOfflineStatus(state.isInternetReachable === false);
});
}
Expand Down
18 changes: 18 additions & 0 deletions src/libs/Pusher/pusher.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import ONYXKEYS from '../../ONYXKEYS';
import Pusher from './library';
import TYPE from './EventType';
import Log from '../Log';

let shouldForceOffline = false;
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: (network) => {
if (!network) {
return;
}
shouldForceOffline = Boolean(network.shouldForceOffline);
},
});

let socket;
const socketEventCallbacks = [];
let customAuthorizer;
Expand Down Expand Up @@ -112,6 +125,11 @@ function bindEventToChannel(channel, eventName, eventCallback = () => {}) {

const chunkedDataEvents = {};
const callback = (eventData) => {
if (shouldForceOffline) {
Log.info('[Pusher] Ignoring a Push event because shouldForceOffline = true');
return;
}

let data;
try {
data = _.isObject(eventData) ? eventData : JSON.parse(eventData);
Expand Down
9 changes: 9 additions & 0 deletions src/libs/actions/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ function setIsOffline(isOffline) {
Onyx.merge(ONYXKEYS.NETWORK, {isOffline});
}

/**
*
* @param {Boolean} shouldForceOffline
*/
function setShouldForceOffline(shouldForceOffline) {
Onyx.merge(ONYXKEYS.NETWORK, {shouldForceOffline});
}

/**
* Test tool that will fail all network requests when enabled
* @param {Boolean} shouldFailAllRequests
Expand All @@ -18,5 +26,6 @@ function setShouldFailAllRequests(shouldFailAllRequests) {

export {
setIsOffline,
setShouldForceOffline,
setShouldFailAllRequests,
};
15 changes: 13 additions & 2 deletions src/libs/actions/SignInRedirect.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ Onyx.connect({
});

let currentIsOffline;
let currentShouldForceOffline;
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: val => currentIsOffline = val.isOffline,
callback: (network) => {
if (!network) {
return;
}
currentIsOffline = network.isOffline;
currentShouldForceOffline = Boolean(network.shouldForceOffline);
},
});

/**
Expand All @@ -31,6 +38,7 @@ function clearStorageAndRedirect(errorMessage) {
const activeClients = currentActiveClients;
const preferredLocale = currentPreferredLocale;
const isOffline = currentIsOffline;
const shouldForceOffline = currentShouldForceOffline;

// Clearing storage discards the authToken. This causes a redirect to the SignIn screen
Onyx.clear()
Expand All @@ -41,7 +49,10 @@ function clearStorageAndRedirect(errorMessage) {
if (activeClients && activeClients.length > 0) {
Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients);
}
if (isOffline) {

// After signing out, set ourselves as offline if we were offline before logging out and we are not forcing it.
// If we are forcing offline, ignore it while signed out, otherwise it would require a refresh because there's no way to toggle the switch to go back online while signed out.
if (isOffline && !shouldForceOffline) {
Onyx.set(ONYXKEYS.NETWORK, {isOffline});
}

Expand Down

0 comments on commit 3342c39

Please sign in to comment.