Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libs/Log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import AppLogs from 'react-native-app-logs';
import pkg from '../../package.json';
import {getCurrentUserEmail} from './CurrentUserStore';
import getPlatform from './getPlatform';
import {post} from './Network';
import MainQueueStore from './Network/MainQueueStore';
import requireParameters from './requireParameters';
import forwardLogsToSentry from './telemetry/forwardLogsToSentry';

Expand All @@ -26,7 +26,7 @@ function LogCommand(parameters: LogCommandParameters): Promise<{requestID: strin

// Note: We are forcing Log to run since it requires no authToken and should only be queued when we are offline.
// Non-cancellable request: during logout, when requests are cancelled, we don't want to cancel any remaining logs
return post(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>;
return MainQueueStore.enqueue(commandName, {...parameters, forceNetworkRequest: true, canCancel: false}) as Promise<{requestID: string}>;
}

// eslint-disable-next-line
Expand Down
29 changes: 8 additions & 21 deletions src/libs/Network/MainQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import type {AnyRequest} from '@src/types/onyx/Request';

import type {OnyxKey} from 'react-native-onyx';

import MainQueueStore from './MainQueueStore';
import {isAuthenticating} from './NetworkStore';
import {isRunning as sequentialQueueIsRunning} from './SequentialQueue';

// Queue for network requests so we don't lose actions done by the user while offline
let networkRequestQueue: AnyRequest[] = [];

/**
* Checks to see if a request can be made.
*/
Expand All @@ -21,12 +19,8 @@ function canMakeRequest<TKey extends OnyxKey>(request: OnyxRequest<TKey>): boole
return request.data?.forceNetworkRequest === true || (!isAuthenticating() && !sequentialQueueIsRunning());
}

function push<TKey extends OnyxKey>(request: OnyxRequest<TKey>) {
networkRequestQueue.push(request as AnyRequest);
}

function replay<TKey extends OnyxKey>(request: OnyxRequest<TKey>) {
push(request);
MainQueueStore.push(request);

process();
}
Expand All @@ -39,6 +33,8 @@ function process() {
return;
}

const networkRequestQueue = MainQueueStore.getAll();

// When the queue length is empty an early return is performed since nothing needs to be processed
if (networkRequestQueue.length === 0) {
return;
Expand Down Expand Up @@ -67,19 +63,10 @@ function process() {

// We clear the request queue at the end by setting the queue to requestsToProcessOnNextRun which will either have some
// requests we want to retry or an empty array
networkRequestQueue = requestsToProcessOnNextRun;
MainQueueStore.replaceAll(requestsToProcessOnNextRun);
}

/**
* Clear the queue and cancels all pending requests
* Non-cancellable requests like Log would not be cleared
*/
function clear() {
networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel);
}

function getAll(): AnyRequest[] {
return networkRequestQueue;
}
// Re-exported so the queue keeps a single entry point for MainQueue's consumers
const {clear, getAll} = MainQueueStore;

export {clear, replay, push, process, getAll};
export {clear, replay, process, getAll};
63 changes: 63 additions & 0 deletions src/libs/Network/MainQueueStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import CONST from '@src/CONST';
import type {Request} from '@src/types/onyx';
import type OnyxRequest from '@src/types/onyx/Request';
import type {AnyRequest} from '@src/types/onyx/Request';
import type Response from '@src/types/onyx/Response';

import type {OnyxKey} from 'react-native-onyx';

import pkg from '../../../package.json';

// Queue for network requests so we don't lose actions done by the user while offline
let networkRequestQueue: AnyRequest[] = [];

function push<TKey extends OnyxKey>(request: OnyxRequest<TKey>) {
networkRequestQueue.push(request as AnyRequest);
}

/**
* Clear the queue and cancels all pending requests
* Non-cancellable requests like Log would not be cleared
*/
function clear() {
networkRequestQueue = networkRequestQueue.filter((request) => !request.data?.canCancel);
}

function getAll(): AnyRequest[] {
return networkRequestQueue;
}

function replaceAll(requests: AnyRequest[]) {
networkRequestQueue = requests;
}

