Skip to content
Closed
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: 1 addition & 3 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,6 @@ const CONST = {
REMOVED_FROM_POLICY: 'removedFromPolicy',
POLICY_DELETED: 'policyDeleted',
},
ERROR: {
INACCESSIBLE_REPORT: 'Report not found',
},
MESSAGE: {
TYPE: {
COMMENT: 'COMMENT',
Expand Down Expand Up @@ -329,6 +326,7 @@ const CONST = {
JSON_CODE: {
SUCCESS: 200,
NOT_AUTHENTICATED: 407,
REQUEST_FAILED: 0,
},
NVP: {
IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'isFirstTimeNewExpensifyUser',
Expand Down
104 changes: 31 additions & 73 deletions src/libs/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import updateSessionAuthTokens from './actions/Session/updateSessionAuthTokens';
import * as NetworkStore from './Network/NetworkStore';
import enhanceParameters from './Network/enhanceParameters';
import * as NetworkEvents from './Network/NetworkEvents';
import * as ErrorUtils from './ErrorUtils';

/**
* Function used to handle expired auth tokens. It re-authenticates with the API and
Expand All @@ -34,17 +35,16 @@ function handleExpiredAuthToken(originalCommand, originalParameters, originalTyp

// eslint-disable-next-line no-use-before-define
return reauthenticate(originalCommand)
.then(() => {
// Now that the API is authenticated, make the original request again with the new authToken
const params = enhanceParameters(originalCommand, originalParameters);
return Network.post(originalCommand, params, originalType);
})
.catch(() => (
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.SUCCESS) {
// Now that the API is authenticated, make the original request again with the new authToken
const params = enhanceParameters(originalCommand, originalParameters);
return Network.post(originalCommand, params, originalType);
}

// If the request did not succeed because of a networking issue or the server did not respond requeue the
// original request.
Network.post(originalCommand, originalParameters, originalType)
));
// If the request did not succeed because of a networking issue or the server did not respond requeue the original request
return Network.post(originalCommand, originalParameters, originalType);
});
}

// We set the logger for Network here so that we can avoid a circular dependency
Expand Down Expand Up @@ -83,8 +83,7 @@ NetworkEvents.onResponse((queuedRequest, response) => {
}

handleExpiredAuthToken(queuedRequest.command, queuedRequest.data, queuedRequest.type)
.then(queuedRequest.resolve || (() => Promise.resolve()))
.catch(queuedRequest.reject || (() => Promise.resolve()));
.then(queuedRequest.resolve || (() => Promise.resolve()));
return;
}

Expand Down Expand Up @@ -137,39 +136,7 @@ function Authenticate(parameters) {

// Add email param so the first Authenticate request is logged on the server w/ this email
email: parameters.email,
})
.then((response) => {
// If we didn't get a 200 response from Authenticate we either failed to Authenticate with
// an expensify login or the login credentials we created after the initial authentication.
// In both cases, we need the user to sign in again with their expensify credentials
if (response.jsonCode !== 200) {
switch (response.jsonCode) {
case 401:
throw new Error('passwordForm.error.incorrectLoginOrPassword');
case 402:
// If too few characters are passed as the password, the WAF will pass it to the API as an empty
// string, which results in a 402 error from Auth.
if (response.message === '402 Missing partnerUserSecret') {
throw new Error('passwordForm.error.incorrectLoginOrPassword');
}
throw new Error('passwordForm.error.twoFactorAuthenticationEnabled');
case 403:
if (response.message === 'Invalid code') {
throw new Error('passwordForm.error.incorrect2fa');
}
throw new Error('passwordForm.error.invalidLoginOrPassword');
case 404:
throw new Error('passwordForm.error.unableToResetPassword');
case 405:
throw new Error('passwordForm.error.noAccess');
case 413:
throw new Error('passwordForm.error.accountLocked');
default:
throw new Error('passwordForm.error.fallback');
}
}
return response;
});
});
}

/**
Expand All @@ -188,10 +155,24 @@ function reauthenticate(command = '') {
partnerUserSecret: credentials.autoGeneratedPassword,
})
.then((response) => {
// If authentication fails throw so that we hit
// the catch below and redirect to sign in
if (response.jsonCode !== 200) {
throw new Error(response.message);
NetworkStore.setIsAuthenticating(false);

if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) {
// If the reauthenticate request failed in flight then don't log the user out. We only want to do this if the API returns a
// non 200 response code and the user will not be able to authenticate with the stored credentials.
if (response.jsonCode === CONST.JSON_CODE.REQUEST_FAILED) {
return response;
}

const errorTranslationKey = ErrorUtils.getErrorKeyFromAuthenticateResponse(response);

// If we experience something other than a network error then redirect the user to sign in
redirectToSignIn(errorTranslationKey);

Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', {
command,
error: response.message,
});
}

// Update authToken in Onyx and in our local variables so that API requests will use the
Expand All @@ -202,30 +183,7 @@ function reauthenticate(command = '') {
// 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);

// The authentication process is finished so the network can be unpaused to continue
// processing requests
NetworkStore.setIsAuthenticating(false);
})

.catch((error) => {
// If authentication fails, then the network can be unpaused
NetworkStore.setIsAuthenticating(false);

// When a fetch() fails and the "API is offline" error is thrown we won't log the user out. Most likely they
// have a spotty connection and will need to try to reauthenticate when they come back online. We will
// re-throw this error so it can be handled by callers of reauthenticate().
if (error.message === CONST.ERROR.API_OFFLINE) {
throw error;
}

// If we experience something other than a network error then redirect the user to sign in
redirectToSignIn(error.message);

Log.hmmm('Redirecting to Sign In because we failed to reauthenticate', {
command,
error: error.message,
});
return response;
});
}

Expand Down
44 changes: 44 additions & 0 deletions src/libs/ErrorUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import CONST from '../CONST';

/**
* @param {Object} response
* @returns {String}
*/
function getErrorKeyFromAuthenticateResponse(response) {
// If we didn't get a 200 response from Authenticate we either failed to Authenticate with
// an expensify login or the login credentials we created after the initial authentication.
// In both cases, we need the user to sign in again with their expensify credentials
if (response.jsonCode === CONST.JSON_CODE.SUCCESS) {
return '';
}

switch (response.jsonCode) {
case 401:
return 'passwordForm.error.incorrectLoginOrPassword';
case 402:
// If too few characters are passed as the password, the WAF will pass it to the API as an empty
// string, which results in a 402 error from Auth.
if (response.message === '402 Missing partnerUserSecret') {
return 'passwordForm.error.incorrectLoginOrPassword';
}
return 'passwordForm.error.twoFactorAuthenticationEnabled';
case 403:
if (response.message === 'Invalid code') {
return 'passwordForm.error.incorrect2fa';
}
return 'passwordForm.error.invalidLoginOrPassword';
case 404:
return 'passwordForm.error.unableToResetPassword';
case 405:
return 'passwordForm.error.noAccess';
case 413:
return 'passwordForm.error.accountLocked';
default:
return 'passwordForm.error.fallback';
}
}

export {
// eslint-disable-next-line import/prefer-default-export
getErrorKeyFromAuthenticateResponse,
};
2 changes: 1 addition & 1 deletion src/libs/Network/PersistedRequestsQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Onyx from 'react-native-onyx';
import * as PersistedRequests from '../actions/PersistedRequests';
import * as NetworkStore from './NetworkStore';
import * as NetworkEvents from './NetworkEvents';
import CONST from '../../CONST';
import ONYXKEYS from '../../ONYXKEYS';
import * as ActiveClientManager from '../ActiveClientManager';
import processRequest from './processRequest';
import CONST from '../../CONST';

let isPersistedRequestsQueueRunning = false;

Expand Down
6 changes: 4 additions & 2 deletions src/libs/Network/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,19 @@ function processNetworkRequestQueue() {
.catch((error) => {
// Because we ran into an error we assume we might be offline and do a "connection" health test
NetworkEvents.triggerRecheckNeeded();

if (retryFailedRequest(queuedRequest, error)) {
return;
}

if (queuedRequest.command !== 'Log') {
NetworkEvents.getLogger().hmmm('[Network] Handled error when making request', error);
NetworkEvents.getLogger().hmmm('[Network] Handled error when making request', {error, command: queuedRequest.command});
} else {
console.debug('[Network] There was an error in the Log API command, unable to log to server!', error);
}

queuedRequest.reject(new Error(CONST.ERROR.API_OFFLINE));
// Resolve with a special client-side jsonCode so API method handlers can identify this scenario
NetworkEvents.triggerResponse(queuedRequest, {jsonCode: CONST.JSON_CODE.REQUEST_FAILED});
});
});

Expand Down
5 changes: 0 additions & 5 deletions src/libs/actions/BankAccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,6 @@ function addPersonalBankAccount(account, password, plaidLinkToken) {
ReimbursementAccount.setBankAccountFormValidationErrors({password: true});
}
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
})
.catch(() => {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
});
}

