diff --git a/src/CONFIG.js b/src/CONFIG.js index 2dd63730b84..6b8d191b514 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -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', diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index dd88073e228..7e22542c048 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -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'; @@ -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, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js index e966d38ab6c..8c27cba1f7a 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.js @@ -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) diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js index a5e61942f24..c1512a805bf 100644 --- a/src/components/TestToolMenu.js +++ b/src/components/TestToolMenu.js @@ -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'; @@ -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 */ @@ -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, }, }; @@ -41,12 +41,14 @@ const TestToolMenu = props => ( Test Preferences - {/* 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. */} User.setShouldUseStagingServer(!lodashGet(props, 'user.shouldUseStagingServer', true))} + isOn={lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi())} + onToggle={() => User.setShouldUseStagingServer( + !lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()), + )} /> diff --git a/src/libs/ApiUtils.js b/src/libs/ApiUtils.js new file mode 100644 index 00000000000..7d779248090 --- /dev/null +++ b/src/libs/ApiUtils.js @@ -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, +}; + diff --git a/src/libs/HttpUtils.js b/src/libs/HttpUtils.js index a1cd16bcc4f..635379d4730 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -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; @@ -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() { diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index ec8b31b5880..e76c0e3faab 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -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(); }); diff --git a/src/libs/NetworkConnection.js b/src/libs/NetworkConnection.js index 7dd0df70eb5..66f62a862b0 100644 --- a/src/libs/NetworkConnection.js +++ b/src/libs/NetworkConnection.js @@ -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 diff --git a/src/libs/shouldUseStagingServer/index.js b/src/libs/shouldUseStagingServer/index.js deleted file mode 100644 index 745dd03b448..00000000000 --- a/src/libs/shouldUseStagingServer/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import CONFIG from '../../CONFIG'; - -/** - * Helper method used to decide which API endpoint to call - * - * @param {Boolean} stagingServerToggleState - * @returns {Boolean} - */ -function shouldUseStagingServer(stagingServerToggleState) { - return CONFIG.IS_IN_STAGING && stagingServerToggleState; -} - -export default shouldUseStagingServer; diff --git a/src/libs/shouldUseStagingServer/index.native.js b/src/libs/shouldUseStagingServer/index.native.js deleted file mode 100644 index 92baff89c83..00000000000 --- a/src/libs/shouldUseStagingServer/index.native.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as Environment from '../Environment/Environment'; - -/** - * Helper method used to decide which API endpoint to call in the Native apps. - * We build the staging native apps with production env config so we cannot rely on those values, - * hence we will decide solely on the value of the shouldUseStagingServer value (always false in production). - * - * @param {Boolean} stagingServerToggleState - * @returns {Boolean} - */ -export default function shouldUseStagingServer(stagingServerToggleState) { - return !Environment.isDevelopment() && stagingServerToggleState; -} diff --git a/src/libs/tryResolveUrlFromApiRoot.js b/src/libs/tryResolveUrlFromApiRoot.js index 7797d344645..b5e5bf2239f 100644 --- a/src/libs/tryResolveUrlFromApiRoot.js +++ b/src/libs/tryResolveUrlFromApiRoot.js @@ -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); } diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 10fde6b007b..34c7c665b7d 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -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({ diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index 3fd3a5946e3..5f65d8b20b5 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -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`, }); });