function enqueue<TKey extends OnyxKey>(command: string, data: Record<string, unknown> = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = false): Promise<Response<TKey>> {
return new Promise((resolve, reject) => {
const request: Request<never> = {
command,
data,
type,
shouldUseSecure,
};

// By default, request are retry-able and cancellable
// (e.g. any requests currently happening when the user logs out are cancelled)
request.data = {
...data,
shouldRetry: data?.shouldRetry ?? true,
canCancel: data?.canCancel ?? true,
appversion: pkg.version,
};

// Add promise handlers to any request that we are not persisting
request.resolve = resolve;
request.reject = reject;

// Add the request to a queue of actions to perform
push(request);
});
}

const MainQueueStore = {clear, push, getAll, replaceAll, enqueue};

export default MainQueueStore;
62 changes: 20 additions & 42 deletions src/libs/Network/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import * as ActiveClientManager from '@libs/ActiveClientManager';

import CONST from '@src/CONST';
import type {Request} from '@src/types/onyx';
import type Response from '@src/types/onyx/Response';

import type {OnyxKey} from 'react-native-onyx';

import pkg from '../../../package.json';
import {process as processMainQueue, push as pushToMainQueue} from './MainQueue';
import {process as processMainQueue} from './MainQueue';
import MainQueueStore from './MainQueueStore';
import {flush as flushSequentialQueue} from './SequentialQueue';

// React Native uses a number for the timer id, but Web/NodeJS uses a Timeout object
let processQueueInterval: NodeJS.Timeout | number;

// We must wait until the ActiveClientManager is ready so that we ensure only the "leader" tab processes any persisted requests
ActiveClientManager.isReady().then(() => {
flushSequentialQueue();
function startMainQueue() {
// We must wait until the ActiveClientManager is ready so that we ensure only the "leader" tab processes any persisted requests
ActiveClientManager.isReady().then(() => {
flushSequentialQueue();

// Start main queue and process once every n ms delay
processQueueInterval = setInterval(processMainQueue, CONST.NETWORK.PROCESS_REQUEST_DELAY_MS);
});
// Start main queue and process once every n ms delay
processQueueInterval = setInterval(processMainQueue, CONST.NETWORK.PROCESS_REQUEST_DELAY_MS);
});
}

/**
* Clear any existing intervals during test runs
Expand All @@ -36,41 +37,18 @@ function clearProcessQueueInterval() {
* Perform a queued post request
*/
function post<TKey extends OnyxKey>(command: string, data: Record<string, unknown> = {}, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = false): Promise<Response<TKey>> {
return new Promise((resolve, reject) => {
const request: Request<never> = {
command,
data,
type,
shouldUseSecure,
};

// By default, request are retry-able and cancellable
// (e.g. any requests currently happening when the user logs out are cancelled)
request.data = {
...data,
shouldRetry: data?.shouldRetry ?? true,
canCancel: data?.canCancel ?? true,
appversion: pkg.version,
};

// Add promise handlers to any request that we are not persisting
request.resolve = resolve;
request.reject = reject;

// Add the request to a queue of actions to perform
pushToMainQueue(request);

// 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;
if (!shouldProcessImmediately) {
return;
}
const promise = MainQueueStore.enqueue<TKey>(command, data, type, shouldUseSecure);

// 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 = data?.shouldProcessImmediately ?? true;
if (shouldProcessImmediately) {
// Try to fire off the request as soon as it's queued so we don't add a delay to every queued command
processMainQueue();
});
}

return promise;
}

export {post, clearProcessQueueInterval};
export {post, startMainQueue, clearProcessQueueInterval};
3 changes: 3 additions & 0 deletions src/setup/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {finishCloudflareSignInFromURL} from '@libs/CloudflareAccess/finishSignInFromURL';
import intlPolyfill from '@libs/IntlPolyfill';
import registerMiddlewares from '@libs/Middleware/register';
import {startMainQueue} from '@libs/Network';
import registerReportActionsPagination from '@libs/registerReportActionsPagination';

import {setDeviceID} from '@userActions/Device';
Expand Down Expand Up @@ -104,6 +105,8 @@ export default function () {
// Onyx.init() because a completed exchange persists the session. No-op on every other load.
finishCloudflareSignInFromURL();

startMainQueue();

initOnyxDerivedValues();

setDeviceID();
Expand Down
Loading