From b146d185b9999b3fa2047520ea612c897962dcfc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:57:46 +0200 Subject: [PATCH 01/20] Migrate Network --- .../Network/{MainQueue.js => MainQueue.ts} | 41 +++----- .../{NetworkStore.js => NetworkStore.ts} | 96 ++++++------------- ...{SequentialQueue.js => SequentialQueue.ts} | 38 +++----- ...anceParameters.js => enhanceParameters.ts} | 17 +--- src/libs/Network/{index.js => index.ts} | 19 ++-- 5 files changed, 68 insertions(+), 143 deletions(-) rename src/libs/Network/{MainQueue.js => MainQueue.ts} (71%) rename src/libs/Network/{NetworkStore.js => NetworkStore.ts} (61%) rename src/libs/Network/{SequentialQueue.js => SequentialQueue.ts} (91%) rename src/libs/Network/{enhanceParameters.js => enhanceParameters.ts} (72%) rename src/libs/Network/{index.js => index.ts} (77%) diff --git a/src/libs/Network/MainQueue.js b/src/libs/Network/MainQueue.ts similarity index 71% rename from src/libs/Network/MainQueue.js rename to src/libs/Network/MainQueue.ts index 5b5b928d3284..5f069e2d0ed4 100644 --- a/src/libs/Network/MainQueue.js +++ b/src/libs/Network/MainQueue.ts @@ -1,42 +1,28 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import * as NetworkStore from './NetworkStore'; import * as SequentialQueue from './SequentialQueue'; import * as Request from '../Request'; +import OnyxRequest from '../../types/onyx/Request'; // Queue for network requests so we don't lose actions done by the user while offline -let networkRequestQueue = []; +let networkRequestQueue: OnyxRequest[] = []; /** * Checks to see if a request can be made. - * - * @param {Object} request - * @param {String} request.type - * @param {String} request.command - * @param {Object} [request.data] - * @param {Boolean} request.data.forceNetworkRequest - * @return {Boolean} */ -function canMakeRequest(request) { +function canMakeRequest(request: OnyxRequest): boolean { // Some requests are always made even when we are in the process of authenticating (typically because they require no authToken e.g. Log, BeginSignIn) // However, if we are in the process of authenticating we always want to queue requests until we are no longer authenticating. - return request.data.forceNetworkRequest === true || (!NetworkStore.isAuthenticating() && !SequentialQueue.isRunning()); + return request.data?.forceNetworkRequest === true || (!NetworkStore.isAuthenticating() && !SequentialQueue.isRunning()); } -/** - * @param {Object} request - */ -function push(request) { +function push(request: OnyxRequest) { networkRequestQueue.push(request); } -/** - * @param {Object} request - */ -function replay(request) { +function replay(request: OnyxRequest) { push(request); - // eslint-disable-next-line no-use-before-define + // eslint-disable-next-line @typescript-eslint/no-use-before-define process(); } @@ -57,12 +43,12 @@ function process() { // - we are in the process of authenticating and the request is retryable (most are) // - the request does not have forceNetworkRequest === true (this will trigger it to process immediately) // - the request does not have shouldRetry === false (specified when we do not want to retry, defaults to true) - const requestsToProcessOnNextRun = []; + const requestsToProcessOnNextRun: OnyxRequest[] = []; - _.each(networkRequestQueue, (queuedRequest) => { + networkRequestQueue.forEach((queuedRequest) => { // Check if we can make this request at all and if we can't see if we should save it for the next run or chuck it into the ether if (!canMakeRequest(queuedRequest)) { - const shouldRetry = lodashGet(queuedRequest, 'data.shouldRetry'); + const shouldRetry = queuedRequest?.data?.shouldRetry; if (shouldRetry) { requestsToProcessOnNextRun.push(queuedRequest); } else { @@ -84,13 +70,10 @@ function process() { * Non-cancellable requests like Log would not be cleared */ function clear() { - networkRequestQueue = _.filter(networkRequestQueue, (request) => !request.data.canCancel); + networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel); } -/** - * @returns {Array} - */ -function getAll() { +function getAll(): OnyxRequest[] { return networkRequestQueue; } diff --git a/src/libs/Network/NetworkStore.js b/src/libs/Network/NetworkStore.ts similarity index 61% rename from src/libs/Network/NetworkStore.js rename to src/libs/Network/NetworkStore.ts index 5ab46a4d65fa..62d9119c2878 100644 --- a/src/libs/Network/NetworkStore.js +++ b/src/libs/Network/NetworkStore.ts @@ -1,32 +1,28 @@ -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; -import _ from 'underscore'; import ONYXKEYS from '../../ONYXKEYS'; +import Credentials from '../../types/onyx/Credentials'; -let credentials; -let authToken; -let supportAuthToken; -let currentUserEmail; +let credentials: Credentials; +let authToken: string | null = null; +let supportAuthToken: string | null = null; +let currentUserEmail: string | null = null; let offline = false; let authenticating = false; // Allow code that is outside of the network listen for when a reconnection happens so that it can execute any side-effects (like flushing the sequential network queue) -let reconnectCallback; +let reconnectCallback: () => void; function triggerReconnectCallback() { - if (!_.isFunction(reconnectCallback)) { + if (typeof reconnectCallback !== 'function') { return; } return reconnectCallback(); } -/** - * @param {Function} callbackFunction - */ -function onReconnection(callbackFunction) { +function onReconnection(callbackFunction: () => void) { reconnectCallback = callbackFunction; } -let resolveIsReadyPromise; +let resolveIsReadyPromise: (args?: unknown[]) => void; let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); @@ -36,7 +32,7 @@ let isReadyPromise = new Promise((resolve) => { * If the values are undefined we haven't read them yet. If they are null or have a value then we have and the network is "ready". */ function checkRequiredData() { - if (_.isUndefined(authToken) || _.isUndefined(credentials)) { + if (authToken === undefined || credentials === undefined) { return; } @@ -53,9 +49,9 @@ function resetHasReadRequiredDataFromStorage() { Onyx.connect({ key: ONYXKEYS.SESSION, callback: (val) => { - authToken = lodashGet(val, 'authToken', null); - supportAuthToken = lodashGet(val, 'supportAuthToken', null); - currentUserEmail = lodashGet(val, 'email', null); + authToken = val?.authToken ?? null; + supportAuthToken = val?.supportAuthToken ?? null; + currentUserEmail = val?.email ?? null; checkRequiredData(); }, }); @@ -63,7 +59,11 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.CREDENTIALS, callback: (val) => { - credentials = val || {}; + if (!val) { + return; + } + + credentials = val; checkRequiredData(); }, }); @@ -82,85 +82,51 @@ Onyx.connect({ triggerReconnectCallback(); } - offline = Boolean(network.shouldForceOffline) || network.isOffline; + offline = Boolean(network.shouldForceOffline) || !!network.isOffline; }, }); -/** - * @returns {Object} - */ -function getCredentials() { +function getCredentials(): Credentials { return credentials; } -/** - * @returns {Boolean} - */ -function isOffline() { +function isOffline(): boolean { return offline; } -/** - * @returns {String} - */ -function getAuthToken() { +function getAuthToken(): string | null { return authToken; } -/** - * @param {String} command - * @returns {[String]} - */ -function isSupportRequest(command) { - return _.contains(['OpenApp', 'ReconnectApp', 'OpenReport'], command); +function isSupportRequest(command: string): boolean { + return ['OpenApp', 'ReconnectApp', 'OpenReport'].includes(command); } -/** - * @returns {String} - */ -function getSupportAuthToken() { +function getSupportAuthToken(): string | null { return supportAuthToken; } -/** - * @param {String} newSupportAuthToken - */ -function setSupportAuthToken(newSupportAuthToken) { +function setSupportAuthToken(newSupportAuthToken: string) { supportAuthToken = newSupportAuthToken; } -/** - * @param {String} newAuthToken - */ -function setAuthToken(newAuthToken) { +function setAuthToken(newAuthToken: string) { authToken = newAuthToken; } -/** - * @returns {String} - */ -function getCurrentUserEmail() { +function getCurrentUserEmail(): string | null { return currentUserEmail; } -/** - * @returns {Promise} - */ -function hasReadRequiredDataFromStorage() { +function hasReadRequiredDataFromStorage(): Promise { return isReadyPromise; } -/** - * @returns {Boolean} - */ -function isAuthenticating() { +function isAuthenticating(): boolean { return authenticating; } -/** - * @param {Boolean} val - */ -function setIsAuthenticating(val) { +function setIsAuthenticating(val: boolean) { authenticating = val; } diff --git a/src/libs/Network/SequentialQueue.js b/src/libs/Network/SequentialQueue.ts similarity index 91% rename from src/libs/Network/SequentialQueue.js rename to src/libs/Network/SequentialQueue.ts index e53515fb5e87..f00abb6d797c 100644 --- a/src/libs/Network/SequentialQueue.js +++ b/src/libs/Network/SequentialQueue.ts @@ -1,4 +1,3 @@ -import _ from 'underscore'; import Onyx from 'react-native-onyx'; import * as PersistedRequests from '../actions/PersistedRequests'; import * as NetworkStore from './NetworkStore'; @@ -8,17 +7,18 @@ import * as Request from '../Request'; import * as RequestThrottle from '../RequestThrottle'; import CONST from '../../CONST'; import * as QueuedOnyxUpdates from '../actions/QueuedOnyxUpdates'; +import OnyxRequest from '../../types/onyx/Request'; -let resolveIsReadyPromise; +let resolveIsReadyPromise: ((args?: unknown[]) => void) | undefined; let isReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); // Resolve the isReadyPromise immediately so that the queue starts working as soon as the page loads -resolveIsReadyPromise(); +resolveIsReadyPromise?.(); let isSequentialQueueRunning = false; -let currentRequest = null; +let currentRequest: Promise | null = null; let isQueuePaused = false; /** @@ -52,16 +52,15 @@ function flushOnyxUpdatesQueue() { * is successfully returned. The first time a request fails we set a random, small, initial wait time. After waiting, we retry the request. If there are subsequent failures the request wait * time is doubled creating an exponential back off in the frequency of requests hitting the server. Since the initial wait time is random and it increases exponentially, the load of * requests to our backend is evenly distributed and it gradually decreases with time, which helps the servers catch up. - * @returns {Promise} */ -function process() { +function process(): Promise { // When the queue is paused, return early. This prevents any new requests from happening. The queue will be flushed again when the queue is unpaused. if (isQueuePaused) { return Promise.resolve(); } const persistedRequests = PersistedRequests.getAll(); - if (_.isEmpty(persistedRequests) || NetworkStore.isOffline()) { + if (persistedRequests.length === 0 || NetworkStore.isOffline()) { return Promise.resolve(); } const requestToProcess = persistedRequests[0]; @@ -71,7 +70,7 @@ function process() { .then((response) => { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. - if (response.shouldPauseQueue) { + if (response?.shouldPauseQueue) { pause(); } PersistedRequests.remove(requestToProcess); @@ -88,6 +87,7 @@ function process() { } return RequestThrottle.sleep().then(process); }); + return currentRequest; } @@ -97,7 +97,7 @@ function flush() { return; } - if (isSequentialQueueRunning || _.isEmpty(PersistedRequests.getAll())) { + if (isSequentialQueueRunning || PersistedRequests.getAll().length === 0) { return; } @@ -121,7 +121,7 @@ function flush() { Onyx.disconnect(connectionID); process().finally(() => { isSequentialQueueRunning = false; - resolveIsReadyPromise(); + resolveIsReadyPromise?.(); currentRequest = null; flushOnyxUpdatesQueue(); }); @@ -144,20 +144,14 @@ function unpause() { flush(); } -/** - * @returns {Boolean} - */ -function isRunning() { +function isRunning(): boolean { return isSequentialQueueRunning; } // Flush the queue when the connection resumes NetworkStore.onReconnection(flush); -/** - * @param {Object} request - */ -function push(request) { +function push(request: OnyxRequest) { // Add request to Persisted Requests so that it can be retried if it fails PersistedRequests.save([request]); @@ -175,10 +169,7 @@ function push(request) { flush(); } -/** - * @returns {Promise} - */ -function getCurrentRequest() { +function getCurrentRequest(): OnyxRequest | Promise { if (currentRequest === null) { return Promise.resolve(); } @@ -187,9 +178,8 @@ function getCurrentRequest() { /** * Returns a promise that resolves when the sequential queue is done processing all persisted write requests. - * @returns {Promise} */ -function waitForIdle() { +function waitForIdle(): Promise { return isReadyPromise; } diff --git a/src/libs/Network/enhanceParameters.js b/src/libs/Network/enhanceParameters.ts similarity index 72% rename from src/libs/Network/enhanceParameters.js rename to src/libs/Network/enhanceParameters.ts index 778be881cb98..72706fed6081 100644 --- a/src/libs/Network/enhanceParameters.js +++ b/src/libs/Network/enhanceParameters.ts @@ -1,27 +1,18 @@ -import lodashGet from 'lodash/get'; -import _ from 'underscore'; import CONFIG from '../../CONFIG'; import getPlatform from '../getPlatform'; import * as NetworkStore from './NetworkStore'; /** * Does this command require an authToken? - * - * @param {String} command - * @return {Boolean} */ -function isAuthTokenRequired(command) { - return !_.contains(['Log', 'Authenticate', 'BeginSignIn', 'SetPassword'], command); +function isAuthTokenRequired(command: string): boolean { + return !['Log', 'Authenticate', 'BeginSignIn', 'SetPassword'].includes(command); } /** * Adds default values to our request data - * - * @param {String} command - * @param {Object} parameters - * @returns {Object} */ -export default function enhanceParameters(command, parameters) { +export default function enhanceParameters(command: string, parameters: Record): Record { const finalParameters = {...parameters}; if (isAuthTokenRequired(command)) { @@ -44,7 +35,7 @@ export default function enhanceParameters(command, parameters) { finalParameters.api_setCookie = false; // Include current user's email in every request and the server logs - finalParameters.email = lodashGet(parameters, 'email', NetworkStore.getCurrentUserEmail()); + finalParameters.email = parameters?.email ?? NetworkStore.getCurrentUserEmail(); return finalParameters; } diff --git a/src/libs/Network/index.js b/src/libs/Network/index.ts similarity index 77% rename from src/libs/Network/index.js rename to src/libs/Network/index.ts index 2f5dc9460e60..4ab4bdd71085 100644 --- a/src/libs/Network/index.js +++ b/src/libs/Network/index.ts @@ -1,9 +1,10 @@ -import lodashGet from 'lodash/get'; import * as ActiveClientManager from '../ActiveClientManager'; import CONST from '../../CONST'; import * as MainQueue from './MainQueue'; import * as SequentialQueue from './SequentialQueue'; import pkg from '../../../package.json'; +import {Request} from '../../types/onyx'; +import Response from '../../types/onyx/Response'; // We must wait until the ActiveClientManager is ready so that we ensure only the "leader" tab processes any persisted requests ActiveClientManager.isReady().then(() => { @@ -15,16 +16,10 @@ ActiveClientManager.isReady().then(() => { /** * Perform a queued post request - * - * @param {String} command - * @param {*} [data] - * @param {String} [type] - * @param {Boolean} [shouldUseSecure] - Whether we should use the secure API - * @returns {Promise} */ -function post(command, data = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = false) { +function post(command: string, data: Record = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = false): Promise { return new Promise((resolve, reject) => { - const request = { + const request: Request = { command, data, type, @@ -35,8 +30,8 @@ function post(command, data = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSec // (e.g. any requests currently happening when the user logs out are cancelled) request.data = { ...data, - shouldRetry: lodashGet(data, 'shouldRetry', true), - canCancel: lodashGet(data, 'canCancel', true), + shouldRetry: data?.shouldRetry ?? true, + canCancel: data?.canCancel ?? true, appversion: pkg.version, }; @@ -50,7 +45,7 @@ function post(command, data = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSec // This check is mainly used to prevent API commands from triggering calls to MainQueue.process() from inside the context of a previous // call to MainQueue.process() e.g. calling a Log command without this would cause the requests in mainQueue to double process // since we call Log inside MainQueue.process(). - const shouldProcessImmediately = lodashGet(request, 'data.shouldProcessImmediately', true); + const shouldProcessImmediately = request?.data.shouldProcessImmediately ?? true; if (!shouldProcessImmediately) { return; } From 7b1f49282d1e081d2ce148ce5bcfd7fdb84e6244 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:58:23 +0200 Subject: [PATCH 02/20] Migrate Middlewares --- .../Middleware/{Logging.js => Logging.ts} | 52 +++---- src/libs/Middleware/Reauthentication.js | 133 ------------------ src/libs/Middleware/Reauthentication.ts | 109 ++++++++++++++ ...heckConnection.js => RecheckConnection.ts} | 13 +- ...esponseInOnyx.js => SaveResponseInOnyx.ts} | 20 +-- src/libs/Middleware/{index.js => index.ts} | 0 src/libs/Middleware/types.ts | 6 + 7 files changed, 146 insertions(+), 187 deletions(-) rename src/libs/Middleware/{Logging.js => Logging.ts} (82%) delete mode 100644 src/libs/Middleware/Reauthentication.js create mode 100644 src/libs/Middleware/Reauthentication.ts rename src/libs/Middleware/{RecheckConnection.js => RecheckConnection.ts} (83%) rename src/libs/Middleware/{SaveResponseInOnyx.js => SaveResponseInOnyx.ts} (78%) rename src/libs/Middleware/{index.js => index.ts} (100%) create mode 100644 src/libs/Middleware/types.ts diff --git a/src/libs/Middleware/Logging.js b/src/libs/Middleware/Logging.ts similarity index 82% rename from src/libs/Middleware/Logging.js rename to src/libs/Middleware/Logging.ts index fdc9f0083abb..c9ec0cc0d44e 100644 --- a/src/libs/Middleware/Logging.js +++ b/src/libs/Middleware/Logging.ts @@ -1,30 +1,26 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Log from '../Log'; import CONST from '../../CONST'; +import Request from '../../types/onyx/Request'; +import Response from '../../types/onyx/Response'; +import Middleware from './types'; -/** - * @param {String} message - * @param {Object} request - * @param {Object} [response] - */ -function logRequestDetails(message, request, response = {}) { +function logRequestDetails(message: string, request: Request, response?: Response | void) { // Don't log about log or else we'd cause an infinite loop if (request.command === 'Log') { return; } - const logParams = { + const logParams: Record = { command: request.command, shouldUseSecure: request.shouldUseSecure, }; - const returnValueList = lodashGet(request, 'data.returnValueList'); + const returnValueList = request?.data?.returnValueList; if (returnValueList) { logParams.returnValueList = returnValueList; } - const nvpNames = lodashGet(request, 'data.nvpNames'); + const nvpNames = request?.data?.nvpNames; if (nvpNames) { logParams.nvpNames = nvpNames; } @@ -37,22 +33,15 @@ function logRequestDetails(message, request, response = {}) { Log.info(message, false, logParams); } -/** - * Logging middleware - * - * @param {Promise} response - * @param {Object} request - * @returns {Promise} - */ -function Logging(response, request) { +const Logging: Middleware = (response, request) => { logRequestDetails('Making API request', request); return response - .then((data) => { + ?.then((data) => { logRequestDetails('Finished API request', request, data); return data; }) .catch((error) => { - const logParams = { + const logParams: Record = { message: error.message, status: error.status, title: error.title, @@ -73,21 +62,18 @@ function Logging(response, request) { // incorrect url, bad cors headers returned by the server, DNS lookup failure etc. Log.hmmm('[Network] API request error: Failed to fetch', logParams); } else if ( - _.contains( - [ - CONST.ERROR.IOS_NETWORK_CONNECTION_LOST, - CONST.ERROR.NETWORK_REQUEST_FAILED, - CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_RUSSIAN, - CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_SWEDISH, - CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_SPANISH, - ], - error.message, - ) + [ + CONST.ERROR.IOS_NETWORK_CONNECTION_LOST, + CONST.ERROR.NETWORK_REQUEST_FAILED, + CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_RUSSIAN, + CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_SWEDISH, + CONST.ERROR.IOS_NETWORK_CONNECTION_LOST_SPANISH, + ].includes(error.message) ) { // These errors seem to happen for native devices with interrupted connections. Often we will see logs about Pusher disconnecting together with these. // This type of error may also indicate a problem with SSL certs. Log.hmmm('[Network] API request error: Connection interruption likely', logParams); - } else if (_.contains([CONST.ERROR.FIREFOX_DOCUMENT_LOAD_ABORTED, CONST.ERROR.SAFARI_DOCUMENT_LOAD_ABORTED], error.message)) { + } else if ([CONST.ERROR.FIREFOX_DOCUMENT_LOAD_ABORTED, CONST.ERROR.SAFARI_DOCUMENT_LOAD_ABORTED].includes(error.message)) { // This message can be observed page load is interrupted (closed or navigated away). Log.hmmm('[Network] API request error: User likely navigated away from or closed browser', logParams); } else if (error.message === CONST.ERROR.IOS_LOAD_FAILED) { @@ -123,6 +109,6 @@ function Logging(response, request) { // Re-throw this error so the next handler can manage it throw error; }); -} +}; export default Logging; diff --git a/src/libs/Middleware/Reauthentication.js b/src/libs/Middleware/Reauthentication.js deleted file mode 100644 index dfe4e1b7fda8..000000000000 --- a/src/libs/Middleware/Reauthentication.js +++ /dev/null @@ -1,133 +0,0 @@ -import lodashGet from 'lodash/get'; -import CONST from '../../CONST'; -import * as NetworkStore from '../Network/NetworkStore'; -import * as MainQueue from '../Network/MainQueue'; -import * as Authentication from '../Authentication'; -import * as Request from '../Request'; -import Log from '../Log'; -import NetworkConnection from '../NetworkConnection'; - -// We store a reference to the active authentication request so that we are only ever making one request to authenticate at a time. -let isAuthenticating = null; - -/** - * @param {String} commandName - * @returns {Promise} - */ -function reauthenticate(commandName) { - if (isAuthenticating) { - return isAuthenticating; - } - - isAuthenticating = Authentication.reauthenticate(commandName) - .then((response) => { - isAuthenticating = null; - return response; - }) - .catch((error) => { - isAuthenticating = null; - throw error; - }); - - return isAuthenticating; -} - -/** - * Reauthentication middleware - * - * @param {Promise} response - * @param {Object} request - * @param {Boolean} isFromSequentialQueue - * @returns {Promise} - */ -function Reauthentication(response, request, isFromSequentialQueue) { - return response - .then((data) => { - // If there is no data for some reason then we cannot reauthenticate - if (!data) { - Log.hmmm('Undefined data in Reauthentication'); - return; - } - - if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { - if (NetworkStore.isOffline()) { - // If we are offline and somehow handling this response we do not want to reauthenticate - throw new Error('Unable to reauthenticate because we are offline'); - } - - // There are some API requests that should not be retried when there is an auth failure like - // creating and deleting logins. In those cases, they should handle the original response instead - // of the new response created by handleExpiredAuthToken. - const shouldRetry = lodashGet(request, 'data.shouldRetry'); - const apiRequestType = lodashGet(request, 'data.apiRequestType'); - - // For the SignInWithShortLivedAuthToken command, if the short token expires, the server returns a 407 error, - // and credentials are still empty at this time, which causes reauthenticate to throw an error (requireParameters), - // and the subsequent SaveResponseInOnyx also cannot be executed, so we need this parameter to skip the reauthentication logic. - const skipReauthentication = lodashGet(request, 'data.skipReauthentication'); - if ((!shouldRetry && !apiRequestType) || skipReauthentication) { - if (isFromSequentialQueue) { - return data; - } - - if (request.resolve) { - request.resolve(data); - } - return data; - } - - // We are already authenticating and using the DeprecatedAPI so we will replay the request - if (!apiRequestType && NetworkStore.isAuthenticating()) { - MainQueue.replay(request); - return data; - } - - return reauthenticate(request.commandName) - .then((authenticateResponse) => { - if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { - return Request.processWithMiddleware(request, isFromSequentialQueue); - } - - if (apiRequestType === CONST.API_REQUEST_TYPE.READ) { - NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken'); - return Promise.resolve(); - } - - MainQueue.replay(request); - return authenticateResponse; - }) - .catch(() => { - if (isFromSequentialQueue || apiRequestType) { - throw new Error('Failed to reauthenticate'); - } - - // If we make it here, then our reauthenticate request could not be made due to a networking issue. The original request can be retried safely. - MainQueue.replay(request); - }); - } - - if (isFromSequentialQueue) { - return data; - } - - if (request.resolve) { - request.resolve(data); - } - - // Return response data so we can chain the response with the following middlewares. - return data; - }) - .catch((error) => { - // If the request is on the sequential queue, re-throw the error so we can decide to retry or not - if (isFromSequentialQueue) { - throw error; - } - - // If we have caught a networking error from a DeprecatedAPI request, resolve it as unable to retry, otherwise the request will never resolve or reject. - if (request.resolve) { - request.resolve({jsonCode: CONST.JSON_CODE.UNABLE_TO_RETRY}); - } - }); -} - -export default Reauthentication; diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts new file mode 100644 index 000000000000..826fab2f6335 --- /dev/null +++ b/src/libs/Middleware/Reauthentication.ts @@ -0,0 +1,109 @@ +import CONST from '../../CONST'; +import * as NetworkStore from '../Network/NetworkStore'; +import * as MainQueue from '../Network/MainQueue'; +import * as Authentication from '../Authentication'; +import * as Request from '../Request'; +import Log from '../Log'; +import NetworkConnection from '../NetworkConnection'; +import Middleware from './types'; + +// We store a reference to the active authentication request so that we are only ever making one request to authenticate at a time. +let isAuthenticating: Promise | null = null; + +function reauthenticate(commandName?: string): Promise { + if (isAuthenticating) { + return isAuthenticating; + } + + isAuthenticating = Authentication.reauthenticate(commandName) + .then((response) => { + isAuthenticating = null; + return response; + }) + .catch((error) => { + isAuthenticating = null; + throw error; + }); + + return isAuthenticating; +} + +const Reauthentication: Middleware = (response, request, isFromSequentialQueue) => + response?.then((data) => { + // If there is no data for some reason then we cannot reauthenticate + if (!data) { + Log.hmmm('Undefined data in Reauthentication'); + return; + } + + if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { + if (NetworkStore.isOffline()) { + // If we are offline and somehow handling this response we do not want to reauthenticate + throw new Error('Unable to reauthenticate because we are offline'); + } + + // There are some API requests that should not be retried when there is an auth failure like + // creating and deleting logins. In those cases, they should handle the original response instead + // of the new response created by handleExpiredAuthToken. + const shouldRetry = request?.data?.shouldRetry; + const apiRequestType = request?.data?.apiRequestType; + + // For the SignInWithShortLivedAuthToken command, if the short token expires, the server returns a 407 error, + // and credentials are still empty at this time, which causes reauthenticate to throw an error (requireParameters), + // and the subsequent SaveResponseInOnyx also cannot be executed, so we need this parameter to skip the reauthentication logic. + const skipReauthentication = request?.data?.skipReauthentication; + if ((!shouldRetry && !apiRequestType) || skipReauthentication) { + if (isFromSequentialQueue) { + return data; + } + + if (request.resolve) { + request.resolve(data); + } + + return data; + } + + // We are already authenticating and using the DeprecatedAPI so we will replay the request + if (!apiRequestType && NetworkStore.isAuthenticating()) { + MainQueue.replay(request); + return data; + } + + return reauthenticate(request?.commandName) + .then((authenticateResponse) => { + if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { + return Request.processWithMiddleware(request, isFromSequentialQueue); + } + + if (apiRequestType === CONST.API_REQUEST_TYPE.READ) { + NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken'); + return Promise.resolve(); + } + + MainQueue.replay(request); + return authenticateResponse; + }) + .catch(() => { + if (isFromSequentialQueue || apiRequestType) { + throw new Error('Failed to reauthenticate'); + } + + // If we make it here, then our reauthenticate request could not be made due to a networking issue. The original request can be retried safely. + MainQueue.replay(request); + }); + } + + if (isFromSequentialQueue) { + return data; + } + + if (request.resolve) { + request.resolve(data); + } + + // Return response data so we can chain the response with the following middlewares. + return data; + }); + +export default Reauthentication; diff --git a/src/libs/Middleware/RecheckConnection.js b/src/libs/Middleware/RecheckConnection.ts similarity index 83% rename from src/libs/Middleware/RecheckConnection.js rename to src/libs/Middleware/RecheckConnection.ts index 58f5cfa601c8..5a685d66fd02 100644 --- a/src/libs/Middleware/RecheckConnection.js +++ b/src/libs/Middleware/RecheckConnection.ts @@ -1,20 +1,17 @@ import CONST from '../../CONST'; import NetworkConnection from '../NetworkConnection'; +import Middleware from './types'; /** - * @returns {Function} cancel timer + * @returns cancel timer */ -function startRecheckTimeoutTimer() { +function startRecheckTimeoutTimer(): () => void { // If request is still in processing after this time, we might be offline const timerID = setTimeout(NetworkConnection.recheckNetworkConnection, CONST.NETWORK.MAX_PENDING_TIME_MS); return () => clearTimeout(timerID); } -/** - * @param {Promise} response - * @returns {Promise} - */ -function RecheckConnection(response) { +const RecheckConnection: Middleware = (response) => { // When the request goes past a certain amount of time we trigger a re-check of the connection const cancelRequestTimeoutTimer = startRecheckTimeoutTimer(); return response @@ -27,6 +24,6 @@ function RecheckConnection(response) { throw error; }) .finally(cancelRequestTimeoutTimer); -} +}; export default RecheckConnection; diff --git a/src/libs/Middleware/SaveResponseInOnyx.js b/src/libs/Middleware/SaveResponseInOnyx.ts similarity index 78% rename from src/libs/Middleware/SaveResponseInOnyx.js rename to src/libs/Middleware/SaveResponseInOnyx.ts index 8cb66c0c10d0..e0d8de0c0990 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.js +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -1,20 +1,15 @@ -import _ from 'underscore'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import * as MemoryOnlyKeys from '../actions/MemoryOnlyKeys/MemoryOnlyKeys'; import * as OnyxUpdates from '../actions/OnyxUpdates'; +import Middleware from './types'; // If we're executing any of these requests, we don't need to trigger our OnyxUpdates flow to update the current data even if our current value is out of // date because all these requests are updating the app to the most current state. const requestsToIgnoreLastUpdateID = ['OpenApp', 'ReconnectApp', 'GetMissingOnyxMessages']; -/** - * @param {Promise} requestResponse - * @param {Object} request - * @returns {Promise} - */ -function SaveResponseInOnyx(requestResponse, request) { - return requestResponse.then((response) => { +const SaveResponseInOnyx: Middleware = (requestResponse, request) => + requestResponse.then((response) => { // Make sure we have response data (i.e. response isn't a promise being passed down to us by a failed retry request and response undefined) if (!response) { return; @@ -28,7 +23,7 @@ function SaveResponseInOnyx(requestResponse, request) { } // If there is an OnyxUpdate for using memory only keys, enable them - _.find(onyxUpdates, ({key, value}) => { + onyxUpdates?.find(({key, value}) => { if (key !== ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS || !value) { return false; } @@ -39,13 +34,13 @@ function SaveResponseInOnyx(requestResponse, request) { const responseToApply = { type: CONST.ONYX_UPDATE_TYPES.HTTPS, - lastUpdateID: Number(response.lastUpdateID || 0), - previousUpdateID: Number(response.previousUpdateID || 0), + lastUpdateID: Number(response.lastUpdateID ?? 0), + previousUpdateID: Number(response.previousUpdateID ?? 0), request, response, }; - if (_.includes(requestsToIgnoreLastUpdateID, request.command) || !OnyxUpdates.doesClientNeedToBeUpdated(Number(response.previousUpdateID || 0))) { + if (requestsToIgnoreLastUpdateID.includes(request.command) || !OnyxUpdates.doesClientNeedToBeUpdated(Number(response.previousUpdateID ?? 0))) { return OnyxUpdates.apply(responseToApply); } @@ -58,6 +53,5 @@ function SaveResponseInOnyx(requestResponse, request) { shouldPauseQueue: true, }); }); -} export default SaveResponseInOnyx; diff --git a/src/libs/Middleware/index.js b/src/libs/Middleware/index.ts similarity index 100% rename from src/libs/Middleware/index.js rename to src/libs/Middleware/index.ts diff --git a/src/libs/Middleware/types.ts b/src/libs/Middleware/types.ts new file mode 100644 index 000000000000..ece210ffe2af --- /dev/null +++ b/src/libs/Middleware/types.ts @@ -0,0 +1,6 @@ +import Request from '../../types/onyx/Request'; +import Response from '../../types/onyx/Response'; + +type Middleware = (response: Promise, request: Request, isFromSequentialQueue: boolean) => Promise; + +export default Middleware; From 60429894d1ffa6e5f07419058d2d0ccb5847851a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:58:39 +0200 Subject: [PATCH 03/20] Migrate Authentication --- .../{Authentication.js => Authentication.ts} | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) rename src/libs/{Authentication.js => Authentication.ts} (85%) diff --git a/src/libs/Authentication.js b/src/libs/Authentication.ts similarity index 85% rename from src/libs/Authentication.js rename to src/libs/Authentication.ts index 9f1967ecf0d8..3f4b84e21831 100644 --- a/src/libs/Authentication.js +++ b/src/libs/Authentication.ts @@ -7,20 +7,21 @@ import redirectToSignIn from './actions/SignInRedirect'; import CONST from '../CONST'; import Log from './Log'; import * as ErrorUtils from './ErrorUtils'; +import Response from '../types/onyx/Response'; +import Credentials from '../types/onyx/Credentials'; -/** - * @param {Object} parameters - * @param {Boolean} [parameters.useExpensifyLogin] - * @param {String} parameters.partnerName - * @param {String} parameters.partnerPassword - * @param {String} parameters.partnerUserID - * @param {String} parameters.partnerUserSecret - * @param {String} [parameters.twoFactorAuthCode] - * @param {String} [parameters.email] - * @param {String} [parameters.authToken] - * @returns {Promise} - */ -function Authenticate(parameters) { +type Parameters = { + useExpensifyLogin?: boolean; + partnerName: string; + partnerPassword: string; + partnerUserID: string; + partnerUserSecret: string; + twoFactorAuthCode?: string; + email?: string; + authToken?: string; +}; + +function Authenticate(parameters: Parameters): Promise { const commandName = 'Authenticate'; requireParameters(['partnerName', 'partnerPassword', 'partnerUserID', 'partnerUserSecret'], parameters, commandName); @@ -48,15 +49,13 @@ function Authenticate(parameters) { /** * Reauthenticate using the stored credentials and redirect to the sign in page if unable to do so. - * - * @param {String} [command] command name for logging purposes - * @returns {Promise} + * @param [command] command name for logging purposes */ -function reauthenticate(command = '') { +function reauthenticate(command = ''): Promise { // Prevent any more requests from being processed while authentication happens NetworkStore.setIsAuthenticating(true); - const credentials = NetworkStore.getCredentials(); + const credentials: Credentials = NetworkStore.getCredentials() as any; return Authenticate({ useExpensifyLogin: false, partnerName: CONFIG.EXPENSIFY.PARTNER_NAME, @@ -76,7 +75,7 @@ function reauthenticate(command = '') { // If authentication fails and we are online then log the user out if (response.jsonCode !== 200) { - const errorMessage = ErrorUtils.getAuthenticateErrorMessage(response); + const errorMessage = ErrorUtils.getAuthenticateErrorMessage(response as any); NetworkStore.setIsAuthenticating(false); Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', { command, From d6769df967aa7b7651786c0e8f81a8815f1ff6c7 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:58:57 +0200 Subject: [PATCH 04/20] Request lib changes --- src/libs/Request.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libs/Request.ts b/src/libs/Request.ts index 459deaf89e1e..034b4d0cc876 100644 --- a/src/libs/Request.ts +++ b/src/libs/Request.ts @@ -2,24 +2,25 @@ import HttpUtils from './HttpUtils'; import enhanceParameters from './Network/enhanceParameters'; import * as NetworkStore from './Network/NetworkStore'; import Request from '../types/onyx/Request'; - -type Middleware = (response: unknown, request: Request, isFromSequentialQueue: boolean) => Promise; +import Response from '../types/onyx/Response'; +import Middleware from './Middleware/types'; let middlewares: Middleware[] = []; -function makeXHR(request: Request): Promise { +function makeXHR(request: Request): Promise { const finalParameters = enhanceParameters(request.command, request?.data ?? {}); - return NetworkStore.hasReadRequiredDataFromStorage().then(() => { + return NetworkStore.hasReadRequiredDataFromStorage().then((): Promise => { // If we're using the Supportal token and this is not a Supportal request // let's just return a promise that will resolve itself. if (NetworkStore.getSupportAuthToken() && !NetworkStore.isSupportRequest(request.command)) { return new Promise((resolve) => resolve()); } - return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure); + + return HttpUtils.xhr(request.command, finalParameters, request.type, request.shouldUseSecure) as Promise; }); } -function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise { +function processWithMiddleware(request: Request, isFromSequentialQueue = false): Promise { return middlewares.reduce((last, middleware) => middleware(last, request, isFromSequentialQueue), makeXHR(request)); } From e8977efaf4c0090eb157c585e1aea541d5bda536 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:59:10 +0200 Subject: [PATCH 05/20] Migrate API --- src/libs/{API.js => API.ts} | 83 ++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 33 deletions(-) rename src/libs/{API.js => API.ts} (63%) diff --git a/src/libs/API.js b/src/libs/API.ts similarity index 63% rename from src/libs/API.js rename to src/libs/API.ts index 491503f07381..7678098f3f3b 100644 --- a/src/libs/API.js +++ b/src/libs/API.ts @@ -1,5 +1,5 @@ -import _ from 'underscore'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxUpdate} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import Log from './Log'; import * as Request from './Request'; import * as Middleware from './Middleware'; @@ -7,6 +7,8 @@ import * as SequentialQueue from './Network/SequentialQueue'; import pkg from '../../package.json'; import CONST from '../CONST'; import * as Pusher from './Pusher/pusher'; +import OnyxRequest from '../types/onyx/Request'; +import Response from '../types/onyx/Response'; // Setup API middlewares. Each request made will pass through a series of middleware functions that will get called in sequence (each one passing the result of the previous to the next). // Note: The ordering here is intentional as we want to Log, Recheck Connection, Reauthenticate, and Save the Response in Onyx. Errors thrown in one middleware will bubble to the next. @@ -25,25 +27,34 @@ Request.use(Middleware.Reauthentication); // middlewares after this, because the SequentialQueue depends on the result of this middleware to pause the queue (if needed) to bring the app to an up-to-date state. Request.use(Middleware.SaveResponseInOnyx); +type OnyxData = { + optimisticData?: OnyxUpdate[]; + successData?: OnyxUpdate[]; + failureData?: OnyxUpdate[]; +}; + +type ApiRequestType = ValueOf; + /** * All calls to API.write() will be persisted to disk as JSON with the params, successData, and failureData. * This is so that if the network is unavailable or the app is closed, we can send the WRITE request later. * - * @param {String} command - Name of API command to call. - * @param {Object} apiCommandParameters - Parameters to send to the API. - * @param {Object} onyxData - Object containing errors, loading states, and optimistic UI data that will be merged + * @param command - Name of API command to call. + * @param apiCommandParameters - Parameters to send to the API. + * @param onyxData - Object containing errors, loading states, and optimistic UI data that will be merged * into Onyx before and after a request is made. Each nested object will be formatted in * the same way as an API response. - * @param {Object} [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. - * @param {Object} [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. - * @param {Object} [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. + * @param [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. + * @param [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. + * @param [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. */ -function write(command, apiCommandParameters = {}, onyxData = {}) { +function write(command: string, apiCommandParameters: Record = {}, onyxData: OnyxData = {}) { Log.info('Called API write', false, {command, ...apiCommandParameters}); + const {optimisticData, ...onyxDataWithoutOptimisticData} = onyxData; // Optimistically update Onyx - if (onyxData.optimisticData) { - Onyx.update(onyxData.optimisticData); + if (optimisticData) { + Onyx.update(optimisticData); } // Assemble the data we'll send to the API @@ -58,7 +69,7 @@ function write(command, apiCommandParameters = {}, onyxData = {}) { }; // Assemble all the request data we'll be storing in the queue - const request = { + const request: OnyxRequest = { command, data: { ...data, @@ -67,7 +78,7 @@ function write(command, apiCommandParameters = {}, onyxData = {}) { shouldRetry: true, canCancel: true, }, - ..._.omit(onyxData, 'optimisticData'), + ...onyxDataWithoutOptimisticData, }; // Write commands can be saved and retried, so push it to the SequentialQueue @@ -82,24 +93,30 @@ function write(command, apiCommandParameters = {}, onyxData = {}) { * Using this method is discouraged and will throw an ESLint error. Use it sparingly and only when all other alternatives have been exhausted. * It is best to discuss it in Slack anytime you are tempted to use this method. * - * @param {String} command - Name of API command to call. - * @param {Object} apiCommandParameters - Parameters to send to the API. - * @param {Object} onyxData - Object containing errors, loading states, and optimistic UI data that will be merged + * @param command - Name of API command to call. + * @param apiCommandParameters - Parameters to send to the API. + * @param onyxData - Object containing errors, loading states, and optimistic UI data that will be merged * into Onyx before and after a request is made. Each nested object will be formatted in * the same way as an API response. - * @param {Object} [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. - * @param {Object} [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. - * @param {Object} [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. - * @param {String} [apiRequestType] - Can be either 'read', 'write', or 'makeRequestWithSideEffects'. We use this to either return the chained + * @param [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. + * @param [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. + * @param [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. + * @param [apiRequestType] - Can be either 'read', 'write', or 'makeRequestWithSideEffects'. We use this to either return the chained * response back to the caller or to trigger reconnection callbacks when re-authentication is required. - * @returns {Promise} + * @returns */ -function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData = {}, apiRequestType = CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { +function makeRequestWithSideEffects( + command: string, + apiCommandParameters = {}, + onyxData: OnyxData = {}, + apiRequestType: ApiRequestType = CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS, +): Promise { Log.info('Called API makeRequestWithSideEffects', false, {command, ...apiCommandParameters}); + const {optimisticData, ...onyxDataWithoutOptimisticData} = onyxData; // Optimistically update Onyx - if (onyxData.optimisticData) { - Onyx.update(onyxData.optimisticData); + if (optimisticData) { + Onyx.update(optimisticData); } // Assemble the data we'll send to the API @@ -110,10 +127,10 @@ function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData }; // Assemble all the request data we'll be storing - const request = { + const request: OnyxRequest = { command, data, - ..._.omit(onyxData, 'optimisticData'), + ...onyxDataWithoutOptimisticData, }; // Return a promise containing the response from HTTPS @@ -123,16 +140,16 @@ function makeRequestWithSideEffects(command, apiCommandParameters = {}, onyxData /** * Requests made with this method are not be persisted to disk. If there is no network connectivity, the request is ignored and discarded. * - * @param {String} command - Name of API command to call. - * @param {Object} apiCommandParameters - Parameters to send to the API. - * @param {Object} onyxData - Object containing errors, loading states, and optimistic UI data that will be merged + * @param command - Name of API command to call. + * @param apiCommandParameters - Parameters to send to the API. + * @param onyxData - Object containing errors, loading states, and optimistic UI data that will be merged * into Onyx before and after a request is made. Each nested object will be formatted in * the same way as an API response. - * @param {Object} [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. - * @param {Object} [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. - * @param {Object} [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. + * @param [onyxData.optimisticData] - Onyx instructions that will be passed to Onyx.update() before the request is made. + * @param [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. + * @param [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. */ -function read(command, apiCommandParameters, onyxData) { +function read(command: string, apiCommandParameters: Record, onyxData: OnyxData) { // Ensure all write requests on the sequential queue have finished responding before running read requests. // Responses from read requests can overwrite the optimistic data inserted by // write requests that use the same Onyx keys and haven't responded yet. From 1719af4afbbffb8f4ba06d049966f135beb20e40 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 22 Sep 2023 17:59:28 +0200 Subject: [PATCH 06/20] Changes in onyx types --- src/types/onyx/Credentials.ts | 7 +++++-- src/types/onyx/Request.ts | 5 +++++ src/types/onyx/Response.ts | 6 +++++- src/types/onyx/Session.ts | 2 ++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/types/onyx/Credentials.ts b/src/types/onyx/Credentials.ts index f6a9ce669ad0..6bc36079f363 100644 --- a/src/types/onyx/Credentials.ts +++ b/src/types/onyx/Credentials.ts @@ -7,9 +7,12 @@ type Credentials = { /** The validate code */ validateCode?: string; - autoGeneratedLogin?: string; - autoGeneratedPassword?: string; + autoGeneratedLogin: string; + autoGeneratedPassword: string; accountID?: number; + + partnerUserID: string; + partnerUserSecret: string; }; export default Credentials; diff --git a/src/types/onyx/Request.ts b/src/types/onyx/Request.ts index 94f14af0ddb3..46106c4685ee 100644 --- a/src/types/onyx/Request.ts +++ b/src/types/onyx/Request.ts @@ -1,12 +1,17 @@ import {OnyxUpdate} from 'react-native-onyx'; +import Response from './Response'; type Request = { command: string; + commandName?: string; data?: Record; type?: string; shouldUseSecure?: boolean; successData?: OnyxUpdate[]; failureData?: OnyxUpdate[]; + + resolve?: (value: Response) => void; + reject?: (value?: unknown) => void; }; export default Request; diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index c501034e971c..86a9b339a2a0 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -3,9 +3,13 @@ import {OnyxUpdate} from 'react-native-onyx'; type Response = { previousUpdateID?: number | string; lastUpdateID?: number | string; - jsonCode?: number; + jsonCode?: number | string; onyxData?: OnyxUpdate[]; requestID?: string; + shouldPauseQueue?: boolean; + + authToken: string; + encryptedAuthToken: string; }; export default Response; diff --git a/src/types/onyx/Session.ts b/src/types/onyx/Session.ts index 75cb4f4818ad..97840aab70c7 100644 --- a/src/types/onyx/Session.ts +++ b/src/types/onyx/Session.ts @@ -5,6 +5,8 @@ type Session = { /** Currently logged in user authToken */ authToken?: string; + supportAuthToken?: string; + /** Currently logged in user encrypted authToken */ encryptedAuthToken?: string; From e1369273d9052804f101fc0d037c8ee30cdab9cf Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Sep 2023 11:48:37 +0200 Subject: [PATCH 07/20] Fix lint and type errors --- src/libs/Authentication.ts | 4 ++-- src/libs/actions/Chronos.ts | 8 ++++---- src/types/onyx/Transaction.ts | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libs/Authentication.ts b/src/libs/Authentication.ts index 3f4b84e21831..47200199f95a 100644 --- a/src/libs/Authentication.ts +++ b/src/libs/Authentication.ts @@ -55,7 +55,7 @@ function reauthenticate(command = ''): Promise { // Prevent any more requests from being processed while authentication happens NetworkStore.setIsAuthenticating(true); - const credentials: Credentials = NetworkStore.getCredentials() as any; + const credentials: Credentials = NetworkStore.getCredentials(); return Authenticate({ useExpensifyLogin: false, partnerName: CONFIG.EXPENSIFY.PARTNER_NAME, @@ -75,7 +75,7 @@ function reauthenticate(command = ''): Promise { // If authentication fails and we are online then log the user out if (response.jsonCode !== 200) { - const errorMessage = ErrorUtils.getAuthenticateErrorMessage(response as any); + const errorMessage = ErrorUtils.getAuthenticateErrorMessage(response); NetworkStore.setIsAuthenticating(false); Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', { command, diff --git a/src/libs/actions/Chronos.ts b/src/libs/actions/Chronos.ts index 1b46a68a1afe..ce821e524722 100644 --- a/src/libs/actions/Chronos.ts +++ b/src/libs/actions/Chronos.ts @@ -1,11 +1,11 @@ -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxUpdate} from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import {ChronosOOOEvent} from '../../types/onyx/OriginalMessage'; const removeEvent = (reportID: string, reportActionID: string, eventID: string, events: ChronosOOOEvent[]) => { - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, @@ -20,7 +20,7 @@ const removeEvent = (reportID: string, reportActionID: string, eventID: string, }, ]; - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, @@ -32,7 +32,7 @@ const removeEvent = (reportID: string, reportActionID: string, eventID: string, }, ]; - const failureData = [ + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 19a5d6105e9c..e65e6dacc08c 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -7,6 +7,7 @@ type WaypointCollection = Record; type Comment = { comment?: string; waypoints?: WaypointCollection; + isLoading?: boolean; }; type GeometryType = 'LineString'; From 24108f86e6a15c5daa6b79dc96259c57ec544148 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 27 Sep 2023 11:59:20 +0200 Subject: [PATCH 08/20] Fix types after review --- src/libs/Authentication.ts | 19 ++-- src/libs/Middleware/Reauthentication.ts | 134 +++++++++++++----------- src/libs/Network/NetworkStore.ts | 4 +- src/types/onyx/Response.ts | 4 +- 4 files changed, 88 insertions(+), 73 deletions(-) diff --git a/src/libs/Authentication.ts b/src/libs/Authentication.ts index 47200199f95a..4f070614ef7e 100644 --- a/src/libs/Authentication.ts +++ b/src/libs/Authentication.ts @@ -8,14 +8,13 @@ import CONST from '../CONST'; import Log from './Log'; import * as ErrorUtils from './ErrorUtils'; import Response from '../types/onyx/Response'; -import Credentials from '../types/onyx/Credentials'; type Parameters = { useExpensifyLogin?: boolean; partnerName: string; partnerPassword: string; - partnerUserID: string; - partnerUserSecret: string; + partnerUserID?: string; + partnerUserSecret?: string; twoFactorAuthCode?: string; email?: string; authToken?: string; @@ -55,13 +54,13 @@ function reauthenticate(command = ''): Promise { // Prevent any more requests from being processed while authentication happens NetworkStore.setIsAuthenticating(true); - const credentials: Credentials = NetworkStore.getCredentials(); + const credentials = NetworkStore.getCredentials(); return Authenticate({ useExpensifyLogin: false, partnerName: CONFIG.EXPENSIFY.PARTNER_NAME, partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD, - partnerUserID: credentials.autoGeneratedLogin, - partnerUserSecret: credentials.autoGeneratedPassword, + partnerUserID: credentials?.autoGeneratedLogin, + partnerUserSecret: credentials?.autoGeneratedPassword, }).then((response) => { if (response.jsonCode === CONST.JSON_CODE.UNABLE_TO_RETRY) { // If authentication fails, then the network can be unpaused @@ -86,12 +85,16 @@ function reauthenticate(command = ''): Promise { } // Update authToken in Onyx and in our local variables so that API requests will use the new authToken - updateSessionAuthTokens(response.authToken, response.encryptedAuthToken); + if (response.authToken && response.encryptedAuthToken) { + updateSessionAuthTokens(response.authToken, response.encryptedAuthToken); + } // Note: It is important to manually set the authToken that is in the store here since any requests that are hooked into // reauthenticate .then() will immediate post and use the local authToken. Onyx updates subscribers lately so it is not // enough to do the updateSessionAuthTokens() call above. - NetworkStore.setAuthToken(response.authToken); + if (response.authToken) { + NetworkStore.setAuthToken(response.authToken); + } // The authentication process is finished so the network can be unpaused to continue processing requests NetworkStore.setIsAuthenticating(false); diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts index 826fab2f6335..9d23ab415e56 100644 --- a/src/libs/Middleware/Reauthentication.ts +++ b/src/libs/Middleware/Reauthentication.ts @@ -29,81 +29,93 @@ function reauthenticate(commandName?: string): Promise { } const Reauthentication: Middleware = (response, request, isFromSequentialQueue) => - response?.then((data) => { - // If there is no data for some reason then we cannot reauthenticate - if (!data) { - Log.hmmm('Undefined data in Reauthentication'); - return; - } - - if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { - if (NetworkStore.isOffline()) { - // If we are offline and somehow handling this response we do not want to reauthenticate - throw new Error('Unable to reauthenticate because we are offline'); + response + ?.then((data) => { + // If there is no data for some reason then we cannot reauthenticate + if (!data) { + Log.hmmm('Undefined data in Reauthentication'); + return; } - // There are some API requests that should not be retried when there is an auth failure like - // creating and deleting logins. In those cases, they should handle the original response instead - // of the new response created by handleExpiredAuthToken. - const shouldRetry = request?.data?.shouldRetry; - const apiRequestType = request?.data?.apiRequestType; - - // For the SignInWithShortLivedAuthToken command, if the short token expires, the server returns a 407 error, - // and credentials are still empty at this time, which causes reauthenticate to throw an error (requireParameters), - // and the subsequent SaveResponseInOnyx also cannot be executed, so we need this parameter to skip the reauthentication logic. - const skipReauthentication = request?.data?.skipReauthentication; - if ((!shouldRetry && !apiRequestType) || skipReauthentication) { - if (isFromSequentialQueue) { + if (data.jsonCode === CONST.JSON_CODE.NOT_AUTHENTICATED) { + if (NetworkStore.isOffline()) { + // If we are offline and somehow handling this response we do not want to reauthenticate + throw new Error('Unable to reauthenticate because we are offline'); + } + + // There are some API requests that should not be retried when there is an auth failure like + // creating and deleting logins. In those cases, they should handle the original response instead + // of the new response created by handleExpiredAuthToken. + const shouldRetry = request?.data?.shouldRetry; + const apiRequestType = request?.data?.apiRequestType; + + // For the SignInWithShortLivedAuthToken command, if the short token expires, the server returns a 407 error, + // and credentials are still empty at this time, which causes reauthenticate to throw an error (requireParameters), + // and the subsequent SaveResponseInOnyx also cannot be executed, so we need this parameter to skip the reauthentication logic. + const skipReauthentication = request?.data?.skipReauthentication; + if ((!shouldRetry && !apiRequestType) || skipReauthentication) { + if (isFromSequentialQueue) { + return data; + } + + if (request.resolve) { + request.resolve(data); + } + return data; } - if (request.resolve) { - request.resolve(data); + // We are already authenticating and using the DeprecatedAPI so we will replay the request + if (!apiRequestType && NetworkStore.isAuthenticating()) { + MainQueue.replay(request); + return data; } - return data; + return reauthenticate(request?.commandName) + .then((authenticateResponse) => { + if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { + return Request.processWithMiddleware(request, isFromSequentialQueue); + } + + if (apiRequestType === CONST.API_REQUEST_TYPE.READ) { + NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken'); + return Promise.resolve(); + } + + MainQueue.replay(request); + return authenticateResponse; + }) + .catch(() => { + if (isFromSequentialQueue || apiRequestType) { + throw new Error('Failed to reauthenticate'); + } + + // If we make it here, then our reauthenticate request could not be made due to a networking issue. The original request can be retried safely. + MainQueue.replay(request); + }); } - // We are already authenticating and using the DeprecatedAPI so we will replay the request - if (!apiRequestType && NetworkStore.isAuthenticating()) { - MainQueue.replay(request); + if (isFromSequentialQueue) { return data; } - return reauthenticate(request?.commandName) - .then((authenticateResponse) => { - if (isFromSequentialQueue || apiRequestType === CONST.API_REQUEST_TYPE.MAKE_REQUEST_WITH_SIDE_EFFECTS) { - return Request.processWithMiddleware(request, isFromSequentialQueue); - } - - if (apiRequestType === CONST.API_REQUEST_TYPE.READ) { - NetworkConnection.triggerReconnectionCallbacks('read request made with expired authToken'); - return Promise.resolve(); - } - - MainQueue.replay(request); - return authenticateResponse; - }) - .catch(() => { - if (isFromSequentialQueue || apiRequestType) { - throw new Error('Failed to reauthenticate'); - } - - // If we make it here, then our reauthenticate request could not be made due to a networking issue. The original request can be retried safely. - MainQueue.replay(request); - }); - } + if (request.resolve) { + request.resolve(data); + } - if (isFromSequentialQueue) { + // Return response data so we can chain the response with the following middlewares. return data; - } - - if (request.resolve) { - request.resolve(data); - } + }) + .catch((error) => { + // If the request is on the sequential queue, re-throw the error so we can decide to retry or not + if (isFromSequentialQueue) { + throw error; + } - // Return response data so we can chain the response with the following middlewares. - return data; - }); + // If we have caught a networking error from a DeprecatedAPI request, resolve it as unable to retry, otherwise the request will never resolve or reject. + if (request.resolve) { + request.resolve({jsonCode: CONST.JSON_CODE.UNABLE_TO_RETRY}); + } + }); export default Reauthentication; diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts index 62d9119c2878..3ecd8954542f 100644 --- a/src/libs/Network/NetworkStore.ts +++ b/src/libs/Network/NetworkStore.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import Credentials from '../../types/onyx/Credentials'; -let credentials: Credentials; +let credentials: Credentials | undefined; let authToken: string | null = null; let supportAuthToken: string | null = null; let currentUserEmail: string | null = null; @@ -86,7 +86,7 @@ Onyx.connect({ }, }); -function getCredentials(): Credentials { +function getCredentials(): Credentials | undefined { return credentials; } diff --git a/src/types/onyx/Response.ts b/src/types/onyx/Response.ts index 5fba13c1b6f8..3d834d0bcb2b 100644 --- a/src/types/onyx/Response.ts +++ b/src/types/onyx/Response.ts @@ -7,8 +7,8 @@ type Response = { onyxData?: OnyxUpdate[]; requestID?: string; shouldPauseQueue?: boolean; - authToken: string; - encryptedAuthToken: string; + authToken?: string; + encryptedAuthToken?: string; message?: string; }; From 81b39afeab6454b39c192daa44461f7380c46b83 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 28 Sep 2023 14:12:12 +0200 Subject: [PATCH 09/20] Partially fix tests --- src/libs/API.ts | 2 +- src/libs/Network/NetworkStore.ts | 8 ++------ src/libs/Network/index.ts | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/libs/API.ts b/src/libs/API.ts index 7678098f3f3b..f78d58f287d8 100644 --- a/src/libs/API.ts +++ b/src/libs/API.ts @@ -149,7 +149,7 @@ function makeRequestWithSideEffects( * @param [onyxData.successData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode === 200. * @param [onyxData.failureData] - Onyx instructions that will be passed to Onyx.update() when the response has jsonCode !== 200. */ -function read(command: string, apiCommandParameters: Record, onyxData: OnyxData) { +function read(command: string, apiCommandParameters: Record, onyxData: OnyxData = {}) { // Ensure all write requests on the sequential queue have finished responding before running read requests. // Responses from read requests can overwrite the optimistic data inserted by // write requests that use the same Onyx keys and haven't responded yet. diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts index 3ecd8954542f..ef1b616c031e 100644 --- a/src/libs/Network/NetworkStore.ts +++ b/src/libs/Network/NetworkStore.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import Credentials from '../../types/onyx/Credentials'; -let credentials: Credentials | undefined; +let credentials: Credentials | null; let authToken: string | null = null; let supportAuthToken: string | null = null; let currentUserEmail: string | null = null; @@ -59,10 +59,6 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.CREDENTIALS, callback: (val) => { - if (!val) { - return; - } - credentials = val; checkRequiredData(); }, @@ -86,7 +82,7 @@ Onyx.connect({ }, }); -function getCredentials(): Credentials | undefined { +function getCredentials(): Credentials | null { return credentials; } diff --git a/src/libs/Network/index.ts b/src/libs/Network/index.ts index 4ab4bdd71085..bf38bc33e95a 100644 --- a/src/libs/Network/index.ts +++ b/src/libs/Network/index.ts @@ -45,7 +45,7 @@ function post(command: string, data: Record = {}, type = CONST. // This check is mainly used to prevent API commands from triggering calls to MainQueue.process() from inside the context of a previous // call to MainQueue.process() e.g. calling a Log command without this would cause the requests in mainQueue to double process // since we call Log inside MainQueue.process(). - const shouldProcessImmediately = request?.data.shouldProcessImmediately ?? true; + const shouldProcessImmediately = request?.data?.shouldProcessImmediately ?? true; if (!shouldProcessImmediately) { return; } From 1d43efeb3f629e1efab193428c1ef1b8d17836ba Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 28 Sep 2023 14:51:06 +0200 Subject: [PATCH 10/20] Fix the last test --- src/libs/Authentication.ts | 8 ++------ src/libs/Network/NetworkStore.ts | 2 +- src/libs/actions/Session/updateSessionAuthTokens.js | 4 ++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/libs/Authentication.ts b/src/libs/Authentication.ts index 4f070614ef7e..cec20504dd04 100644 --- a/src/libs/Authentication.ts +++ b/src/libs/Authentication.ts @@ -85,16 +85,12 @@ function reauthenticate(command = ''): Promise { } // Update authToken in Onyx and in our local variables so that API requests will use the new authToken - if (response.authToken && response.encryptedAuthToken) { - updateSessionAuthTokens(response.authToken, response.encryptedAuthToken); - } + updateSessionAuthTokens(response.authToken, response.encryptedAuthToken); // Note: It is important to manually set the authToken that is in the store here since any requests that are hooked into // reauthenticate .then() will immediate post and use the local authToken. Onyx updates subscribers lately so it is not // enough to do the updateSessionAuthTokens() call above. - if (response.authToken) { - NetworkStore.setAuthToken(response.authToken); - } + NetworkStore.setAuthToken(response.authToken ?? null); // The authentication process is finished so the network can be unpaused to continue processing requests NetworkStore.setIsAuthenticating(false); diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts index ef1b616c031e..b187623f7f54 100644 --- a/src/libs/Network/NetworkStore.ts +++ b/src/libs/Network/NetworkStore.ts @@ -106,7 +106,7 @@ function setSupportAuthToken(newSupportAuthToken: string) { supportAuthToken = newSupportAuthToken; } -function setAuthToken(newAuthToken: string) { +function setAuthToken(newAuthToken: string | null) { authToken = newAuthToken; } diff --git a/src/libs/actions/Session/updateSessionAuthTokens.js b/src/libs/actions/Session/updateSessionAuthTokens.js index 5be53c77a92c..e88b3b993c7a 100644 --- a/src/libs/actions/Session/updateSessionAuthTokens.js +++ b/src/libs/actions/Session/updateSessionAuthTokens.js @@ -2,8 +2,8 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../../ONYXKEYS'; /** - * @param {String} authToken - * @param {String} encryptedAuthToken + * @param {String | undefined} authToken + * @param {String | undefined} encryptedAuthToken */ export default function updateSessionAuthTokens(authToken, encryptedAuthToken) { Onyx.merge(ONYXKEYS.SESSION, {authToken, encryptedAuthToken}); From c2a3c12c5f2d8c4798721912979f493523a7a1e0 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 4 Oct 2023 13:01:33 +0200 Subject: [PATCH 11/20] Adjust after review --- src/libs/Middleware/Logging.ts | 2 +- src/libs/Middleware/Reauthentication.ts | 4 ++-- src/libs/Network/NetworkStore.ts | 2 +- src/libs/Network/enhanceParameters.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/Middleware/Logging.ts b/src/libs/Middleware/Logging.ts index c9ec0cc0d44e..171cb4b9ab4c 100644 --- a/src/libs/Middleware/Logging.ts +++ b/src/libs/Middleware/Logging.ts @@ -36,7 +36,7 @@ function logRequestDetails(message: string, request: Request, response?: Respons const Logging: Middleware = (response, request) => { logRequestDetails('Making API request', request); return response - ?.then((data) => { + .then((data) => { logRequestDetails('Finished API request', request, data); return data; }) diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts index 9d23ab415e56..980eda9f29e3 100644 --- a/src/libs/Middleware/Reauthentication.ts +++ b/src/libs/Middleware/Reauthentication.ts @@ -10,7 +10,7 @@ import Middleware from './types'; // We store a reference to the active authentication request so that we are only ever making one request to authenticate at a time. let isAuthenticating: Promise | null = null; -function reauthenticate(commandName?: string): Promise { +function reauthenticate(commandName: string): Promise { if (isAuthenticating) { return isAuthenticating; } @@ -30,7 +30,7 @@ function reauthenticate(commandName?: string): Promise { const Reauthentication: Middleware = (response, request, isFromSequentialQueue) => response - ?.then((data) => { + .then((data) => { // If there is no data for some reason then we cannot reauthenticate if (!data) { Log.hmmm('Undefined data in Reauthentication'); diff --git a/src/libs/Network/NetworkStore.ts b/src/libs/Network/NetworkStore.ts index b187623f7f54..0910031bdb63 100644 --- a/src/libs/Network/NetworkStore.ts +++ b/src/libs/Network/NetworkStore.ts @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; import Credentials from '../../types/onyx/Credentials'; -let credentials: Credentials | null; +let credentials: Credentials | null = null; let authToken: string | null = null; let supportAuthToken: string | null = null; let currentUserEmail: string | null = null; diff --git a/src/libs/Network/enhanceParameters.ts b/src/libs/Network/enhanceParameters.ts index 72706fed6081..54d72a7c6c99 100644 --- a/src/libs/Network/enhanceParameters.ts +++ b/src/libs/Network/enhanceParameters.ts @@ -35,7 +35,7 @@ export default function enhanceParameters(command: string, parameters: Record Date: Wed, 4 Oct 2023 14:38:37 +0200 Subject: [PATCH 12/20] Fix types, and personal preference --- src/libs/Middleware/Reauthentication.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/Middleware/Reauthentication.ts b/src/libs/Middleware/Reauthentication.ts index 980eda9f29e3..aec09227e441 100644 --- a/src/libs/Middleware/Reauthentication.ts +++ b/src/libs/Middleware/Reauthentication.ts @@ -10,7 +10,7 @@ import Middleware from './types'; // We store a reference to the active authentication request so that we are only ever making one request to authenticate at a time. let isAuthenticating: Promise | null = null; -function reauthenticate(commandName: string): Promise { +function reauthenticate(commandName?: string): Promise { if (isAuthenticating) { return isAuthenticating; } @@ -61,7 +61,6 @@ const Reauthentication: Middleware = (response, request, isFromSequentialQueue) if (request.resolve) { request.resolve(data); } - return data; } From 5dcc9aa870202656df4718aa0e9ff4df9d7b8d0b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 5 Oct 2023 17:15:11 +0200 Subject: [PATCH 13/20] Fix type error --- src/libs/Network/SequentialQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index f0b195042d27..980bbc0efc44 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -88,7 +88,7 @@ function process(): Promise { return RequestThrottle.sleep() .then(process) .catch(() => { - Onyx.update(requestToProcess.failureData); + Onyx.update(requestToProcess.failureData ?? []); PersistedRequests.remove(requestToProcess); RequestThrottle.clear(); return process(); From 5e59e13e1b503edc8299e5da002822881a64e6a3 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 6 Oct 2023 14:00:56 +0200 Subject: [PATCH 14/20] Fix tests --- src/libs/Network/SequentialQueue.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 980bbc0efc44..e74bb98f506e 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -1,3 +1,4 @@ +import _ from 'underscore'; import Onyx from 'react-native-onyx'; import * as PersistedRequests from '../actions/PersistedRequests'; import * as NetworkStore from './NetworkStore'; @@ -70,7 +71,7 @@ function process(): Promise { .then((response) => { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. - if (response?.shouldPauseQueue) { + if (!!response?.shouldPauseQueue) { pause(); } PersistedRequests.remove(requestToProcess); From e278f4bc21639ae28ab6787fbd40c43567671c16 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 6 Oct 2023 14:16:24 +0200 Subject: [PATCH 15/20] Fix lint --- src/libs/Network/SequentialQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index e74bb98f506e..8081e7303f8a 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -71,7 +71,7 @@ function process(): Promise { .then((response) => { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. - if (!!response?.shouldPauseQueue) { + if (response?.shouldPauseQueue ?? false) { pause(); } PersistedRequests.remove(requestToProcess); From 1f3f6969c02476c0ae7a4468e09808ec3228e001 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 6 Oct 2023 14:54:59 +0200 Subject: [PATCH 16/20] Remove underscore import --- src/libs/Network/SequentialQueue.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 8081e7303f8a..0faf43e66de0 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -1,4 +1,3 @@ -import _ from 'underscore'; import Onyx from 'react-native-onyx'; import * as PersistedRequests from '../actions/PersistedRequests'; import * as NetworkStore from './NetworkStore'; From 7607591c68dc524231074c6a4af3119057e04e56 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 6 Oct 2023 14:57:23 +0200 Subject: [PATCH 17/20] Get back to original if statement --- src/libs/Network/SequentialQueue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Network/SequentialQueue.ts b/src/libs/Network/SequentialQueue.ts index 0faf43e66de0..980bbc0efc44 100644 --- a/src/libs/Network/SequentialQueue.ts +++ b/src/libs/Network/SequentialQueue.ts @@ -70,7 +70,7 @@ function process(): Promise { .then((response) => { // A response might indicate that the queue should be paused. This happens when a gap in onyx updates is detected between the client and the server and // that gap needs resolved before the queue can continue. - if (response?.shouldPauseQueue ?? false) { + if (response?.shouldPauseQueue) { pause(); } PersistedRequests.remove(requestToProcess); From 75318944e58a8b0677f64db03ab5070b1a08a55f Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 13:09:12 +0200 Subject: [PATCH 18/20] Remove TODO --- src/libs/actions/Plaid.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Plaid.ts b/src/libs/actions/Plaid.ts index edf6a4fafe05..8db17a51f962 100644 --- a/src/libs/actions/Plaid.ts +++ b/src/libs/actions/Plaid.ts @@ -58,8 +58,7 @@ function openPlaidBankAccountSelector(publicToken: string, bankName: string, all bankName, }, }, - // TODO: Remove after https://github.com/Expensify/react-native-onyx/pull/381 is merged - ] as unknown[] as OnyxUpdate[], + ], successData: [ { onyxMethod: Onyx.METHOD.MERGE, @@ -69,8 +68,7 @@ function openPlaidBankAccountSelector(publicToken: string, bankName: string, all errors: null, }, }, - // TODO: Remove after https://github.com/Expensify/react-native-onyx/pull/381 is merged - ] as unknown[] as OnyxUpdate[], + ], failureData: [ { onyxMethod: Onyx.METHOD.MERGE, From 0f437dc6aeea9caafb41c290824c2e9d3c5959cd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 13:11:39 +0200 Subject: [PATCH 19/20] Fix SaveResponseOnyx formatting --- src/libs/Middleware/SaveResponseInOnyx.ts | 60 ++++++++++------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/libs/Middleware/SaveResponseInOnyx.ts b/src/libs/Middleware/SaveResponseInOnyx.ts index 16d8a9781aa3..0a279a7425b4 100644 --- a/src/libs/Middleware/SaveResponseInOnyx.ts +++ b/src/libs/Middleware/SaveResponseInOnyx.ts @@ -9,9 +9,8 @@ import Middleware from './types'; const requestsToIgnoreLastUpdateID = ['OpenApp', 'ReconnectApp', 'GetMissingOnyxMessages']; const SaveResponseInOnyx: Middleware = (requestResponse, request) => - requestResponse - .then((response = {}) => { - const onyxUpdates = response?.onyxData ?? []; + requestResponse.then((response = {}) => { + const onyxUpdates = response?.onyxData ?? []; // Sometimes we call requests that are successfull but they don't have any response or any success/failure data to set. Let's return early since // we don't need to store anything here. @@ -20,42 +19,35 @@ const SaveResponseInOnyx: Middleware = (requestResponse, request) => } // If there is an OnyxUpdate for using memory only keys, enable them - _.find(onyxUpdates, ({key, value}) => { + onyxUpdates?.find(({key, value}) => { if (key !== ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS || !value) { return false; } - // If there is an OnyxUpdate for using memory only keys, enable them - onyxUpdates?.find(({key, value}) => { - if (key !== ONYXKEYS.IS_USING_MEMORY_ONLY_KEYS || !value) { - return false; - } - - MemoryOnlyKeys.enable(); - return true; - }); - - const responseToApply = { - type: CONST.ONYX_UPDATE_TYPES.HTTPS, - lastUpdateID: Number(response?.lastUpdateID ?? 0), - previousUpdateID: Number(response?.previousUpdateID ?? 0), - request, - response: response ?? {}, - }; - - if (requestsToIgnoreLastUpdateID.includes(request.command) || !OnyxUpdates.doesClientNeedToBeUpdated(Number(response?.previousUpdateID ?? 0))) { - return OnyxUpdates.apply(responseToApply); - } + MemoryOnlyKeys.enable(); + return true; + }); + + const responseToApply = { + type: CONST.ONYX_UPDATE_TYPES.HTTPS, + lastUpdateID: Number(response?.lastUpdateID ?? 0), + previousUpdateID: Number(response?.previousUpdateID ?? 0), + request, + response: response ?? {}, + }; + + if (requestsToIgnoreLastUpdateID.includes(request.command) || !OnyxUpdates.doesClientNeedToBeUpdated(Number(response?.previousUpdateID ?? 0))) { + return OnyxUpdates.apply(responseToApply); + } - // Save the update IDs to Onyx so they can be used to fetch incremental updates if the client gets out of sync from the server - OnyxUpdates.saveUpdateInformation(responseToApply); + // Save the update IDs to Onyx so they can be used to fetch incremental updates if the client gets out of sync from the server + OnyxUpdates.saveUpdateInformation(responseToApply); - // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. - return Promise.resolve({ - ...response, - shouldPauseQueue: true, - }); - }) - + // Ensure the queue is paused while the client resolves the gap in onyx updates so that updates are guaranteed to happen in a specific order. + return Promise.resolve({ + ...response, + shouldPauseQueue: true, + }); + }); export default SaveResponseInOnyx; From c3b7fcb416a4efd63972e41597c7fb26006d952b Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 11 Oct 2023 18:20:07 +0200 Subject: [PATCH 20/20] Fix lint --- src/libs/actions/Plaid.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Plaid.ts b/src/libs/actions/Plaid.ts index 8db17a51f962..410a8c57d176 100644 --- a/src/libs/actions/Plaid.ts +++ b/src/libs/actions/Plaid.ts @@ -1,4 +1,4 @@ -import Onyx, {OnyxUpdate} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import getPlaidLinkTokenParameters from '../getPlaidLinkTokenParameters'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API';