Expand All @@ -112,8 +109,6 @@ function deleteBankAccount(bankAccountID) {
} else {
Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000);
}
}).catch(() => {
Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000);
});
}

Expand Down
11 changes: 4 additions & 7 deletions src/libs/actions/PaymentMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ function deleteDebitCard(fundID) {
} else {
Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000);
}
})
.catch(() => {
Growl.show(Localize.translateLocal('common.genericErrorMessage'), CONST.GROWL.ERROR, 3000);
});
}

Expand Down Expand Up @@ -211,14 +208,14 @@ function transferWalletBalance(paymentMethod) {
API.TransferWalletBalance(parameters)
.then((response) => {
if (response.jsonCode !== 200) {
throw new Error(response.message);
Growl.error(Localize.translateLocal('transferAmountPage.failedTransfer'));
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: false});
return;
}

Onyx.merge(ONYXKEYS.USER_WALLET, {currentBalance: 0});
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {shouldShowConfirmModal: true, loading: false});
Navigation.navigate(ROUTES.SETTINGS_PAYMENTS);
}).catch(() => {
Growl.error(Localize.translateLocal('transferAmountPage.failedTransfer'));
Onyx.merge(ONYXKEYS.WALLET_TRANSFER, {loading: false});
});
}

Expand Down
2 changes: 0 additions & 2 deletions src/libs/actions/PersonalDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,6 @@ function setPersonalDetails(details, shouldGrowl) {
} else if (response.jsonCode === 401) {
Growl.error(Localize.translateLocal('personalDetails.error.lastNameLength'), 3000);
}
}).catch((error) => {
console.debug('Error while setting personal details', error);
});
}

