Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA][TS migration] Migrate 'API.js' & 'Middleware' & 'Authentication.js' & 'Network' lib to TypeScript #28031

Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b146d18
Migrate Network
blazejkustra Sep 22, 2023
7b1f492
Migrate Middlewares
blazejkustra Sep 22, 2023
6042989
Migrate Authentication
blazejkustra Sep 22, 2023
d6769df
Request lib changes
blazejkustra Sep 22, 2023
e8977ef
Migrate API
blazejkustra Sep 22, 2023
1719af4
Changes in onyx types
blazejkustra Sep 22, 2023
0e1b762
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Sep 26, 2023
4f13d5a
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Sep 27, 2023
e136927
Fix lint and type errors
blazejkustra Sep 27, 2023
24108f8
Fix types after review
blazejkustra Sep 27, 2023
8c8bce4
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Sep 28, 2023
81b39af
Partially fix tests
blazejkustra Sep 28, 2023
1d43efe
Fix the last test
blazejkustra Sep 28, 2023
df38acb
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 3, 2023
c2a3c12
Adjust after review
blazejkustra Oct 4, 2023
113624c
Fix types, and personal preference
blazejkustra Oct 4, 2023
36d28a4
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 4, 2023
57a111b
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 5, 2023
5dcc9aa
Fix type error
blazejkustra Oct 5, 2023
847b859
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 6, 2023
5e59e13
Fix tests
blazejkustra Oct 6, 2023
e278f4b
Fix lint
blazejkustra Oct 6, 2023
1f3f696
Remove underscore import
blazejkustra Oct 6, 2023
7607591
Get back to original if statement
blazejkustra Oct 6, 2023
84c08d0
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 11, 2023
7531894
Remove TODO
blazejkustra Oct 11, 2023
0f437dc
Fix SaveResponseOnyx formatting
blazejkustra Oct 11, 2023
c3b7fcb
Fix lint
blazejkustra Oct 11, 2023
d47d344
Merge branch 'main' into ts-migration/API-Middleware-Authentication-N…
blazejkustra Oct 16, 2023
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
83 changes: 50 additions & 33 deletions src/libs/API.js → src/libs/API.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
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';
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.
Expand All @@ -28,25 +30,34 @@ Request.use(Middleware.HandleUnusedOptimisticID);
// 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<typeof CONST.API_REQUEST_TYPE>;

/**
* 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<string, unknown> = {}, 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
Expand All @@ -61,7 +72,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,
Expand All @@ -70,7 +81,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
Expand All @@ -85,24 +96,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<void | Response> {
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
Expand All @@ -113,10 +130,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
Expand All @@ -126,16 +143,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<string, unknown>, 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.
Expand Down
38 changes: 18 additions & 20 deletions src/libs/Authentication.js → src/libs/Authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import redirectToSignIn from './actions/SignInRedirect';
import CONST from '../CONST';
import Log from './Log';
import * as ErrorUtils from './ErrorUtils';
import Response from '../types/onyx/Response';

/**
* @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<Response> {
const commandName = 'Authenticate';

requireParameters(['partnerName', 'partnerPassword', 'partnerUserID', 'partnerUserSecret'], parameters, commandName);
Expand Down Expand Up @@ -48,11 +48,9 @@ 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<void> {
// Prevent any more requests from being processed while authentication happens
NetworkStore.setIsAuthenticating(true);

Expand All @@ -61,8 +59,8 @@ function reauthenticate(command = '') {
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
Expand Down Expand Up @@ -92,7 +90,7 @@ function reauthenticate(command = '') {
// 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);
NetworkStore.setAuthToken(response.authToken ?? null);
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved

// The authentication process is finished so the network can be unpaused to continue processing requests
NetworkStore.setIsAuthenticating(false);
Expand Down
50 changes: 18 additions & 32 deletions src/libs/Middleware/Logging.js → src/libs/Middleware/Logging.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown> = {
command: request.command,
shouldUseSecure: request.shouldUseSecure,
};

const returnValueList = lodashGet(request, 'data.returnValueList');
const returnValueList = request?.data?.returnValueList;
if (returnValueList) {
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
logParams.returnValueList = returnValueList;
}

const nvpNames = lodashGet(request, 'data.nvpNames');
const nvpNames = request?.data?.nvpNames;
if (nvpNames) {
logParams.nvpNames = nvpNames;
}
Expand All @@ -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);
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
return response
.then((data) => {
logRequestDetails('Finished API request', request, data);
return data;
})
.catch((error) => {
const logParams = {
const logParams: Record<string, unknown> = {
message: error.message,
status: error.status,
title: error.title,
Expand All @@ -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) {
Expand Down Expand Up @@ -123,6 +109,6 @@ function Logging(response, request) {
// Re-throw this error so the next handler can manage it
throw error;
});
}
};

export default Logging;
Loading
Loading