Skip to content

Commit

Permalink
Move callbacks to Events. Start separating logic more.
Browse files Browse the repository at this point in the history
  • Loading branch information
marcaaron committed Mar 24, 2022
1 parent 204514d commit 9193948
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 155 deletions.
12 changes: 6 additions & 6 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import updateSessionAuthTokens from './actions/Session/updateSessionAuthTokens';
import setSessionLoadingAndError from './actions/Session/setSessionLoadingAndError';
import * as NetworkStore from './Network/NetworkStore';
import enhanceParameters from './Network/enhanceParameters';

import * as NetworkEvents from './Network/NetworkEvents';

/**
* Function used to handle expired auth tokens. It re-authenticates with the API and
Expand Down Expand Up @@ -48,9 +48,9 @@ function handleExpiredAuthToken(originalCommand, originalParameters, originalTyp
));
}

Network.registerLogHandler(() => Log);
NetworkEvents.registerLogHandler(() => Log);

Network.registerRequestHandler((queuedRequest, finalParameters) => {
NetworkEvents.registerRequestHandler((queuedRequest, finalParameters) => {
if (queuedRequest.command === 'Log') {
return;
}
Expand All @@ -63,11 +63,11 @@ Network.registerRequestHandler((queuedRequest, finalParameters) => {
});
});

Network.registerRequestSkippedHandler((parameters) => {
NetworkEvents.registerRequestSkippedHandler((parameters) => {
Log.hmmm('Trying to make a request when Network is not ready', parameters);
});

Network.registerResponseHandler((queuedRequest, response) => {
NetworkEvents.registerResponseHandler((queuedRequest, response) => {
if (queuedRequest.command !== 'Log') {
Log.info('Finished API request', false, {
command: queuedRequest.command,
Expand Down Expand Up @@ -110,7 +110,7 @@ Network.registerResponseHandler((queuedRequest, response) => {
queuedRequest.resolve(response);
});

Network.registerErrorHandler((queuedRequest, error) => {
NetworkEvents.registerErrorHandler((queuedRequest, error) => {
if (error.name === CONST.ERROR.REQUEST_CANCELLED) {
Log.info('[API] request canceled', false, queuedRequest);
return;
Expand Down
26 changes: 26 additions & 0 deletions src/libs/Network/NetworkEvents.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import createCallback from '../createCallback';

const [getLogger, registerLogHandler] = createCallback();
const [triggerConnectivityResumed, onConnectivityResumed] = createCallback();
const [onRequest, registerRequestHandler] = createCallback();
const [onResponse, registerResponseHandler] = createCallback();
const [onError, registerErrorHandler] = createCallback();
const [triggerRecheckNeeded, registerConnectionCheckCallback] = createCallback();
const [onRequestSkipped, registerRequestSkippedHandler] = createCallback();

export {
registerLogHandler,
getLogger,
triggerConnectivityResumed,
onConnectivityResumed,
onRequest,
registerRequestHandler,
onResponse,
registerResponseHandler,
onError,
registerErrorHandler,
triggerRecheckNeeded,
registerConnectionCheckCallback,
registerRequestSkippedHandler,
onRequestSkipped,
};
28 changes: 28 additions & 0 deletions src/libs/Network/NetworkStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import ONYXKEYS from '../../ONYXKEYS';
import * as NetworkEvents from './NetworkEvents';

let credentials;
let authToken;
let currentUserEmail;
let networkReady = false;
let authenticating = false;
let isOffline = false;

/**
* @param {Boolean} ready
Expand Down Expand Up @@ -41,6 +43,31 @@ Onyx.connect({
},
});

// We subscribe to the online/offline status of the network to determine when we should fire off API calls
// vs queueing them for later.
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: (network) => {
if (!network) {
return;
}

// Client becomes online emit connectivity resumed event
if (isOffline && !network.isOffline) {
NetworkEvents.triggerConnectivityResumed();
}

isOffline = network.isOffline;
},
});

/**
* @returns {Boolean}
*/
function getIsOffline() {
return isOffline;
}

