diff --git a/config/proxyConfig.js b/config/proxyConfig.js new file mode 100644 index 00000000000..fa09c436461 --- /dev/null +++ b/config/proxyConfig.js @@ -0,0 +1,9 @@ +/** + * These are the base API roots used to send requests to the proxy. + * We only specify for staging URLs as API requests are sent to the production + * servers by default. + */ +module.exports = { + STAGING: '/staging/', + STAGING_SECURE: '/staging-secure/', +}; diff --git a/config/webpack/webpack.dev.js b/config/webpack/webpack.dev.js index 0b48c4e5f35..2867d857ee0 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -21,6 +21,7 @@ module.exports = (env = {}) => portfinder.getPortPromise({port: BASE_PORT}) : { proxy: { '/api': 'http://[::1]:9000', + '/staging': 'http://[::1]:9000', '/chat-attachments': 'http://[::1]:9000', }, }; diff --git a/src/CONFIG.js b/src/CONFIG.js index 6b8d191b514..d430f9d9883 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -84,4 +84,5 @@ export default { DEV_PORT: process.env.PORT || 8080, E2E_TESTING: lodashGet(Config, 'E2E_TESTING', 'false') === 'true', SEND_CRASH_REPORTS: lodashGet(Config, 'SEND_CRASH_REPORTS', 'false') === 'true', + IS_USING_WEB_PROXY: getPlatform() === 'web' && useWebProxy, }; diff --git a/src/components/TestToolMenu.js b/src/components/TestToolMenu.js index c1512a805bf..eaae9aec984 100644 --- a/src/components/TestToolMenu.js +++ b/src/components/TestToolMenu.js @@ -15,6 +15,7 @@ import networkPropTypes from './networkPropTypes'; import compose from '../libs/compose'; import {withNetwork} from './OnyxProvider'; import * as ApiUtils from '../libs/ApiUtils'; +import CONFIG from '../CONFIG'; const propTypes = { /** User object in Onyx */ @@ -42,15 +43,18 @@ const TestToolMenu = props => ( {/* 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', ApiUtils.isUsingStagingApi()), - )} - /> - + This enables QA, internal testers and external devs to take advantage of sandbox environments for 3rd party services like Plaid and Onfido. + This toggle is not rendered for internal devs as they make environment changes directly to the .env file. */} + {!CONFIG.IS_USING_LOCAL_WEB && ( + + User.setShouldUseStagingServer( + !lodashGet(props, 'user.shouldUseStagingServer', ApiUtils.isUsingStagingApi()), + )} + /> + + )} {/* When toggled the app will be forced offline. */} diff --git a/src/libs/ApiUtils.js b/src/libs/ApiUtils.js index 7d779248090..08533c83b07 100644 --- a/src/libs/ApiUtils.js +++ b/src/libs/ApiUtils.js @@ -4,6 +4,7 @@ import ONYXKEYS from '../ONYXKEYS'; import CONFIG from '../CONFIG'; import CONST from '../CONST'; import * as Environment from './Environment/Environment'; +import proxyConfig from '../../config/proxyConfig'; // 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 @@ -17,8 +18,8 @@ Environment.getEnvironment() Onyx.connect({ key: ONYXKEYS.USER, callback: (val) => { - // Toggling between APIs is not allowed on production - if (ENV_NAME === CONST.ENVIRONMENT.PRODUCTION) { + // Toggling between APIs is not allowed on production and internal dev environment + if (ENV_NAME === CONST.ENVIRONMENT.PRODUCTION || CONFIG.IS_USING_LOCAL_WEB) { shouldUseStagingServer = false; return; } @@ -41,6 +42,11 @@ function getApiRoot(request) { const shouldUseSecure = lodashGet(request, 'shouldUseSecure', false); if (shouldUseStagingServer) { + if (CONFIG.IS_USING_WEB_PROXY) { + return shouldUseSecure + ? proxyConfig.STAGING_SECURE + : proxyConfig.STAGING; + } return shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_API_ROOT : CONFIG.EXPENSIFY.STAGING_API_ROOT; diff --git a/web/proxy.js b/web/proxy.js index 768963d0810..3bce0851460 100644 --- a/web/proxy.js +++ b/web/proxy.js @@ -1,21 +1,18 @@ const http = require('http'); const https = require('https'); +const proxyConfig = require('../config/proxyConfig'); require('dotenv').config(); if (process.env.USE_WEB_PROXY === 'false') { process.stdout.write('Skipping proxy as USE_WEB_PROXY was set to false.\n'); process.exit(); } - -let host = 'www.expensify.com'; - -// If we are testing against the staging API then we must use the correct host here or nothing with work. -if (/staging/.test(process.env.EXPENSIFY_URL)) { - host = 'staging.expensify.com'; -} +const host = 'www.expensify.com'; +const stagingHost = 'staging.expensify.com'; +const stagingSecureHost = 'staging-secure.expensify.com'; // eslint-disable-next-line no-console -console.log(`Creating proxy with host: ${host}`); +console.log(`Creating proxy with host: ${host} for production API and ${stagingHost} for staging API`); /** * Local proxy server that hits the production endpoint @@ -24,13 +21,36 @@ console.log(`Creating proxy with host: ${host}`); * environment that has no local API. */ const server = http.createServer((request, response) => { + let hostname = host; + let requestPath = request.url; + + /** + * When a request is matching a proxy config path we might direct it to a different host (e.g. staging) + * For requests matching proxy config patterns we replace the mapping url (prefix) with the actual path. + * This is done because the staging api root is only intended for the proxy, + * the actual server request must use the /api path. + * For example, + * /api?command=OpenReport => request sent to production server + * /staging/api?command=OpenReport => request sent to staging server + * /staging-secure/api?command=OpenReport => request sent to secure staging server + * /chat-attachments/46545... => request sent to production server + * /staging/chat-attachments/46545... => request sent to staging server + */ + if (request.url.startsWith(proxyConfig.STAGING_SECURE)) { + hostname = stagingSecureHost; + requestPath = request.url.replace(proxyConfig.STAGING_SECURE, '/'); + } else if (request.url.startsWith(proxyConfig.STAGING)) { + hostname = stagingHost; + requestPath = request.url.replace(proxyConfig.STAGING, '/'); + } + const proxyRequest = https.request({ - hostname: host, + hostname, method: 'POST', - path: request.url, + path: requestPath, headers: { ...request.headers, - host, + host: hostname, 'user-agent': request.headers['user-agent'].concat(' Development-NewDot/1.0'), }, port: 443,