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

Refactor: Create libs/ApiUtils #15178

Merged
merged 23 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
743ae89
CONFIG - rename API ROOT urls for clarity
kidroca Feb 15, 2023
db491dd
Create `libs/ApiUtils`
kidroca Feb 15, 2023
a851403
Create `ApiUtils.getCommandUrl` and replace usage in HttpUtils
kidroca Feb 15, 2023
4c60df7
Update `tryResolveUrlFromApiRoot` to use `ApiUtils.getApiRoot`
kidroca Feb 15, 2023
92bb599
ApiUtils - remove platform specific implementations and use a single …
kidroca Mar 6, 2023
8cf601b
Update `TestToolMenu` to use `ApiUtils`
kidroca Mar 6, 2023
9c64b57
Delete unused `./libs/shouldUseStagingServer/`
kidroca Mar 6, 2023
6df8c6f
Rename CONFIG.PRODUCTION_API_ROOT to PRIMARY_API_ROOT
kidroca Mar 6, 2023
4708122
Apply suggestions from code review
kidroca Mar 7, 2023
595bb23
Document PRIMARY_API_ROOT
kidroca Mar 7, 2023
6d88ebf
Update AddressSearch to use ApiUtils instead of referencing API_ROOT …
kidroca Mar 7, 2023
8f85692
ApiUtils: Add missing docks
kidroca Mar 7, 2023
b75f313
ApiUtils: make `getApiRoot` `request` param optional
kidroca Mar 7, 2023
ebdb915
ApiUtils: apply code review suggestions to simplify `canUseStagingSer…
kidroca Mar 8, 2023
0269102
Rename CONFIG.PRIMARY_API_ROOT to DEFAULT
kidroca Mar 8, 2023
ca66fba
ApiUtils - Apply suggestions from code review
kidroca Mar 8, 2023
39fd3c6
ApiUtils rename `getCommandUrl` -> `getCommandURL`
kidroca Mar 8, 2023
7b49753
Add requested comments for clarity
kidroca Mar 10, 2023
9be14bc
ApiUtils - Move `Onyx.connect` inside `getEnvironment` promise
kidroca Mar 10, 2023
e3d84f9
Update NetworkConnection - use the updated CONFIG value - DEFAULT_API…
kidroca Mar 10, 2023
3be8792
ApiUtils - remove `canUseStagingServer`
kidroca Mar 13, 2023
c197b41
Docs: Update src/libs/ApiUtils.js
kidroca Mar 13, 2023
d641fa8
Docs: TestToolMenu code comments
kidroca Mar 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
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,
kidroca marked this conversation as resolved.
Show resolved Hide resolved
},
};

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. */}
kidroca marked this conversation as resolved.
Show resolved Hide resolved
<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() {
kidroca marked this conversation as resolved.
Show resolved Hide resolved
mountiny marked this conversation as resolved.
Show resolved Hide resolved
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),
});
kidroca marked this conversation as resolved.
Show resolved Hide resolved
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;
}
kidroca marked this conversation as resolved.
Show resolved Hide resolved

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`,
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
}).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