/**
* @returns {String}
*/
Expand Down Expand Up @@ -99,4 +126,5 @@ export {
setIsReady,
setIsAuthenticating,
isAuthenticating,
getIsOffline,
};
96 changes: 96 additions & 0 deletions src/libs/Network/PersistedRequestsQueue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import _ from 'underscore';
import Onyx from 'react-native-onyx';
import * as NetworkRequestQueue from '../actions/NetworkRequestQueue';
import * as NetworkStore from './NetworkStore';
import * as NetworkEvents from './NetworkEvents';
import CONST from '../../CONST';
import ONYXKEYS from '../../ONYXKEYS';
import * as ActiveClientManager from '../ActiveClientManager';
import processRequest from './processRequest';

let persistedRequestsQueueRunning = false;

/**
* This method will get any persisted requests and fire them off in parallel to retry them.
* If we get any jsonCode besides 407 the request is a success. It doesn't make sense to
* continually retry things that have returned a response. However, we can retry any requests
* with known networking errors like "Failed to fetch".
*
* @returns {Promise}
*/
function process() {
const persistedRequests = NetworkRequestQueue.getPersistedRequests();

// This sanity check is also a recursion exit point
if (NetworkStore.getIsOffline() || _.isEmpty(persistedRequests)) {
return Promise.resolve();
}

const tasks = _.map(persistedRequests, request => processRequest(request)
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) {
NetworkEvents.getLogger().info('Persisted optimistic request needs authentication');
} else {
NetworkEvents.getLogger().info('Persisted optimistic request returned a valid jsonCode. Not retrying.');
}
NetworkEvents.onResponse(request, response);
NetworkRequestQueue.removeRetryableRequest(request);
})
.catch((error) => {
// If we are catching a known network error like "Failed to fetch" allow this request to be retried if we have retries left
if (error.message === CONST.ERROR.FAILED_TO_FETCH) {
const retryCount = NetworkRequestQueue.incrementRetries(request);
NetworkEvents.getLogger().info('Persisted request failed', false, {retryCount, command: request.command, error: error.message});
if (retryCount >= CONST.NETWORK.MAX_REQUEST_RETRIES) {
NetworkEvents.getLogger().info('Request failed too many times removing from storage', false, {retryCount, command: request.command, error: error.message});
NetworkRequestQueue.removeRetryableRequest(request);
}
} else if (error.name === CONST.ERROR.REQUEST_CANCELLED) {
NetworkEvents.getLogger().info('Persisted request was cancelled. Not retrying.');
NetworkEvents.onError(request);
NetworkRequestQueue.removeRetryableRequest(request);
} else {
NetworkEvents.getLogger().alert(`${CONST.ERROR.ENSURE_BUGBOT} unknown error while retrying persisted request. Not retrying.`, {
command: request.command,
error: error.message,
});
NetworkRequestQueue.removeRetryableRequest(request);
}
}));

// Do a recursive call in case the queue is not empty after processing the current batch
return Promise.all(tasks)
.then(process);
}

function flush() {
if (persistedRequestsQueueRunning) {
return;
}

// NETWORK_REQUEST_QUEUE is shared across clients, thus every client/tab will have a copy
// It is very important to only process the queue from leader client otherwise requests will be duplicated.
if (!ActiveClientManager.isClientTheLeader()) {
return;
}

persistedRequestsQueueRunning = true;

// Ensure persistedRequests are read from storage before proceeding with the queue
const connectionId = Onyx.connect({
key: ONYXKEYS.NETWORK_REQUEST_QUEUE,
callback: () => {
Onyx.disconnect(connectionId);
process()
.finally(() => persistedRequestsQueueRunning = false);
},
});
}

// Flush the queue when the connection resumes
NetworkEvents.onConnectivityResumed(flush);

export {
// eslint-disable-next-line import/prefer-default-export
flush,
};
Loading

0 comments on commit 9193948

Please sign in to comment.