From 55e80fd13f6fe77b949041471a2b48b7065ed160 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 6 May 2021 00:13:01 +0530 Subject: [PATCH 1/9] fix: messages are repeated on page refresh & process queue only when active client --- src/libs/ActiveClientManager/index.js | 15 +++++++++++++ src/libs/Network.js | 32 ++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index 0d2fcd481016..6f29f20a0fb0 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -7,6 +7,16 @@ const clientID = Str.guid(); const maxClients = 20; let activeClients; +let setReady = null; + +// Whether the client Manager has started +let isInit = false; + +// Are we ready to determine active Client? +const isReady = new Promise((res) => { + setReady = res; +}); + Onyx.connect({ key: ONYXKEYS.ACTIVE_CLIENTS, callback: (val) => { @@ -15,6 +25,9 @@ Onyx.connect({ activeClients.shift(); Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients); } + if (isInit) { + setReady(); + } }, }); @@ -23,6 +36,7 @@ Onyx.connect({ */ function init() { Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]); + isInit = true; } /** @@ -36,5 +50,6 @@ function isClientTheLeader() { export { init, + isReady, isClientTheLeader, }; diff --git a/src/libs/Network.js b/src/libs/Network.js index 2ec06271ffe9..0b58445ee037 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -3,9 +3,13 @@ import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; import HttpUtils from './HttpUtils'; import ONYXKEYS from '../ONYXKEYS'; +import * as ActiveClientManager from './ActiveClientManager'; let isQueuePaused = false; +// Are we retrying requests when user comes back online? +let retryRequestsAdded = false; + // Queue for network requests so we don't lose actions done by the user while offline let networkRequestQueue = []; @@ -31,15 +35,21 @@ let didLoadPersistedRequests; Onyx.connect({ key: ONYXKEYS.NETWORK_REQUEST_QUEUE, callback: (persistedRequests) => { - if (didLoadPersistedRequests || !persistedRequests) { - return; - } + ActiveClientManager.isReady.then(() => { + // Only process queue when client is Leader and we have not retried the requests already + if (!ActiveClientManager.isClientTheLeader() + || retryRequestsAdded + || didLoadPersistedRequests + || !persistedRequests.length) { + return; + } - // Merge the persisted requests with the requests in memory then clear out the queue as we only need to load - // this once when the app initializes - networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; - Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); - didLoadPersistedRequests = true; + // Merge the persisted requests with the requests in memory then clear out the queue as we only need to load + // this once when the app initializes + networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; + Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); + didLoadPersistedRequests = true; + }); }, }); @@ -112,6 +122,7 @@ function processNetworkRequestQueue() { !request.data.doNotRetry && request.data.persist )); Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests); + retryRequestsAdded = true; return; } @@ -160,6 +171,11 @@ function processNetworkRequestQueue() { .catch(error => onError(queuedRequest, error)); }); + // User came back online and the request queue will resume, thus we should clear the NETWORK_REQUEST_QUEUE + if (ActiveClientManager.isClientTheLeader() && retryRequestsAdded) { + Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); + } + // We clear the request queue at the end by setting the queue to retryableRequests which will either have some // requests we want to retry or an empty array networkRequestQueue = requestsToProcessOnNextRun; From 8289bad7eb04cdebf85f278f330896f80c34fdc3 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 9 May 2021 19:24:22 +0530 Subject: [PATCH 2/9] fix: messages are repeated across tabs --- src/libs/ActiveClientManager/index.js | 32 +++++++++---- src/libs/ActiveClientManager/index.native.js | 4 ++ src/libs/Network.js | 49 ++++++++++++++------ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index 6f29f20a0fb0..1a7c980e36f1 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -7,15 +7,13 @@ const clientID = Str.guid(); const maxClients = 20; let activeClients; -let setReady = null; -// Whether the client Manager has started -let isInit = false; +// Whether the clientID is set +let didInitialize = false; -// Are we ready to determine active Client? -const isReady = new Promise((res) => { - setReady = res; -}); +// Internal callback to be fired to mark ActiveClientManager as ready +// Due to Client's Id will be set asynchronously, we want to call this callback on completion. +let onReadyCallback = null; Onyx.connect({ key: ONYXKEYS.ACTIVE_CLIENTS, @@ -25,8 +23,8 @@ Onyx.connect({ activeClients.shift(); Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients); } - if (isInit) { - setReady(); + if (didInitialize) { + onReadyCallback(); } }, }); @@ -36,7 +34,21 @@ Onyx.connect({ */ function init() { Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]); - isInit = true; + didInitialize = true; +} + +/** + * Allow to run any task after ActiveClientManager has successfully initialized + * + * @returns {Promise} + */ +function isReady() { + if (didInitialize) { + return Promise.resolve(); + } + return new Promise((res) => { + onReadyCallback = res; + }); } /** diff --git a/src/libs/ActiveClientManager/index.native.js b/src/libs/ActiveClientManager/index.native.js index 9bda7c6d3c1e..f75bbd7ccc69 100644 --- a/src/libs/ActiveClientManager/index.native.js +++ b/src/libs/ActiveClientManager/index.native.js @@ -7,8 +7,12 @@ function init() {} function isClientTheLeader() { return true; } +function isReady() { + return Promise.resolve(); +} export { init, + isReady, isClientTheLeader, }; diff --git a/src/libs/Network.js b/src/libs/Network.js index e604106da706..a350d96d91bb 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -8,9 +8,6 @@ import CONST from '../CONST'; let isQueuePaused = false; -// Are we retrying requests when user comes back online? -let retryRequestsAdded = false; - // Queue for network requests so we don't lose actions done by the user while offline let networkRequestQueue = []; @@ -36,15 +33,28 @@ let didLoadPersistedRequests; Onyx.connect({ key: ONYXKEYS.NETWORK_REQUEST_QUEUE, callback: (persistedRequests) => { - ActiveClientManager.isReady.then(() => { - // Only process queue when client is Leader and we have not retried the requests already + ActiveClientManager.isReady().then(() => { + // NETWORK_REQUEST_QUEUE is shared across tabs thus when user refreshs the tabs, this queue will be + // processed but in that case it should only be processed by leader other wise requests will be repeated + // We only process the persisted requests when + // a) Client is leader, + // b) When user comes back online, this callback will be called, + // c) requests are not already loaded, + // d) When there is at least one request if (!ActiveClientManager.isClientTheLeader() - || retryRequestsAdded + || isOffline || didLoadPersistedRequests || !persistedRequests.length) { return; } + // Queue processing expects handlers but due to we are loading the requests from Storage + // we have to add new ones. + _.each(persistedRequests, (request) => { + request.resolve = () => {}; + request.reject = () => {}; + }); + // Merge the persisted requests with the requests in memory then clear out the queue as we only need to load // this once when the app initializes networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; @@ -117,13 +127,17 @@ function processNetworkRequestQueue() { if (!networkRequestQueue.length) { return; } - - // If we have a request then we need to check if it can be persisted in case we close the tab while offline - const retryableRequests = _.filter(networkRequestQueue, request => ( - !request.data.doNotRetry && request.data.persist - )); - Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests); - retryRequestsAdded = true; + const retryableRequests = []; + + // If we have a request then we need to check if it can be persisted in case we close the tab while offline. + // We filter persisted requests from the normal Queue to remove duplicates + networkRequestQueue = _.reject(networkRequestQueue, (request) => { + if (!request.data.doNotRetry && request.data.persist) { + retryableRequests.push(request); + return true; + } + }); + Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests); return; } @@ -172,11 +186,16 @@ function processNetworkRequestQueue() { .catch(error => onError(queuedRequest, error)); }); - // User came back online and the request queue will resume, thus we should clear the NETWORK_REQUEST_QUEUE - if (ActiveClientManager.isClientTheLeader() && retryRequestsAdded) { + // We should clear the NETWORK_REQUEST_QUEUE when we have loaded the perssited requests & Client is leader + if (ActiveClientManager.isClientTheLeader() && didLoadPersistedRequests) { Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); } + // User could have bad connectivity and he can go offline multiple times + // thus we allow NETWORK_REQUEST_QUEUE to be processed multiple times but only after we have processed + // old requests in the NETWORK_REQUEST_QUEUE + didLoadPersistedRequests = false; + // We clear the request queue at the end by setting the queue to retryableRequests which will either have some // requests we want to retry or an empty array networkRequestQueue = requestsToProcessOnNextRun; From a3e5150d216087a5f245e21c9292f9d6627de200 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 20 Jun 2021 03:35:22 +0530 Subject: [PATCH 3/9] improve code readability & process request when user comes back online --- src/libs/ActiveClientManager/index.js | 27 ++++++++-------- src/libs/Network.js | 45 ++++++++++++++++++--------- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index 1a7c980e36f1..fa752da1c2e0 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -8,12 +8,15 @@ const maxClients = 20; let activeClients; -// Whether the clientID is set +// Whether the current clientID is set let didInitialize = false; -// Internal callback to be fired to mark ActiveClientManager as ready -// Due to Client's Id will be set asynchronously, we want to call this callback on completion. -let onReadyCallback = null; +let resolveReadyPromise = null; + +// Keeps track of the ActiveClientManager's readiness. +const readyPromise = new Promise((res) => { + resolveReadyPromise = res; +}); Onyx.connect({ key: ONYXKEYS.ACTIVE_CLIENTS, @@ -21,10 +24,11 @@ Onyx.connect({ activeClients = !val ? [] : val; if (activeClients.length >= maxClients) { activeClients.shift(); - Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients); - } - if (didInitialize) { - onReadyCallback(); + Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients).then(() => { + if (didInitialize) { + resolveReadyPromise(); + } + }); } }, }); @@ -43,12 +47,7 @@ function init() { * @returns {Promise} */ function isReady() { - if (didInitialize) { - return Promise.resolve(); - } - return new Promise((res) => { - onReadyCallback = res; - }); + return readyPromise; } /** diff --git a/src/libs/Network.js b/src/libs/Network.js index a350d96d91bb..7051cf55b03b 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -26,19 +26,32 @@ let onError = () => {}; let isOffline; Onyx.connect({ key: ONYXKEYS.NETWORK, - callback: val => isOffline = val && val.isOffline, + callback: (val) => { + if (!val) { + return; + } + + // Client becomes online, process the queue. + if (isOffline && !val.isOffline) { + // Fake merge which trigger the NETWORK_REQUEST_QUEUE Onyx's subscription callback + Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); + } + isOffline = val.isOffline; + }, }); let didLoadPersistedRequests; -Onyx.connect({ - key: ONYXKEYS.NETWORK_REQUEST_QUEUE, - callback: (persistedRequests) => { - ActiveClientManager.isReady().then(() => { - // NETWORK_REQUEST_QUEUE is shared across tabs thus when user refreshs the tabs, this queue will be - // processed but in that case it should only be processed by leader other wise requests will be repeated + +ActiveClientManager.isReady().then(() => { + Onyx.connect({ + key: ONYXKEYS.NETWORK_REQUEST_QUEUE, + callback: (persistedRequests) => { + // NETWORK_REQUEST_QUEUE is shared across clients, thus every client will have similiar copy of + // NETWORK_REQUEST_QUEUE. It is very important to only process the queue from leader client + // otherwise requests will be duplicated. // We only process the persisted requests when - // a) Client is leader, - // b) When user comes back online, this callback will be called, + // a) Client is leader. + // b) User is online. // c) requests are not already loaded, // d) When there is at least one request if (!ActiveClientManager.isClientTheLeader() @@ -49,7 +62,7 @@ Onyx.connect({ } // Queue processing expects handlers but due to we are loading the requests from Storage - // we have to add new ones. + // we just noop them to ignore the errors. _.each(persistedRequests, (request) => { request.resolve = () => {}; request.reject = () => {}; @@ -60,8 +73,8 @@ Onyx.connect({ networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); didLoadPersistedRequests = true; - }); - }, + }, + }); }); // Subscribe to the user's session so we can include their email in every request and include it in the server logs @@ -137,7 +150,9 @@ function processNetworkRequestQueue() { return true; } }); - Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests); + if (retryableRequests.length) { + Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, retryableRequests); + } return; } @@ -186,7 +201,9 @@ function processNetworkRequestQueue() { .catch(error => onError(queuedRequest, error)); }); - // We should clear the NETWORK_REQUEST_QUEUE when we have loaded the perssited requests & Client is leader + // We should clear the NETWORK_REQUEST_QUEUE when we have loaded the persisted requests & they are processed. + // As multiple client will be sharing the same Queue and NETWORK_REQUEST_QUEUE is synchronized among clients, + // we only ask Leader client to clear the queue if (ActiveClientManager.isClientTheLeader() && didLoadPersistedRequests) { Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); } From 9231c1f9af665197e234ba70b29ca4b6d9f99bb1 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 24 Jun 2021 16:22:35 +0530 Subject: [PATCH 4/9] refactor --- src/libs/ActiveClientManager/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index fa752da1c2e0..0ffcff7a23c1 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -13,9 +13,10 @@ let didInitialize = false; let resolveReadyPromise = null; -// Keeps track of the ActiveClientManager's readiness. -const readyPromise = new Promise((res) => { - resolveReadyPromise = res; +// Keeps track of the ActiveClientManager's readiness in one place +// so that multiple calls of isReady resolve the same promise +const readyPromise = new Promise((resolve) => { + resolveReadyPromise = resolve; }); Onyx.connect({ From 175d438a01f95538ab0ad913f71a05ddf4568f95 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 25 Jun 2021 03:29:44 +0530 Subject: [PATCH 5/9] refactor --- src/libs/ActiveClientManager/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index 0ffcff7a23c1..d476a4be84a3 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -38,8 +38,8 @@ Onyx.connect({ * Add our client ID to the list of active IDs */ function init() { - Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]); didInitialize = true; + Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]); } /** From 16c147f00de85fec995537dd4b5c4016c121e1ec Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 26 Jun 2021 04:40:01 +0530 Subject: [PATCH 6/9] null check --- src/libs/Network.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Network.js b/src/libs/Network.js index 7051cf55b03b..4450efe2a4ca 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -57,6 +57,7 @@ ActiveClientManager.isReady().then(() => { if (!ActiveClientManager.isClientTheLeader() || isOffline || didLoadPersistedRequests + || !persistedRequests || !persistedRequests.length) { return; } From 4efe050389567e26c3e750a1698d4fb51f611e39 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 30 Jul 2021 04:30:02 +0530 Subject: [PATCH 7/9] fixed --- src/libs/ActiveClientManager/index.js | 28 +++++++-------------------- src/libs/Network.js | 4 ++++ src/libs/actions/Report.js | 4 ++-- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/libs/ActiveClientManager/index.js b/src/libs/ActiveClientManager/index.js index d476a4be84a3..3b23f22c83c1 100644 --- a/src/libs/ActiveClientManager/index.js +++ b/src/libs/ActiveClientManager/index.js @@ -7,16 +7,12 @@ const clientID = Str.guid(); const maxClients = 20; let activeClients; - -// Whether the current clientID is set -let didInitialize = false; - -let resolveReadyPromise = null; +let isInitialized; // Keeps track of the ActiveClientManager's readiness in one place // so that multiple calls of isReady resolve the same promise -const readyPromise = new Promise((resolve) => { - resolveReadyPromise = resolve; +const isInitializedPromise = new Promise((resolve) => { + isInitialized = resolve; }); Onyx.connect({ @@ -25,11 +21,7 @@ Onyx.connect({ activeClients = !val ? [] : val; if (activeClients.length >= maxClients) { activeClients.shift(); - Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients).then(() => { - if (didInitialize) { - resolveReadyPromise(); - } - }); + Onyx.set(ONYXKEYS.ACTIVE_CLIENTS, activeClients); } }, }); @@ -38,17 +30,11 @@ Onyx.connect({ * Add our client ID to the list of active IDs */ function init() { - didInitialize = true; - Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]); + Onyx.merge(ONYXKEYS.ACTIVE_CLIENTS, [clientID]).then(isInitialized); } -/** - * Allow to run any task after ActiveClientManager has successfully initialized - * - * @returns {Promise} - */ function isReady() { - return readyPromise; + return isInitializedPromise; } /** @@ -62,6 +48,6 @@ function isClientTheLeader() { export { init, - isReady, isClientTheLeader, + isReady, }; diff --git a/src/libs/Network.js b/src/libs/Network.js index 4450efe2a4ca..0f871084a9dc 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -46,6 +46,8 @@ ActiveClientManager.isReady().then(() => { Onyx.connect({ key: ONYXKEYS.NETWORK_REQUEST_QUEUE, callback: (persistedRequests) => { + console.debug('leader', ActiveClientManager.isClientTheLeader()); + // NETWORK_REQUEST_QUEUE is shared across clients, thus every client will have similiar copy of // NETWORK_REQUEST_QUEUE. It is very important to only process the queue from leader client // otherwise requests will be duplicated. @@ -61,6 +63,7 @@ ActiveClientManager.isReady().then(() => { || !persistedRequests.length) { return; } + console.debug('process', persistedRequests.length); // Queue processing expects handlers but due to we are loading the requests from Storage // we just noop them to ignore the errors. @@ -78,6 +81,7 @@ ActiveClientManager.isReady().then(() => { }); }); + // Subscribe to the user's session so we can include their email in every request and include it in the server logs let email; Onyx.connect({ diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 09e9875d19e4..7a8aff08ab2d 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -582,8 +582,8 @@ function updateReportWithNewAction(reportID, reportAction, notificationPreferenc const reportActionsToMerge = {}; if (reportAction.clientID) { - // Remove the optimistic action from the report since we are about to replace it with the real one (which has - // the true sequenceNumber) + // Remove the optimistic action from the report since we are about to replace it + // with the real one (which has the true sequenceNumber) reportActionsToMerge[reportAction.clientID] = null; } From c76aa8cc64ab2232fe3f3aa46b6bef19da32bbf6 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Fri, 30 Jul 2021 04:33:23 +0530 Subject: [PATCH 8/9] comments --- src/libs/Network.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/Network.js b/src/libs/Network.js index 0f871084a9dc..76d110d9ab7e 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -46,8 +46,6 @@ ActiveClientManager.isReady().then(() => { Onyx.connect({ key: ONYXKEYS.NETWORK_REQUEST_QUEUE, callback: (persistedRequests) => { - console.debug('leader', ActiveClientManager.isClientTheLeader()); - // NETWORK_REQUEST_QUEUE is shared across clients, thus every client will have similiar copy of // NETWORK_REQUEST_QUEUE. It is very important to only process the queue from leader client // otherwise requests will be duplicated. @@ -63,7 +61,6 @@ ActiveClientManager.isReady().then(() => { || !persistedRequests.length) { return; } - console.debug('process', persistedRequests.length); // Queue processing expects handlers but due to we are loading the requests from Storage // we just noop them to ignore the errors. From ddb3cbd9f0ad2ed6ffc8fef190ffe44414d96194 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 7 Aug 2021 02:34:25 +0530 Subject: [PATCH 9/9] refactor --- src/libs/Network.js | 81 +++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/src/libs/Network.js b/src/libs/Network.js index 76d110d9ab7e..a781595089c6 100644 --- a/src/libs/Network.js +++ b/src/libs/Network.js @@ -21,9 +21,46 @@ let enhanceParameters; let onResponse = () => {}; let onError = () => {}; +let didLoadPersistedRequests; +let isOffline; + +/** + * Process the offline NETWORK_REQUEST_QUEUE + * @param {Array | null} persistedRequests - Requests + */ +function processOfflineQueue(persistedRequests) { + // NETWORK_REQUEST_QUEUE is shared across clients, thus every client will have similiar copy of + // NETWORK_REQUEST_QUEUE. It is very important to only process the queue from leader client + // otherwise requests will be duplicated. + // We only process the persisted requests when + // a) Client is leader. + // b) User is online. + // c) requests are not already loaded, + // d) When there is at least one request + if (!ActiveClientManager.isClientTheLeader() + || isOffline + || didLoadPersistedRequests + || !persistedRequests + || !persistedRequests.length) { + return; + } + + // Queue processing expects handlers but due to we are loading the requests from Storage + // we just noop them to ignore the errors. + _.each(persistedRequests, (request) => { + request.resolve = () => {}; + request.reject = () => {}; + }); + + // Merge the persisted requests with the requests in memory then clear out the queue as we only need to load + // this once when the app initializes + networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; + Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); + didLoadPersistedRequests = true; +} + // We subscribe to changes to the online/offline status of the network to determine when we should fire off API calls // vs queueing them for later. -let isOffline; Onyx.connect({ key: ONYXKEYS.NETWORK, callback: (val) => { @@ -33,52 +70,24 @@ Onyx.connect({ // Client becomes online, process the queue. if (isOffline && !val.isOffline) { - // Fake merge which trigger the NETWORK_REQUEST_QUEUE Onyx's subscription callback - Onyx.merge(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); + const connection = Onyx.connect({ + key: ONYXKEYS.NETWORK_REQUEST_QUEUE, + callback: processOfflineQueue, + }); + Onyx.disconnect(connection); } isOffline = val.isOffline; }, }); -let didLoadPersistedRequests; - +// Subscribe to NETWORK_REQUEST_QUEUE queue as soon as Client is ready ActiveClientManager.isReady().then(() => { Onyx.connect({ key: ONYXKEYS.NETWORK_REQUEST_QUEUE, - callback: (persistedRequests) => { - // NETWORK_REQUEST_QUEUE is shared across clients, thus every client will have similiar copy of - // NETWORK_REQUEST_QUEUE. It is very important to only process the queue from leader client - // otherwise requests will be duplicated. - // We only process the persisted requests when - // a) Client is leader. - // b) User is online. - // c) requests are not already loaded, - // d) When there is at least one request - if (!ActiveClientManager.isClientTheLeader() - || isOffline - || didLoadPersistedRequests - || !persistedRequests - || !persistedRequests.length) { - return; - } - - // Queue processing expects handlers but due to we are loading the requests from Storage - // we just noop them to ignore the errors. - _.each(persistedRequests, (request) => { - request.resolve = () => {}; - request.reject = () => {}; - }); - - // Merge the persisted requests with the requests in memory then clear out the queue as we only need to load - // this once when the app initializes - networkRequestQueue = [...networkRequestQueue, ...persistedRequests]; - Onyx.set(ONYXKEYS.NETWORK_REQUEST_QUEUE, []); - didLoadPersistedRequests = true; - }, + callback: processOfflineQueue, }); }); - // Subscribe to the user's session so we can include their email in every request and include it in the server logs let email; Onyx.connect({