diff --git a/config/proxyConfig.js b/config/proxyConfig.js new file mode 100644 index 000000000000..0af64857d7bc --- /dev/null +++ b/config/proxyConfig.js @@ -0,0 +1,9 @@ +/** + * These are the base API root 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 0b48c4e5f35a..926dcd0bcc1a 100644 --- a/config/webpack/webpack.dev.js +++ b/config/webpack/webpack.dev.js @@ -21,6 +21,8 @@ module.exports = (env = {}) => portfinder.getPortPromise({port: BASE_PORT}) : { proxy: { '/api': 'http://[::1]:9000', + '/staging-api': 'http://[::1]:9000', + '/staging-secure-api': 'http://[::1]:9000', '/chat-attachments': 'http://[::1]:9000', }, }; diff --git a/src/CONFIG.js b/src/CONFIG.js index 2dd63730b84c..f6db312b7a19 100644 --- a/src/CONFIG.js +++ b/src/CONFIG.js @@ -78,4 +78,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/libs/HttpUtils.js b/src/libs/HttpUtils.js index 5fedc22f4d56..0aa379f6b633 100644 --- a/src/libs/HttpUtils.js +++ b/src/libs/HttpUtils.js @@ -5,15 +5,15 @@ 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'; +import proxyConfig from '../../config/proxyConfig'; // 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; +let shouldUseStagingServer = false; Onyx.connect({ key: ONYXKEYS.USER, - callback: val => stagingServerToggleState = lodashGet(val, 'shouldUseStagingServer', shouldDefaultToStaging), + callback: val => shouldUseStagingServer = lodashGet(val, 'shouldUseStagingServer', shouldDefaultToStaging), }); let shouldFailAllRequests = false; @@ -109,10 +109,13 @@ function xhr(command, data, type = CONST.NETWORK.METHOD.POST, shouldUseSecure = 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; + if (shouldUseStagingServer) { + if (CONFIG.IS_USING_WEB_PROXY) { + apiRoot = shouldUseSecure ? proxyConfig.STAGING_SECURE : proxyConfig.STAGING; + } else { + apiRoot = shouldUseSecure ? CONFIG.EXPENSIFY.STAGING_SECURE_EXPENSIFY_URL : CONFIG.EXPENSIFY.STAGING_EXPENSIFY_URL; + } } - return processHTTPRequest(`${apiRoot}api?command=${command}`, type, formData, data.canCancel); } diff --git a/src/libs/shouldUseStagingServer/index.js b/src/libs/shouldUseStagingServer/index.js deleted file mode 100644 index 745dd03b4489..000000000000 --- 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 92baff89c835..000000000000 --- 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/web/proxy.js b/web/proxy.js index 768963d0810c..4dc1a1018c13 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 = new URL(process.env.EXPENSIFY_URL || 'https://www.expensify.com').hostname; +const stagingHost = new URL(process.env.STAGING_EXPENSIFY_URL || 'https://staging.expensify.com').hostname; +const stagingSecureHost = new URL(process.env.STAGING_SECURE_EXPENSIFY_URL || 'https://staging-secure.expensify.com').hostname; // 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,35 @@ 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 + */ + 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,