diff --git a/src/libs/actions/OnyxUpdateManager/index.ts b/src/libs/actions/OnyxUpdateManager/index.ts index 8c6695379614..deb530547021 100644 --- a/src/libs/actions/OnyxUpdateManager/index.ts +++ b/src/libs/actions/OnyxUpdateManager/index.ts @@ -5,10 +5,10 @@ import Log from '@libs/Log'; import * as SequentialQueue from '@libs/Network/SequentialQueue'; import * as App from '@userActions/App'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxUpdatesFromServer, Response} from '@src/types/onyx'; +import type {OnyxUpdatesFromServer} from '@src/types/onyx'; import {isValidOnyxUpdateFromServer} from '@src/types/onyx/OnyxUpdatesFromServer'; import * as OnyxUpdateManagerUtils from './utils'; -import deferredUpdatesProxy from './utils/deferredUpdates'; +import * as DeferredOnyxUpdates from './utils/DeferredOnyxUpdates'; // This file is in charge of looking at the updateIDs coming from the server and comparing them to the last updateID that the client has. // If the client is behind the server, then we need to @@ -39,8 +39,6 @@ Onyx.connect({ }, }); -let queryPromise: Promise | undefined; - let resolveQueryPromiseWrapper: () => void; const createQueryPromiseWrapper = () => new Promise((resolve) => { @@ -50,8 +48,7 @@ const createQueryPromiseWrapper = () => let queryPromiseWrapper = createQueryPromiseWrapper(); const resetDeferralLogicVariables = () => { - queryPromise = undefined; - deferredUpdatesProxy.deferredUpdates = {}; + DeferredOnyxUpdates.clear({shouldUnpauseSequentialQueue: false}); }; // This function will reset the query variables, unpause the SequentialQueue and log an info to the user. @@ -61,9 +58,7 @@ function finalizeUpdatesAndResumeQueue() { resolveQueryPromiseWrapper(); queryPromiseWrapper = createQueryPromiseWrapper(); - resetDeferralLogicVariables(); - Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null); - SequentialQueue.unpause(); + DeferredOnyxUpdates.clear(); } /** @@ -111,25 +106,24 @@ function handleOnyxUpdateGap(onyxUpdatesFromServer: OnyxEntry 0) { + if (areDeferredUpdatesQueued) { return; } @@ -142,10 +136,12 @@ function handleOnyxUpdateGap(onyxUpdatesFromServer: OnyxEntry OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates(clientLastUpdateID)); + DeferredOnyxUpdates.setMissingOnyxUpdatesQueryPromise( + App.getMissingOnyxUpdates(lastUpdateIDFromClient, previousUpdateIDFromServer).then(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates(clientLastUpdateID)), + ); } - queryPromise.finally(finalizeUpdatesAndResumeQueue); + DeferredOnyxUpdates.getMissingOnyxUpdatesQueryPromise()?.finally(finalizeUpdatesAndResumeQueue); } export default () => { diff --git a/src/libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates.ts b/src/libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates.ts new file mode 100644 index 000000000000..451de2ed8a5c --- /dev/null +++ b/src/libs/actions/OnyxUpdateManager/utils/DeferredOnyxUpdates.ts @@ -0,0 +1,133 @@ +import Onyx from 'react-native-onyx'; +import type {DeferredUpdatesDictionary} from '@libs/actions/OnyxUpdateManager/types'; +import * as SequentialQueue from '@libs/Network/SequentialQueue'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxUpdatesFromServer, Response} from '@src/types/onyx'; +import {isValidOnyxUpdateFromServer} from '@src/types/onyx/OnyxUpdatesFromServer'; +// eslint-disable-next-line import/no-cycle +import * as OnyxUpdateManagerUtils from '.'; + +let missingOnyxUpdatesQueryPromise: Promise | undefined; +let deferredUpdates: DeferredUpdatesDictionary = {}; + +/** + * Returns the promise that fetches the missing onyx updates + * @returns the promise + */ +function getMissingOnyxUpdatesQueryPromise() { + return missingOnyxUpdatesQueryPromise; +} + +/** + * Sets the promise that fetches the missing onyx updates + */ +function setMissingOnyxUpdatesQueryPromise(promise: Promise) { + missingOnyxUpdatesQueryPromise = promise; +} + +type GetDeferredOnyxUpdatesOptiosn = { + minUpdateID?: number; +}; + +/** + * Returns the deferred updates that are currently in the queue + * @param minUpdateID An optional minimum update ID to filter the deferred updates by + * @returns + */ +function getUpdates(options?: GetDeferredOnyxUpdatesOptiosn) { + if (options?.minUpdateID == null) { + return deferredUpdates; + } + + return Object.entries(deferredUpdates).reduce( + (accUpdates, [lastUpdateID, update]) => ({ + ...accUpdates, + ...(Number(lastUpdateID) > (options.minUpdateID ?? 0) ? {[Number(lastUpdateID)]: update} : {}), + }), + {}, + ); +} + +/** + * Returns a boolean indicating whether the deferred updates queue is empty + * @returns a boolean indicating whether the deferred updates queue is empty + */ +function isEmpty() { + return Object.keys(deferredUpdates).length === 0; +} + +/** + * Manually processes and applies the updates from the deferred updates queue. (used e.g. for push notifications) + */ +function process() { + if (missingOnyxUpdatesQueryPromise) { + missingOnyxUpdatesQueryPromise.finally(() => OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates); + } + + missingOnyxUpdatesQueryPromise = OnyxUpdateManagerUtils.validateAndApplyDeferredUpdates(); +} + +type EnqueueDeferredOnyxUpdatesOptions = { + shouldPauseSequentialQueue?: boolean; +}; + +/** + * Allows adding onyx updates to the deferred updates queue manually. + * @param updates The updates that should be applied (e.g. updates from push notifications) + * @param options additional flags to change the behaviour of this function + */ +function enqueue(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, options?: EnqueueDeferredOnyxUpdatesOptions) { + if (options?.shouldPauseSequentialQueue ?? true) { + SequentialQueue.pause(); + } + + // We check here if the "updates" param is a single update. + // If so, we only need to insert one update into the deferred updates queue. + if (isValidOnyxUpdateFromServer(updates)) { + const lastUpdateID = Number(updates.lastUpdateID); + deferredUpdates[lastUpdateID] = updates; + } else { + // If the "updates" param is an object, we need to insert multiple updates into the deferred updates queue. + Object.entries(updates).forEach(([lastUpdateIDString, update]) => { + const lastUpdateID = Number(lastUpdateIDString); + if (deferredUpdates[lastUpdateID]) { + return; + } + + deferredUpdates[lastUpdateID] = update; + }); + } +} + +/** + * Adds updates to the deferred updates queue and processes them immediately + * @param updates The updates that should be applied (e.g. updates from push notifications) + */ +function enqueueAndProcess(updates: OnyxUpdatesFromServer | DeferredUpdatesDictionary, options?: EnqueueDeferredOnyxUpdatesOptions) { + enqueue(updates, options); + process(); +} + +type ClearDeferredOnyxUpdatesOptions = { + shouldResetGetMissingOnyxUpdatesPromise?: boolean; + shouldUnpauseSequentialQueue?: boolean; +}; + +/** + * Clears the deferred updates queue and unpauses the SequentialQueue + * @param options additional flags to change the behaviour of this function + */ +function clear(options?: ClearDeferredOnyxUpdatesOptions) { + deferredUpdates = {}; + + if (options?.shouldResetGetMissingOnyxUpdatesPromise ?? true) { + missingOnyxUpdatesQueryPromise = undefined; + } + + if (options?.shouldUnpauseSequentialQueue ?? true) { + Onyx.set(ONYXKEYS.ONYX_UPDATES_FROM_SERVER, null); + SequentialQueue.unpause(); + } +} + +export {getMissingOnyxUpdatesQueryPromise, setMissingOnyxUpdatesQueryPromise, getUpdates, isEmpty, process, enqueue, enqueueAndProcess, clear}; diff --git a/src/libs/actions/OnyxUpdateManager/utils/deferredUpdates.ts b/src/libs/actions/OnyxUpdateManager/utils/deferredUpdates.ts deleted file mode 100644 index 838c27821aae..000000000000 --- a/src/libs/actions/OnyxUpdateManager/utils/deferredUpdates.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type {DeferredUpdatesDictionary} from '@libs/actions/OnyxUpdateManager/types'; -import createProxyForObject from '@src/utils/createProxyForObject'; - -const deferredUpdatesValue = {deferredUpdates: {} as DeferredUpdatesDictionary}; - -const deferredUpdatesProxy = createProxyForObject(deferredUpdatesValue); - -export default deferredUpdatesProxy; diff --git a/src/libs/actions/OnyxUpdateManager/utils/index.ts b/src/libs/actions/OnyxUpdateManager/utils/index.ts index 04e39610ed6b..be062b1e0921 100644 --- a/src/libs/actions/OnyxUpdateManager/utils/index.ts +++ b/src/libs/actions/OnyxUpdateManager/utils/index.ts @@ -4,7 +4,8 @@ import * as App from '@userActions/App'; import type {DeferredUpdatesDictionary, DetectGapAndSplitResult} from '@userActions/OnyxUpdateManager/types'; import ONYXKEYS from '@src/ONYXKEYS'; import {applyUpdates} from './applyUpdates'; -import deferredUpdatesProxy from './deferredUpdates'; +// eslint-disable-next-line import/no-cycle +import * as DeferredOnyxUpdates from './DeferredOnyxUpdates'; let lastUpdateIDAppliedToClient = 0; Onyx.connect({ @@ -12,9 +13,13 @@ Onyx.connect({ callback: (value) => (lastUpdateIDAppliedToClient = value ?? 0), }); -// In order for the deferred updates to be applied correctly in order, -// we need to check if there are any gaps between deferred updates. - +/** + * In order for the deferred updates to be applied correctly in order, + * we need to check if there are any gaps between deferred updates. + * @param updates The deferred updates to be checked for gaps + * @param clientLastUpdateID An optional lastUpdateID passed to use instead of the lastUpdateIDAppliedToClient + * @returns + */ function detectGapsAndSplit(updates: DeferredUpdatesDictionary, clientLastUpdateID?: number): DetectGapAndSplitResult { const lastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0; @@ -75,8 +80,10 @@ function detectGapsAndSplit(updates: DeferredUpdatesDictionary, clientLastUpdate return {applicableUpdates, updatesAfterGaps, latestMissingUpdateID}; } -// This function will check for gaps in the deferred updates and -// apply the updates in order after the missing updates are fetched and applied +/** + * This function will check for gaps in the deferred updates and + * apply the updates in order after the missing updates are fetched and applied + */ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousParams?: {newLastUpdateIDFromClient: number; latestMissingUpdateID: number}): Promise { const lastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0; @@ -84,13 +91,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa // We only want to apply deferred updates that are newer than the last update that was applied to the client. // At this point, the missing updates from "GetMissingOnyxUpdates" have been applied already, so we can safely filter out. - const pendingDeferredUpdates = Object.entries(deferredUpdatesProxy.deferredUpdates).reduce( - (accUpdates, [lastUpdateID, update]) => ({ - ...accUpdates, - ...(Number(lastUpdateID) > lastUpdateIDFromClient ? {[Number(lastUpdateID)]: update} : {}), - }), - {}, - ); + const pendingDeferredUpdates = DeferredOnyxUpdates.getUpdates({minUpdateID: lastUpdateIDFromClient}); // If there are no remaining deferred updates after filtering out outdated ones, // we can just unpause the queue and return @@ -106,7 +107,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa Log.info('[DeferredUpdates] Gap detected in deferred updates', false, {lastUpdateIDFromClient, latestMissingUpdateID}); return new Promise((resolve, reject) => { - deferredUpdatesProxy.deferredUpdates = {}; + DeferredOnyxUpdates.clear({shouldUnpauseSequentialQueue: false, shouldResetGetMissingOnyxUpdatesPromise: false}); applyUpdates(applicableUpdates).then(() => { // After we have applied the applicable updates, there might have been new deferred updates added. @@ -116,7 +117,7 @@ function validateAndApplyDeferredUpdates(clientLastUpdateID?: number, previousPa const newLastUpdateIDFromClient = clientLastUpdateID ?? lastUpdateIDAppliedToClient ?? 0; - deferredUpdatesProxy.deferredUpdates = {...deferredUpdatesProxy.deferredUpdates, ...updatesAfterGaps}; + DeferredOnyxUpdates.enqueue(updatesAfterGaps, {shouldPauseSequentialQueue: false}); // If lastUpdateIDAppliedToClient got updated in the meantime, we will just retrigger the validation and application of the current deferred updates. if (latestMissingUpdateID <= newLastUpdateIDFromClient) {