Expand Down
27 changes: 12 additions & 15 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -380,19 +380,18 @@ function update(policyID, values, shouldGrowl = false) {
API.UpdatePolicy({policyID, value: JSON.stringify(values), lastModified: null})
.then((policyResponse) => {
if (policyResponse.jsonCode !== 200) {
throw new Error();
updateLocalPolicyValues(policyID, {isPolicyUpdating: false});

// Show the user feedback
const errorMessage = Localize.translateLocal('workspace.editor.genericFailureMessage');
Growl.error(errorMessage, 5000);
return;
}

updateLocalPolicyValues(policyID, {...values, isPolicyUpdating: false});
if (shouldGrowl) {
Growl.show(Localize.translateLocal('workspace.common.growlMessageOnSave'), CONST.GROWL.SUCCESS, 3000);
}
}).catch(() => {
updateLocalPolicyValues(policyID, {isPolicyUpdating: false});

// Show the user feedback
const errorMessage = Localize.translateLocal('workspace.editor.genericFailureMessage');
Growl.error(errorMessage, 5000);
});
}

Expand Down Expand Up @@ -447,7 +446,9 @@ function setCustomUnit(policyID, values) {
})
.then((response) => {
if (response.jsonCode !== 200) {
throw new Error();
// Show the user feedback
Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
return;
}

updateLocalPolicyValues(policyID, {
Expand All @@ -457,9 +458,6 @@ function setCustomUnit(policyID, values) {
value: values.attributes.unit,
},
});
}).catch(() => {
// Show the user feedback
Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
});
}

Expand All @@ -477,7 +475,9 @@ function setCustomUnitRate(policyID, customUnitID, values) {
})
.then((response) => {
if (response.jsonCode !== 200) {
throw new Error();
// Show the user feedback
Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
return;
}

updateLocalPolicyValues(policyID, {
Expand All @@ -489,9 +489,6 @@ function setCustomUnitRate(policyID, customUnitID, values) {
},
},
});
}).catch(() => {
// Show the user feedback
Growl.error(Localize.translateLocal('workspace.editor.genericFailureMessage'), 5000);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,12 @@ function setupWithdrawalAccount(params) {
const updatedACHData = mergeParamsWithLocalACHData(params);
API.BankAccount_SetupWithdrawal(updatedACHData)
.then((response) => {
if (response.jsonCode === CONST.JSON_CODE.REQUEST_FAILED) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}});
errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage'));
return;
}

Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...updatedACHData}});
const currentStep = updatedACHData.currentStep;
const responseACHData = lodashGet(response, 'achData', {});
Expand Down Expand Up @@ -253,11 +259,6 @@ function setupWithdrawalAccount(params) {
navigation.goToWithdrawalAccountSetupStep(nextStep, responseACHData);
}
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false});
})
.catch((response) => {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false, achData: {...updatedACHData}});
console.error(response.stack);
errors.showBankAccountErrorModal(Localize.translateLocal('common.genericErrorMessage'));
});
}

Expand Down
Loading