From 65aefcd45b1caa253e0eac406d7c56d25fe19f16 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 8 Jun 2020 11:46:38 +0200 Subject: [PATCH 1/3] Replace getConfig helper to read value from process.env --- client/components/Nav.jsx | 13 ++++----- client/modules/App/App.jsx | 5 ++-- client/modules/IDE/actions/uploader.js | 5 ++-- client/modules/IDE/components/AssetSize.jsx | 5 ++-- .../modules/IDE/components/FileUploader.jsx | 6 ++-- .../IDE/components/UploadFileModal.jsx | 4 +-- client/modules/IDE/selectors/users.js | 4 +-- client/store.js | 5 ++-- client/utils/getConfig.js | 17 +++++++++++ client/utils/getConfig.test.js | 28 +++++++++++++++++++ 10 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 client/utils/getConfig.js create mode 100644 client/utils/getConfig.test.js diff --git a/client/components/Nav.jsx b/client/components/Nav.jsx index 22305b3a..dd7c800e 100644 --- a/client/components/Nav.jsx +++ b/client/components/Nav.jsx @@ -10,14 +10,13 @@ import * as projectActions from '../modules/IDE/actions/project'; import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences'; import { logoutUser } from '../modules/User/actions'; +import getConfig from '../utils/getConfig'; import { metaKeyName, } from '../utils/metaKey'; import CaretLeftIcon from '../images/left-arrow.svg'; import TriangleIcon from '../images/down-filled-triangle.svg'; import LogoIcon from '../images/p5js-logo-small.svg'; -const __process = (typeof global !== 'undefined' ? global : window).process; - class Nav extends React.PureComponent { constructor(props) { super(props); @@ -272,7 +271,7 @@ class Nav extends React.PureComponent { New - { __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) && + { getConfig('LOGIN_ENABLED') && (!this.props.project.owner || this.isUserOwner()) &&
  • } - {__process.env.UI_COLLECTIONS_ENABLED && + {getConfig('UI_COLLECTIONS_ENABLED') && this.props.user.authenticated && this.props.project.id &&
  • @@ -337,7 +336,7 @@ class Nav extends React.PureComponent { Add to Collection
  • } - { __process.env.EXAMPLES_ENABLED && + { getConfig('EXAMPLES_ENABLED') &&
  • - {__process.env.UI_COLLECTIONS_ENABLED && + {getConfig('UI_COLLECTIONS_ENABLED') &&
  • - {this.state.isMounted && !window.devToolsExtension && __process.env.NODE_ENV === 'development' && } + {this.state.isMounted && !window.devToolsExtension && getConfig('NODE_ENV') === 'development' && } {this.props.children} ); diff --git a/client/modules/IDE/actions/uploader.js b/client/modules/IDE/actions/uploader.js index c7a0139f..c3518630 100644 --- a/client/modules/IDE/actions/uploader.js +++ b/client/modules/IDE/actions/uploader.js @@ -1,10 +1,11 @@ import axios from 'axios'; +import getConfig from '../../../utils/getConfig'; import { createFile } from './files'; import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils'; const __process = (typeof global !== 'undefined' ? global : window).process; -const s3BucketHttps = __process.env.S3_BUCKET_URL_BASE || - `https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`; +const s3BucketHttps = getConfig('S3_BUCKET_URL_BASE') || + `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`; const ROOT_URL = __process.env.API_URL; const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB diff --git a/client/modules/IDE/components/AssetSize.jsx b/client/modules/IDE/components/AssetSize.jsx index 2e4c1282..cf2356e2 100644 --- a/client/modules/IDE/components/AssetSize.jsx +++ b/client/modules/IDE/components/AssetSize.jsx @@ -3,8 +3,9 @@ import React from 'react'; import { connect } from 'react-redux'; import prettyBytes from 'pretty-bytes'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const limit = __process.env.UPLOAD_LIMIT || 250000000; +import getConfig from '../../../utils/getConfig'; + +const limit = getConfig('UPLOAD_LIMIT') || 250000000; const MAX_SIZE_B = limit; const formatPercent = (percent) => { diff --git a/client/modules/IDE/components/FileUploader.jsx b/client/modules/IDE/components/FileUploader.jsx index c9515f5c..e2e6e509 100644 --- a/client/modules/IDE/components/FileUploader.jsx +++ b/client/modules/IDE/components/FileUploader.jsx @@ -4,11 +4,11 @@ import Dropzone from 'dropzone'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as UploaderActions from '../actions/uploader'; +import getConfig from '../../../utils/getConfig'; import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const s3Bucket = __process.env.S3_BUCKET_URL_BASE || - `https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`; +const s3Bucket = getConfig('S3_BUCKET_URL_BASE') || + `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`; class FileUploader extends React.Component { componentDidMount() { diff --git a/client/modules/IDE/components/UploadFileModal.jsx b/client/modules/IDE/components/UploadFileModal.jsx index 27fa7c6f..ff7e9c2d 100644 --- a/client/modules/IDE/components/UploadFileModal.jsx +++ b/client/modules/IDE/components/UploadFileModal.jsx @@ -3,12 +3,12 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Link } from 'react-router'; import prettyBytes from 'pretty-bytes'; +import getConfig from '../../../utils/getConfig'; import FileUploader from './FileUploader'; import { getreachedTotalSizeLimit } from '../selectors/users'; import ExitIcon from '../../../images/exit.svg'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const limit = __process.env.UPLOAD_LIMIT || 250000000; +const limit = getConfig('UPLOAD_LIMIT') || 250000000; const limitText = prettyBytes(limit); class UploadFileModal extends React.Component { diff --git a/client/modules/IDE/selectors/users.js b/client/modules/IDE/selectors/users.js index 556d99dd..eabf2e5c 100644 --- a/client/modules/IDE/selectors/users.js +++ b/client/modules/IDE/selectors/users.js @@ -1,10 +1,10 @@ import { createSelector } from 'reselect'; +import getConfig from '../../../utils/getConfig'; -const __process = (typeof global !== 'undefined' ? global : window).process; const getAuthenticated = state => state.user.authenticated; const getTotalSize = state => state.user.totalSize; const getAssetsTotalSize = state => state.assets.totalSize; -const limit = __process.env.UPLOAD_LIMIT || 250000000; +const limit = getConfig('UPLOAD_LIMIT') || 250000000; export const getCanUploadMedia = createSelector( getAuthenticated, diff --git a/client/store.js b/client/store.js index dbb3685e..a8d2114e 100644 --- a/client/store.js +++ b/client/store.js @@ -3,15 +3,14 @@ import thunk from 'redux-thunk'; import DevTools from './modules/App/components/DevTools'; import rootReducer from './reducers'; import { clearState, loadState } from './persistState'; - -const __process = (typeof global !== 'undefined' ? global : window).process; +import getConfig from './utils/getConfig'; export default function configureStore(initialState) { const enhancers = [ applyMiddleware(thunk), ]; - if (__process.env.CLIENT && __process.env.NODE_ENV === 'development') { + if (getConfig('CLIENT') && getConfig('NODE_ENV') === 'development') { // Enable DevTools only when rendering on client and during development. enhancers.push(window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument()); } diff --git a/client/utils/getConfig.js b/client/utils/getConfig.js new file mode 100644 index 00000000..3d8331a2 --- /dev/null +++ b/client/utils/getConfig.js @@ -0,0 +1,17 @@ +/** + * Returns config item from environment + */ +export default function getConfig(key) { + if (key == null) { + throw new Error('"key" must be provided to getConfig()'); + } + + const __process = (typeof global !== 'undefined' ? global : window).process; + const value = __process.env[key]; + + if (value == null) { + console.warn(`getConfig("${key}") returned null`); + } + + return value; +} diff --git a/client/utils/getConfig.test.js b/client/utils/getConfig.test.js new file mode 100644 index 00000000..05659cae --- /dev/null +++ b/client/utils/getConfig.test.js @@ -0,0 +1,28 @@ +import getConfig from './getConfig'; + +describe('utils/getConfig()', () => { + beforeEach(() => { + delete global.process.env.CONFIG_TEST_KEY_NAME; + delete window.process.env.CONFIG_TEST_KEY_NAME; + }); + + it('throws if key is not defined', () => { + expect(() => getConfig(/* key is missing */)).toThrow(/must be provided/); + }); + + it('fetches from global.process', () => { + global.process.env.CONFIG_TEST_KEY_NAME = 'editor.p5js.org'; + + expect(getConfig('CONFIG_TEST_KEY_NAME')).toBe('editor.p5js.org'); + }); + + it('fetches from window.process', () => { + window.process.env.CONFIG_TEST_KEY_NAME = 'editor.p5js.org'; + + expect(getConfig('CONFIG_TEST_KEY_NAME')).toBe('editor.p5js.org'); + }); + + it('warns but does not throw if no value found', () => { + expect(() => getConfig('CONFIG_TEST_KEY_NAME')).not.toThrow(); + }); +}); From a225d28f7553f3732730930b2d3ae625e3c74e9b Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 8 Jun 2020 12:29:24 +0200 Subject: [PATCH 2/3] Use apiClient instance instead of directly calling Axios Reduces the amount of duplication and provides a single place where we can configure base URL, crendentials and other headers --- client/modules/IDE/actions/assets.js | 9 +++---- client/modules/IDE/actions/collections.js | 30 ++++++++++----------- client/modules/IDE/actions/files.js | 10 +++---- client/modules/IDE/actions/preferences.js | 7 ++--- client/modules/IDE/actions/project.js | 22 ++++++++-------- client/modules/IDE/actions/projects.js | 11 +++----- client/modules/IDE/actions/uploader.js | 21 +++++---------- client/modules/User/actions.js | 32 ++++++++++------------- client/modules/User/pages/AccountView.jsx | 7 ++--- client/modules/User/pages/SignupView.jsx | 7 ++--- client/utils/apiClient.js | 17 ++++++++++++ 11 files changed, 80 insertions(+), 93 deletions(-) create mode 100644 client/utils/apiClient.js diff --git a/client/modules/IDE/actions/assets.js b/client/modules/IDE/actions/assets.js index 483e6d4e..79df4285 100644 --- a/client/modules/IDE/actions/assets.js +++ b/client/modules/IDE/actions/assets.js @@ -1,10 +1,7 @@ -import axios from 'axios'; +import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from './loader'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - function setAssets(assets, totalSize) { return { type: ActionTypes.SET_ASSETS, @@ -16,7 +13,7 @@ function setAssets(assets, totalSize) { export function getAssets() { return (dispatch) => { dispatch(startLoader()); - axios.get(`${ROOT_URL}/S3/objects`, { withCredentials: true }) + apiClient.get('/S3/objects') .then((response) => { dispatch(setAssets(response.data.assets, response.data.totalSize)); dispatch(stopLoader()); @@ -39,7 +36,7 @@ export function deleteAsset(assetKey) { export function deleteAssetRequest(assetKey) { return (dispatch) => { - axios.delete(`${ROOT_URL}/S3/${assetKey}`, { withCredentials: true }) + apiClient.delete(`/S3/${assetKey}`) .then((response) => { dispatch(deleteAsset(assetKey)); }) diff --git a/client/modules/IDE/actions/collections.js b/client/modules/IDE/actions/collections.js index 03cf2a64..3aa954ac 100644 --- a/client/modules/IDE/actions/collections.js +++ b/client/modules/IDE/actions/collections.js @@ -1,11 +1,9 @@ -import axios from 'axios'; import { browserHistory } from 'react-router'; +import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from './loader'; import { setToastText, showToast } from './toast'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; const TOAST_DISPLAY_TIME_MS = 1500; @@ -15,11 +13,11 @@ export function getCollections(username) { dispatch(startLoader()); let url; if (username) { - url = `${ROOT_URL}/${username}/collections`; + url = `/${username}/collections`; } else { - url = `${ROOT_URL}/collections`; + url = '/collections'; } - axios.get(url, { withCredentials: true }) + apiClient.get(url) .then((response) => { dispatch({ type: ActionTypes.SET_COLLECTIONS, @@ -41,8 +39,8 @@ export function getCollections(username) { export function createCollection(collection) { return (dispatch) => { dispatch(startLoader()); - const url = `${ROOT_URL}/collections`; - return axios.post(url, collection, { withCredentials: true }) + const url = '/collections'; + return apiClient.post(url, collection) .then((response) => { dispatch({ type: ActionTypes.CREATE_COLLECTION @@ -73,8 +71,8 @@ export function createCollection(collection) { export function addToCollection(collectionId, projectId) { return (dispatch) => { dispatch(startLoader()); - const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`; - return axios.post(url, { withCredentials: true }) + const url = `/collections/${collectionId}/${projectId}`; + return apiClient.post(url) .then((response) => { dispatch({ type: ActionTypes.ADD_TO_COLLECTION, @@ -105,8 +103,8 @@ export function addToCollection(collectionId, projectId) { export function removeFromCollection(collectionId, projectId) { return (dispatch) => { dispatch(startLoader()); - const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`; - return axios.delete(url, { withCredentials: true }) + const url = `/collections/${collectionId}/${projectId}`; + return apiClient.delete(url) .then((response) => { dispatch({ type: ActionTypes.REMOVE_FROM_COLLECTION, @@ -136,8 +134,8 @@ export function removeFromCollection(collectionId, projectId) { export function editCollection(collectionId, { name, description }) { return (dispatch) => { - const url = `${ROOT_URL}/collections/${collectionId}`; - return axios.patch(url, { name, description }, { withCredentials: true }) + const url = `/collections/${collectionId}`; + return apiClient.patch(url, { name, description }) .then((response) => { dispatch({ type: ActionTypes.EDIT_COLLECTION, @@ -159,8 +157,8 @@ export function editCollection(collectionId, { name, description }) { export function deleteCollection(collectionId) { return (dispatch) => { - const url = `${ROOT_URL}/collections/${collectionId}`; - return axios.delete(url, { withCredentials: true }) + const url = `/collections/${collectionId}`; + return apiClient.delete(url) .then((response) => { dispatch({ type: ActionTypes.DELETE_COLLECTION, diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js index 004600b2..e17e46c5 100644 --- a/client/modules/IDE/actions/files.js +++ b/client/modules/IDE/actions/files.js @@ -1,13 +1,11 @@ -import axios from 'axios'; import objectID from 'bson-objectid'; import blobUtil from 'blob-util'; import { reset } from 'redux-form'; +import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide'; import { setProjectSavedTime } from './project'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; function appendToFilename(filename, string) { const dotIndex = filename.lastIndexOf('.'); @@ -50,7 +48,7 @@ export function createFile(formProps) { parentId, children: [] }; - axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true }) + apiClient.post(`/projects/${state.project.id}/files`, postParams) .then((response) => { dispatch({ type: ActionTypes.CREATE_FILE, @@ -106,7 +104,7 @@ export function createFolder(formProps) { parentId, fileType: 'folder' }; - axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true }) + apiClient.post(`/projects/${state.project.id}/files`, postParams) .then((response) => { dispatch({ type: ActionTypes.CREATE_FILE, @@ -161,7 +159,7 @@ export function deleteFile(id, parentId) { parentId } }; - axios.delete(`${ROOT_URL}/projects/${state.project.id}/files/${id}`, deleteConfig, { withCredentials: true }) + apiClient.delete(`/projects/${state.project.id}/files/${id}`, deleteConfig) .then(() => { dispatch({ type: ActionTypes.DELETE_FILE, diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index 01ba077c..a182da76 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -1,11 +1,8 @@ -import axios from 'axios'; +import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - function updatePreferences(formParams, dispatch) { - axios.put(`${ROOT_URL}/preferences`, formParams, { withCredentials: true }) + apiClient.put('/preferences', formParams) .then(() => { }) .catch((error) => { diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index d42c307d..2e2bca0b 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -1,8 +1,9 @@ import { browserHistory } from 'react-router'; -import axios from 'axios'; import objectID from 'bson-objectid'; import each from 'async/each'; import isEqual from 'lodash/isEqual'; +import apiClient from '../../../utils/apiClient'; +import getConfig from '../../../utils/getConfig'; import * as ActionTypes from '../../../constants'; import { showToast, setToastText } from './toast'; import { @@ -14,8 +15,7 @@ import { } from './ide'; import { clearState, saveState } from '../../../persistState'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; +const ROOT_URL = getConfig('API_URL'); export function setProject(project) { return { @@ -52,7 +52,7 @@ export function setNewProject(project) { export function getProject(id, username) { return (dispatch, getState) => { dispatch(justOpenedProject()); - axios.get(`${ROOT_URL}/${username}/projects/${id}`, { withCredentials: true }) + apiClient.get(`/${username}/projects/${id}`) .then((response) => { dispatch(setProject(response.data)); dispatch(setUnsavedChanges(false)); @@ -142,7 +142,7 @@ export function saveProject(selectedFile = null, autosave = false) { fileToUpdate.content = selectedFile.content; } if (state.project.id) { - return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true }) + return apiClient.put(`/projects/${state.project.id}`, formParams) .then((response) => { dispatch(endSavingProject()); dispatch(setUnsavedChanges(false)); @@ -177,7 +177,7 @@ export function saveProject(selectedFile = null, autosave = false) { }); } - return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true }) + return apiClient.post('/projects', formParams) .then((response) => { dispatch(endSavingProject()); const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data); @@ -260,7 +260,7 @@ export function cloneProject(id) { if (!id) { resolve(getState()); } else { - fetch(`${ROOT_URL}/projects/${id}`) + apiClient.get(`/projects/${id}`) .then(res => res.json()) .then(data => resolve({ files: data.files, @@ -287,7 +287,7 @@ export function cloneProject(id) { const formParams = { url: file.url }; - axios.post(`${ROOT_URL}/S3/copy`, formParams, { withCredentials: true }) + apiClient.post('/S3/copy', formParams) .then((response) => { file.url = response.data.url; callback(null); @@ -298,7 +298,7 @@ export function cloneProject(id) { }, (err) => { // if not errors in duplicating the files on S3, then duplicate it const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles }); - axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true }) + apiClient.post('/projects', formParams) .then((response) => { browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`); dispatch(setNewProject(response.data)); @@ -337,7 +337,7 @@ export function setProjectSavedTime(updatedAt) { export function changeProjectName(id, newName) { return (dispatch, getState) => { const state = getState(); - axios.put(`${ROOT_URL}/projects/${id}`, { name: newName }, { withCredentials: true }) + apiClient.put(`/projects/${id}`, { name: newName }) .then((response) => { if (response.status === 200) { dispatch({ @@ -364,7 +364,7 @@ export function changeProjectName(id, newName) { export function deleteProject(id) { return (dispatch, getState) => { - axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true }) + apiClient.delete(`/projects/${id}`) .then(() => { const state = getState(); if (id === state.project.id) { diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js index eb653ec8..d41747b5 100644 --- a/client/modules/IDE/actions/projects.js +++ b/client/modules/IDE/actions/projects.js @@ -1,21 +1,18 @@ -import axios from 'axios'; +import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; import { startLoader, stopLoader } from './loader'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - // eslint-disable-next-line export function getProjects(username) { return (dispatch) => { dispatch(startLoader()); let url; if (username) { - url = `${ROOT_URL}/${username}/projects`; + url = `/${username}/projects`; } else { - url = `${ROOT_URL}/projects`; + url = '/projects'; } - axios.get(url, { withCredentials: true }) + apiClient.get(url) .then((response) => { dispatch({ type: ActionTypes.SET_PROJECTS, diff --git a/client/modules/IDE/actions/uploader.js b/client/modules/IDE/actions/uploader.js index c3518630..eca1ffe6 100644 --- a/client/modules/IDE/actions/uploader.js +++ b/client/modules/IDE/actions/uploader.js @@ -1,12 +1,10 @@ -import axios from 'axios'; +import apiClient from '../../../utils/apiClient'; import getConfig from '../../../utils/getConfig'; import { createFile } from './files'; import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils'; -const __process = (typeof global !== 'undefined' ? global : window).process; const s3BucketHttps = getConfig('S3_BUCKET_URL_BASE') || `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`; -const ROOT_URL = __process.env.API_URL; const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB function localIntercept(file, options = {}) { @@ -47,18 +45,13 @@ export function dropzoneAcceptCallback(userId, file, done) { }); } else { file.postData = []; // eslint-disable-line - axios.post( - `${ROOT_URL}/S3/sign`, { - name: file.name, - type: file.type, - size: file.size, - userId + apiClient.post('/S3/sign', { + name: file.name, + type: file.type, + size: file.size, + userId // _csrf: document.getElementById('__createPostToken').value - }, - { - withCredentials: true - } - ) + }) .then((response) => { file.custom_status = 'ready'; // eslint-disable-line file.postData = response.data; // eslint-disable-line diff --git a/client/modules/User/actions.js b/client/modules/User/actions.js index 19928549..dd54224d 100644 --- a/client/modules/User/actions.js +++ b/client/modules/User/actions.js @@ -1,13 +1,9 @@ import { browserHistory } from 'react-router'; -import axios from 'axios'; import * as ActionTypes from '../../constants'; +import apiClient from '../../utils/apiClient'; import { showErrorModal, justOpenedProject } from '../IDE/actions/ide'; import { showToast, setToastText } from '../IDE/actions/toast'; - -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - export function authError(error) { return { type: ActionTypes.AUTH_ERROR, @@ -17,7 +13,7 @@ export function authError(error) { export function signUpUser(previousPath, formValues) { return (dispatch) => { - axios.post(`${ROOT_URL}/signup`, formValues, { withCredentials: true }) + apiClient.post('/signup', formValues) .then((response) => { dispatch({ type: ActionTypes.AUTH_USER, @@ -34,7 +30,7 @@ export function signUpUser(previousPath, formValues) { } export function loginUser(formValues) { - return axios.post(`${ROOT_URL}/login`, formValues, { withCredentials: true }); + return apiClient.post('/login', formValues); } export function loginUserSuccess(user) { @@ -74,7 +70,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) { export function getUser() { return (dispatch) => { - axios.get(`${ROOT_URL}/session`, { withCredentials: true }) + apiClient.get('/session') .then((response) => { dispatch({ type: ActionTypes.AUTH_USER, @@ -95,7 +91,7 @@ export function getUser() { export function validateSession() { return (dispatch, getState) => { - axios.get(`${ROOT_URL}/session`, { withCredentials: true }) + apiClient.get('/session') .then((response) => { const state = getState(); if (state.user.username !== response.data.username) { @@ -113,7 +109,7 @@ export function validateSession() { export function logoutUser() { return (dispatch) => { - axios.get(`${ROOT_URL}/logout`, { withCredentials: true }) + apiClient.get('/logout') .then(() => { dispatch({ type: ActionTypes.UNAUTH_USER @@ -131,7 +127,7 @@ export function initiateResetPassword(formValues) { dispatch({ type: ActionTypes.RESET_PASSWORD_INITIATE }); - axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true }) + apiClient.post('/reset-password', formValues) .then(() => { // do nothing }) @@ -150,7 +146,7 @@ export function initiateVerification() { dispatch({ type: ActionTypes.EMAIL_VERIFICATION_INITIATE }); - axios.post(`${ROOT_URL}/verify/send`, {}, { withCredentials: true }) + apiClient.post('/verify/send', {}) .then(() => { // do nothing }) @@ -170,7 +166,7 @@ export function verifyEmailConfirmation(token) { type: ActionTypes.EMAIL_VERIFICATION_VERIFY, state: 'checking', }); - return axios.get(`${ROOT_URL}/verify?t=${token}`, {}, { withCredentials: true }) + return apiClient.get(`/verify?t=${token}`, {}) .then(response => dispatch({ type: ActionTypes.EMAIL_VERIFICATION_VERIFIED, message: response.data, @@ -194,7 +190,7 @@ export function resetPasswordReset() { export function validateResetPasswordToken(token) { return (dispatch) => { - axios.get(`${ROOT_URL}/reset-password/${token}`) + apiClient.get(`/reset-password/${token}`) .then(() => { // do nothing if the token is valid }) @@ -206,7 +202,7 @@ export function validateResetPasswordToken(token) { export function updatePassword(token, formValues) { return (dispatch) => { - axios.post(`${ROOT_URL}/reset-password/${token}`, formValues) + apiClient.post(`/reset-password/${token}`, formValues) .then((response) => { dispatch(loginUserSuccess(response.data)); browserHistory.push('/'); @@ -226,7 +222,7 @@ export function updateSettingsSuccess(user) { export function updateSettings(formValues) { return dispatch => - axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) + apiClient.put('/account', formValues) .then((response) => { dispatch(updateSettingsSuccess(response.data)); browserHistory.push('/'); @@ -248,7 +244,7 @@ export function createApiKeySuccess(user) { export function createApiKey(label) { return dispatch => - axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true }) + apiClient.post('/account/api-keys', { label }) .then((response) => { dispatch(createApiKeySuccess(response.data)); }) @@ -260,7 +256,7 @@ export function createApiKey(label) { export function removeApiKey(keyId) { return dispatch => - axios.delete(`${ROOT_URL}/account/api-keys/${keyId}`, { withCredentials: true }) + apiClient.delete(`/account/api-keys/${keyId}`) .then((response) => { dispatch({ type: ActionTypes.API_KEY_REMOVED, diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 23c126bc..fff8d331 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -3,18 +3,15 @@ import React from 'react'; import { reduxForm } from 'redux-form'; import { bindActionCreators } from 'redux'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; -import axios from 'axios'; import { Helmet } from 'react-helmet'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import AccountForm from '../components/AccountForm'; +import apiClient from '../../../utils/apiClient'; import { validateSettings } from '../../../utils/reduxFormUtils'; import SocialAuthButton from '../components/SocialAuthButton'; import APIKeyForm from '../components/APIKeyForm'; import Nav from '../../../components/Nav'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - function SocialLoginPanel(props) { return ( @@ -96,7 +93,7 @@ function asyncValidate(formProps, dispatch, props) { const queryParams = {}; queryParams[fieldToValidate] = formProps[fieldToValidate]; queryParams.check_type = fieldToValidate; - return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams }) + return apiClient.get('/signup/duplicate_check', { params: queryParams }) .then((response) => { if (response.data.exists) { const error = {}; diff --git a/client/modules/User/pages/SignupView.jsx b/client/modules/User/pages/SignupView.jsx index b51c6ac6..225bb8e4 100644 --- a/client/modules/User/pages/SignupView.jsx +++ b/client/modules/User/pages/SignupView.jsx @@ -1,19 +1,16 @@ import PropTypes from 'prop-types'; import React from 'react'; import { bindActionCreators } from 'redux'; -import axios from 'axios'; import { Link, browserHistory } from 'react-router'; import { Helmet } from 'react-helmet'; import { reduxForm } from 'redux-form'; import * as UserActions from '../actions'; import SignupForm from '../components/SignupForm'; +import apiClient from '../../../utils/apiClient'; import { validateSignup } from '../../../utils/reduxFormUtils'; import SocialAuthButton from '../components/SocialAuthButton'; import Nav from '../../../components/Nav'; -const __process = (typeof global !== 'undefined' ? global : window).process; -const ROOT_URL = __process.env.API_URL; - class SignupView extends React.Component { gotoHomePage = () => { browserHistory.push('/'); @@ -86,7 +83,7 @@ function asyncValidate(formProps, dispatch, props) { const queryParams = {}; queryParams[fieldToValidate] = formProps[fieldToValidate]; queryParams.check_type = fieldToValidate; - return axios.get(`${ROOT_URL}/signup/duplicate_check`, { params: queryParams }) + return apiClient.get('/signup/duplicate_check', { params: queryParams }) .then((response) => { if (response.data.exists) { errors[fieldToValidate] = response.data.message; diff --git a/client/utils/apiClient.js b/client/utils/apiClient.js new file mode 100644 index 00000000..a8347674 --- /dev/null +++ b/client/utils/apiClient.js @@ -0,0 +1,17 @@ +import axios from 'axios'; + +import getConfig from './getConfig'; + +const ROOT_URL = getConfig('API_URL'); + +/** + * Configures an Axios instance with the correct API URL + */ +function createClientInstance() { + return axios.create({ + baseURL: ROOT_URL, + withCredentials: true + }); +} + +export default createClientInstance(); From 6d90dd2071f81eb2f6e8f78bc3791988ce8d193d Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sat, 13 Jun 2020 12:51:26 +0200 Subject: [PATCH 3/3] Do not log in test environment --- client/utils/getConfig.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/client/utils/getConfig.js b/client/utils/getConfig.js index 3d8331a2..43c050ae 100644 --- a/client/utils/getConfig.js +++ b/client/utils/getConfig.js @@ -1,17 +1,24 @@ +function isTestEnvironment() { + // eslint-disable-next-line no-use-before-define + return getConfig('NODE_ENV', { warn: false }) === 'test'; +} + /** * Returns config item from environment */ -export default function getConfig(key) { +function getConfig(key, options = { warn: !isTestEnvironment() }) { if (key == null) { throw new Error('"key" must be provided to getConfig()'); } - const __process = (typeof global !== 'undefined' ? global : window).process; - const value = __process.env[key]; + const env = (typeof global !== 'undefined' ? global : window)?.process?.env || {}; + const value = env[key]; - if (value == null) { + if (value == null && options?.warn !== false) { console.warn(`getConfig("${key}") returned null`); } return value; } + +export default getConfig;