Skip to content

Commit

Permalink
Merge pull request #15178 from kidroca/kidroca/refactor/api-root
Browse files Browse the repository at this point in the history
Refactor: Create libs/ApiUtils
  • Loading branch information
mountiny authored Mar 16, 2023
2 parents 0497209 + d641fa8 commit 44f9871
Show file tree
Hide file tree
Showing 13 changed files with 120 additions and 68 deletions.
14 changes: 10 additions & 4 deletions src/CONFIG.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,17 @@ export default {
EXPENSIFY: {
// Note: This will be EXACTLY what is set for EXPENSIFY_URL whether the proxy is enabled or not.
EXPENSIFY_URL: expensifyURL,
SECURE_EXPENSIFY_URL: secureURLRoot,
NEW_EXPENSIFY_URL: newExpensifyURL,
URL_API_ROOT: expensifyURLRoot,
STAGING_EXPENSIFY_URL: stagingExpensifyURL,
STAGING_SECURE_EXPENSIFY_URL: stagingSecureExpensifyUrl,

// The DEFAULT API is the API used by most environments, except staging, where we use STAGING (defined below)
// The "staging toggle" in settings toggles between DEFAULT and STAGING APIs
// On both STAGING and PROD this (DEFAULT) address points to production
// On DEV it can be configured through ENV settings and can be a proxy or ngrok address (defaults to PROD)
// Usually you don't need to use this URL directly - prefer `ApiUtils.getApiRoot()`
DEFAULT_API_ROOT: expensifyURLRoot,
DEFAULT_SECURE_API_ROOT: secureURLRoot,
STAGING_API_ROOT: stagingExpensifyURL,
STAGING_SECURE_API_ROOT: stagingSecureExpensifyUrl,
PARTNER_NAME: lodashGet(Config, 'EXPENSIFY_PARTNER_NAME', 'chat-expensify-com'),
PARTNER_PASSWORD: lodashGet(Config, 'EXPENSIFY_PARTNER_PASSWORD', 'e21965746fd75f82bb66'),
EXPENSIFY_CASH_REFERER: 'ecash',
Expand Down
4 changes: 2 additions & 2 deletions src/components/AddressSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import PropTypes from 'prop-types';
import {LogBox, ScrollView, View} from 'react-native';
import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete';
import lodashGet from 'lodash/get';
import CONFIG from '../CONFIG';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
import TextInput from './TextInput';
import * as ApiUtils from '../libs/ApiUtils';
import * as GooglePlacesUtils from '../libs/GooglePlacesUtils';
import CONST from '../CONST';

Expand Down Expand Up @@ -199,7 +199,7 @@ const AddressSearch = (props) => {
query={query}
requestUrl={{
useOnPlatform: 'all',
url: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Proxy_GooglePlaces&proxyUrl=`,
url: ApiUtils.getCommandURL({command: 'Proxy_GooglePlaces&proxyUrl='}),
}}
textInputProps={{
InputComp: TextInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const AnchorRenderer = (props) => {
const parentStyle = lodashGet(props.tnode, 'parent.styles.nativeTextRet', {});
const attrHref = htmlAttribs.href || '';
const attrPath = lodashGet(Url.getURLObject(attrHref), 'path', '').replace('/', '');
const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.STAGING_EXPENSIFY_URL);
const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONFIG.EXPENSIFY.STAGING_API_ROOT);
const internalNewExpensifyPath = (Url.hasSameExpensifyOrigin(attrHref, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(attrHref, CONST.STAGING_NEW_EXPENSIFY_URL)) && attrPath;
const internalExpensifyPath = hasExpensifyOrigin
&& !attrPath.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL_PATHNAME)
Expand Down
16 changes: 9 additions & 7 deletions src/components/TestToolMenu.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'underscore';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import styles from '../styles/styles';
Expand All @@ -15,8 +14,7 @@ import TestToolRow from './TestToolRow';
import networkPropTypes from './networkPropTypes';
import compose from '../libs/compose';
import {withNetwork} from './OnyxProvider';
import getPlatform from '../libs/getPlatform';
import CONST from '../CONST';
import * as ApiUtils from '../libs/ApiUtils';

const propTypes = {
/** User object in Onyx */
Expand All @@ -31,7 +29,9 @@ const propTypes = {

const defaultProps = {
user: {
shouldUseStagingServer: false,
// The default value is environment specific and can't be set with `defaultProps` (ENV is not resolved yet)
// When undefined (during render) STAGING defaults to `true`, other envs default to `false`
shouldUseStagingServer: undefined,
},
};

Expand All @@ -41,12 +41,14 @@ const TestToolMenu = props => (
Test Preferences
</Text>

{/* Option to switch from using the staging secure endpoint or the production secure endpoint.
{/* Option to switch between staging and default api endpoints.
This enables QA and internal testers to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. */}
<TestToolRow title="Use Staging Server">
<Switch
isOn={lodashGet(props, 'user.shouldUseStagingServer', _.contains([CONST.PLATFORM.WEB, CONST.PLATFORM.DESKTOP], getPlatform()))}
onToggle={() => User.setShouldUseStagingServer(!lodashGet(props, 'user.shouldUseStagingServer', true))}
isOn={lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi())}
onToggle={() => User.setShouldUseStagingServer(
!lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()),
)}
/>
</TestToolRow>

Expand Down
80 changes: 80 additions & 0 deletions src/libs/ApiUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import CONFIG from '../CONFIG';
import CONST from '../CONST';
import * as Environment from './Environment/Environment';

// To avoid rebuilding native apps, native apps use production config for both staging and prod
// We use the async environment check because it works on all platforms
let ENV_NAME = CONST.ENVIRONMENT.PRODUCTION;
let shouldUseStagingServer = false;
Environment.getEnvironment()
.then((envName) => {
ENV_NAME = envName;

// We connect here, so we have the updated ENV_NAME when Onyx callback runs
Onyx.connect({
key: ONYXKEYS.USER,
callback: (val) => {
// Toggling between APIs is not allowed on production
if (ENV_NAME === CONST.ENVIRONMENT.PRODUCTION) {
shouldUseStagingServer = false;
return;
}

const defaultToggleState = ENV_NAME === CONST.ENVIRONMENT.STAGING;
shouldUseStagingServer = lodashGet(val, 'shouldUseStagingServer', defaultToggleState);
},
});
});

/**
* Get the currently used API endpoint
* (Non-production environments allow for dynamically switching the API)
*
* @param {Object} [request]
* @param {Boolean} [request.shouldUseSecure]
* @returns {String}
*/
function getApiRoot(request) {
const shouldUseSecure = lodashGet(request, 'shouldUseSecure', false);

if (shouldUseStagingServer) {
return shouldUseSecure
? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT
: CONFIG.EXPENSIFY.STAGING_API_ROOT;
}

return shouldUseSecure
? CONFIG.EXPENSIFY.DEFAULT_SECURE_API_ROOT
: CONFIG.EXPENSIFY.DEFAULT_API_ROOT;
}

/**
* Get the command url for the given request
*
* @param {Object} request
* @param {String} request.command - the name of the API command
* @param {Boolean} [request.shouldUseSecure]
* @returns {String}
*/
function getCommandURL(request) {
return `${getApiRoot(request)}api?command=${request.command}`;
}

/**
* Check if we're currently using the staging API root
*
* @returns {Boolean}
*/
function isUsingStagingApi() {
return shouldUseStagingServer;
}

export {
getApiRoot,
getCommandURL,
isUsingStagingApi,
};

22 changes: 3 additions & 19 deletions src/libs/HttpUtils.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import Onyx from 'react-native-onyx';
import lodashGet from 'lodash/get';
import _ from 'underscore';
import CONFIG from '../CONFIG';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';
import HttpsError from './Errors/HttpsError';
import shouldUseStagingServer from './shouldUseStagingServer';
import getPlatform from './getPlatform';

// Desktop and web use staging config too so we we should default to staging API endpoint if on those platforms
const shouldDefaultToStaging = _.contains([CONST.PLATFORM.WEB, CONST.PLATFORM.DESKTOP], getPlatform());
let stagingServerToggleState = false;
Onyx.connect({
key: ONYXKEYS.USER,
callback: val => stagingServerToggleState = lodashGet(val, 'shouldUseStagingServer', shouldDefaultToStaging),
});
import * as ApiUtils from './ApiUtils';

let shouldFailAllRequests = false;
let shouldForceOffline = false;
Expand Down Expand Up @@ -119,13 +108,8 @@ function xhr(command, data, type = CONST.NETWORK.METHOD.POST, shouldUseSecure =
formData.append(key, val);
});

let apiRoot = shouldUseSecure ? CONFIG.EXPENSIFY.SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.URL_API_ROOT;

if (shouldUseStagingServer(stagingServerToggleState)) {
apiRoot = shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.STAGING_EXPENSIFY_URL;
}

return processHTTPRequest(`${apiRoot}api?command=${command}`, type, formData, data.canCancel);
const url = ApiUtils.getCommandURL({shouldUseSecure, command});
return processHTTPRequest(url, type, formData, data.canCancel);
}

function cancelPendingRequests() {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class AuthScreens extends React.Component {
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=AuthenticatePusher`,
}).then(() => {
User.subscribeToUserEvents();
});
Expand Down
2 changes: 1 addition & 1 deletion src/libs/NetworkConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function subscribeToNetInfo() {
// By default, NetInfo uses `/` for `reachabilityUrl`
// When App is served locally (or from Electron) this address is always reachable - even offline
// Using the API url ensures reachability is tested over internet
reachabilityUrl: `${CONFIG.EXPENSIFY.URL_API_ROOT}api`,
reachabilityUrl: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api`,
reachabilityTest: response => Promise.resolve(response.status === 200),

// If a check is taking longer than this time we're considered offline
Expand Down
13 changes: 0 additions & 13 deletions src/libs/shouldUseStagingServer/index.js

This file was deleted.

13 changes: 0 additions & 13 deletions src/libs/shouldUseStagingServer/index.native.js

This file was deleted.

16 changes: 11 additions & 5 deletions src/libs/tryResolveUrlFromApiRoot.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import Config from '../CONFIG';
import * as ApiUtils from './ApiUtils';

// Absolute URLs (`/` or `//`) should be resolved from API ROOT
// Legacy attachments can come from either staging or prod, depending on the env they were uploaded by
// Both should be replaced and loaded from API ROOT of the current environment
const ORIGINS_TO_REPLACE = ['/+', Config.EXPENSIFY.EXPENSIFY_URL, Config.EXPENSIFY.STAGING_EXPENSIFY_URL];
const ORIGINS_TO_REPLACE = [
'/+',
Config.EXPENSIFY.EXPENSIFY_URL,
Config.EXPENSIFY.STAGING_API_ROOT,
Config.EXPENSIFY.DEFAULT_API_ROOT,
];

// Anything starting with a match from ORIGINS_TO_REPLACE
const ORIGIN_PATTERN = new RegExp(`^(${ORIGINS_TO_REPLACE.join('|')})`);

/**
* When possible resolve sources relative to API ROOT
* Updates applicable URLs, so they are accessed relative to URL_API_ROOT
* When possible this function resolve URLs relative to API ROOT
* - Absolute URLs like `/{path}`, become: `https://{API_ROOT}/{path}`
* - Similarly for prod or staging URLs we replace the `https://www.expensify`
* or `https://staging.expensify` part, with `https://{API_ROOT}`
* - Unmatched URLs are returned with no modifications
* - Unmatched URLs (non expensify) are returned with no modifications
*
* @param {String} url
* @returns {String}
*/
export default function tryResolveUrlFromApiRoot(url) {
return url.replace(ORIGIN_PATTERN, Config.EXPENSIFY.URL_API_ROOT);
const apiRoot = ApiUtils.getApiRoot({shouldUseSecure: false});
return url.replace(ORIGIN_PATTERN, apiRoot);
}
2 changes: 1 addition & 1 deletion tests/actions/ReportTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('actions/Report', () => {
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=AuthenticatePusher`,
});

Onyx.init({
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/UnreadIndicatorsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ beforeAll(() => {
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=AuthenticatePusher`,
authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api?command=AuthenticatePusher`,
});
});

Expand Down

0 comments on commit 44f9871

Please sign in to comment.