Merge pull request #1452 from andrewn/chore/config-handler

Helpers for config and API client
This commit is contained in:
Cassie Tarakajian 2020-06-15 12:55:41 -04:00 committed by GitHub
commit b805754d04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 155 additions and 117 deletions

View file

@ -10,14 +10,13 @@ import * as projectActions from '../modules/IDE/actions/project';
import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences'; import { setAllAccessibleOutput } from '../modules/IDE/actions/preferences';
import { logoutUser } from '../modules/User/actions'; import { logoutUser } from '../modules/User/actions';
import getConfig from '../utils/getConfig';
import { metaKeyName, } from '../utils/metaKey'; import { metaKeyName, } from '../utils/metaKey';
import CaretLeftIcon from '../images/left-arrow.svg'; import CaretLeftIcon from '../images/left-arrow.svg';
import TriangleIcon from '../images/down-filled-triangle.svg'; import TriangleIcon from '../images/down-filled-triangle.svg';
import LogoIcon from '../images/p5js-logo-small.svg'; import LogoIcon from '../images/p5js-logo-small.svg';
const __process = (typeof global !== 'undefined' ? global : window).process;
class Nav extends React.PureComponent { class Nav extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
@ -272,7 +271,7 @@ class Nav extends React.PureComponent {
New New
</button> </button>
</li> </li>
{ __process.env.LOGIN_ENABLED && (!this.props.project.owner || this.isUserOwner()) && { getConfig('LOGIN_ENABLED') && (!this.props.project.owner || this.isUserOwner()) &&
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
<button <button
onClick={this.handleSave} onClick={this.handleSave}
@ -324,7 +323,7 @@ class Nav extends React.PureComponent {
Open Open
</Link> </Link>
</li> } </li> }
{__process.env.UI_COLLECTIONS_ENABLED && {getConfig('UI_COLLECTIONS_ENABLED') &&
this.props.user.authenticated && this.props.user.authenticated &&
this.props.project.id && this.props.project.id &&
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
@ -337,7 +336,7 @@ class Nav extends React.PureComponent {
Add to Collection Add to Collection
</Link> </Link>
</li>} </li>}
{ __process.env.EXAMPLES_ENABLED && { getConfig('EXAMPLES_ENABLED') &&
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
<Link <Link
to="/p5/sketches" to="/p5/sketches"
@ -587,7 +586,7 @@ class Nav extends React.PureComponent {
My sketches My sketches
</Link> </Link>
</li> </li>
{__process.env.UI_COLLECTIONS_ENABLED && {getConfig('UI_COLLECTIONS_ENABLED') &&
<li className="nav__dropdown-item"> <li className="nav__dropdown-item">
<Link <Link
to={`/${this.props.user.username}/collections`} to={`/${this.props.user.username}/collections`}
@ -635,7 +634,7 @@ class Nav extends React.PureComponent {
} }
renderUserMenu(navDropdownState) { renderUserMenu(navDropdownState) {
const isLoginEnabled = __process.env.LOGIN_ENABLED; const isLoginEnabled = getConfig('LOGIN_ENABLED');
const isAuthenticated = this.props.user.authenticated; const isAuthenticated = this.props.user.authenticated;
if (isLoginEnabled && isAuthenticated) { if (isLoginEnabled && isAuthenticated) {

View file

@ -1,11 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import getConfig from '../../utils/getConfig';
import DevTools from './components/DevTools'; import DevTools from './components/DevTools';
import { setPreviousPath } from '../IDE/actions/ide'; import { setPreviousPath } from '../IDE/actions/ide';
const __process = (typeof global !== 'undefined' ? global : window).process;
class App extends React.Component { class App extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
@ -35,7 +34,7 @@ class App extends React.Component {
render() { render() {
return ( return (
<div className="app"> <div className="app">
{this.state.isMounted && !window.devToolsExtension && __process.env.NODE_ENV === 'development' && <DevTools />} {this.state.isMounted && !window.devToolsExtension && getConfig('NODE_ENV') === 'development' && <DevTools />}
{this.props.children} {this.props.children}
</div> </div>
); );

View file

@ -1,10 +1,7 @@
import axios from 'axios'; import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from './loader'; import { startLoader, stopLoader } from './loader';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function setAssets(assets, totalSize) { function setAssets(assets, totalSize) {
return { return {
type: ActionTypes.SET_ASSETS, type: ActionTypes.SET_ASSETS,
@ -16,7 +13,7 @@ function setAssets(assets, totalSize) {
export function getAssets() { export function getAssets() {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
axios.get(`${ROOT_URL}/S3/objects`, { withCredentials: true }) apiClient.get('/S3/objects')
.then((response) => { .then((response) => {
dispatch(setAssets(response.data.assets, response.data.totalSize)); dispatch(setAssets(response.data.assets, response.data.totalSize));
dispatch(stopLoader()); dispatch(stopLoader());
@ -39,7 +36,7 @@ export function deleteAsset(assetKey) {
export function deleteAssetRequest(assetKey) { export function deleteAssetRequest(assetKey) {
return (dispatch) => { return (dispatch) => {
axios.delete(`${ROOT_URL}/S3/${assetKey}`, { withCredentials: true }) apiClient.delete(`/S3/${assetKey}`)
.then((response) => { .then((response) => {
dispatch(deleteAsset(assetKey)); dispatch(deleteAsset(assetKey));
}) })

View file

@ -1,11 +1,9 @@
import axios from 'axios';
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from './loader'; import { startLoader, stopLoader } from './loader';
import { setToastText, showToast } from './toast'; 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; const TOAST_DISPLAY_TIME_MS = 1500;
@ -15,11 +13,11 @@ export function getCollections(username) {
dispatch(startLoader()); dispatch(startLoader());
let url; let url;
if (username) { if (username) {
url = `${ROOT_URL}/${username}/collections`; url = `/${username}/collections`;
} else { } else {
url = `${ROOT_URL}/collections`; url = '/collections';
} }
axios.get(url, { withCredentials: true }) apiClient.get(url)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.SET_COLLECTIONS, type: ActionTypes.SET_COLLECTIONS,
@ -41,8 +39,8 @@ export function getCollections(username) {
export function createCollection(collection) { export function createCollection(collection) {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
const url = `${ROOT_URL}/collections`; const url = '/collections';
return axios.post(url, collection, { withCredentials: true }) return apiClient.post(url, collection)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.CREATE_COLLECTION type: ActionTypes.CREATE_COLLECTION
@ -73,8 +71,8 @@ export function createCollection(collection) {
export function addToCollection(collectionId, projectId) { export function addToCollection(collectionId, projectId) {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`; const url = `/collections/${collectionId}/${projectId}`;
return axios.post(url, { withCredentials: true }) return apiClient.post(url)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.ADD_TO_COLLECTION, type: ActionTypes.ADD_TO_COLLECTION,
@ -105,8 +103,8 @@ export function addToCollection(collectionId, projectId) {
export function removeFromCollection(collectionId, projectId) { export function removeFromCollection(collectionId, projectId) {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`; const url = `/collections/${collectionId}/${projectId}`;
return axios.delete(url, { withCredentials: true }) return apiClient.delete(url)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.REMOVE_FROM_COLLECTION, type: ActionTypes.REMOVE_FROM_COLLECTION,
@ -136,8 +134,8 @@ export function removeFromCollection(collectionId, projectId) {
export function editCollection(collectionId, { name, description }) { export function editCollection(collectionId, { name, description }) {
return (dispatch) => { return (dispatch) => {
const url = `${ROOT_URL}/collections/${collectionId}`; const url = `/collections/${collectionId}`;
return axios.patch(url, { name, description }, { withCredentials: true }) return apiClient.patch(url, { name, description })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.EDIT_COLLECTION, type: ActionTypes.EDIT_COLLECTION,
@ -159,8 +157,8 @@ export function editCollection(collectionId, { name, description }) {
export function deleteCollection(collectionId) { export function deleteCollection(collectionId) {
return (dispatch) => { return (dispatch) => {
const url = `${ROOT_URL}/collections/${collectionId}`; const url = `/collections/${collectionId}`;
return axios.delete(url, { withCredentials: true }) return apiClient.delete(url)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.DELETE_COLLECTION, type: ActionTypes.DELETE_COLLECTION,

View file

@ -1,13 +1,11 @@
import axios from 'axios';
import objectID from 'bson-objectid'; import objectID from 'bson-objectid';
import blobUtil from 'blob-util'; import blobUtil from 'blob-util';
import { reset } from 'redux-form'; import { reset } from 'redux-form';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide'; import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide';
import { setProjectSavedTime } from './project'; import { setProjectSavedTime } from './project';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function appendToFilename(filename, string) { function appendToFilename(filename, string) {
const dotIndex = filename.lastIndexOf('.'); const dotIndex = filename.lastIndexOf('.');
@ -50,7 +48,7 @@ export function createFile(formProps) {
parentId, parentId,
children: [] children: []
}; };
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true }) apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.CREATE_FILE, type: ActionTypes.CREATE_FILE,
@ -106,7 +104,7 @@ export function createFolder(formProps) {
parentId, parentId,
fileType: 'folder' fileType: 'folder'
}; };
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true }) apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.CREATE_FILE, type: ActionTypes.CREATE_FILE,
@ -161,7 +159,7 @@ export function deleteFile(id, parentId) {
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(() => { .then(() => {
dispatch({ dispatch({
type: ActionTypes.DELETE_FILE, type: ActionTypes.DELETE_FILE,

View file

@ -1,11 +1,8 @@
import axios from 'axios'; import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function updatePreferences(formParams, dispatch) { function updatePreferences(formParams, dispatch) {
axios.put(`${ROOT_URL}/preferences`, formParams, { withCredentials: true }) apiClient.put('/preferences', formParams)
.then(() => { .then(() => {
}) })
.catch((error) => { .catch((error) => {

View file

@ -1,8 +1,9 @@
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
import axios from 'axios';
import objectID from 'bson-objectid'; import objectID from 'bson-objectid';
import each from 'async/each'; import each from 'async/each';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
import apiClient from '../../../utils/apiClient';
import getConfig from '../../../utils/getConfig';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { showToast, setToastText } from './toast'; import { showToast, setToastText } from './toast';
import { import {
@ -14,8 +15,7 @@ import {
} from './ide'; } from './ide';
import { clearState, saveState } from '../../../persistState'; import { clearState, saveState } from '../../../persistState';
const __process = (typeof global !== 'undefined' ? global : window).process; const ROOT_URL = getConfig('API_URL');
const ROOT_URL = __process.env.API_URL;
export function setProject(project) { export function setProject(project) {
return { return {
@ -52,7 +52,7 @@ export function setNewProject(project) {
export function getProject(id, username) { export function getProject(id, username) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(justOpenedProject()); dispatch(justOpenedProject());
axios.get(`${ROOT_URL}/${username}/projects/${id}`, { withCredentials: true }) apiClient.get(`/${username}/projects/${id}`)
.then((response) => { .then((response) => {
dispatch(setProject(response.data)); dispatch(setProject(response.data));
dispatch(setUnsavedChanges(false)); dispatch(setUnsavedChanges(false));
@ -142,7 +142,7 @@ export function saveProject(selectedFile = null, autosave = false) {
fileToUpdate.content = selectedFile.content; fileToUpdate.content = selectedFile.content;
} }
if (state.project.id) { 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) => { .then((response) => {
dispatch(endSavingProject()); dispatch(endSavingProject());
dispatch(setUnsavedChanges(false)); 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) => { .then((response) => {
dispatch(endSavingProject()); dispatch(endSavingProject());
const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data); const { hasChanges, synchedProject } = getSynchedProject(getState(), response.data);
@ -260,7 +260,7 @@ export function cloneProject(id) {
if (!id) { if (!id) {
resolve(getState()); resolve(getState());
} else { } else {
fetch(`${ROOT_URL}/projects/${id}`) apiClient.get(`/projects/${id}`)
.then(res => res.json()) .then(res => res.json())
.then(data => resolve({ .then(data => resolve({
files: data.files, files: data.files,
@ -287,7 +287,7 @@ export function cloneProject(id) {
const formParams = { const formParams = {
url: file.url url: file.url
}; };
axios.post(`${ROOT_URL}/S3/copy`, formParams, { withCredentials: true }) apiClient.post('/S3/copy', formParams)
.then((response) => { .then((response) => {
file.url = response.data.url; file.url = response.data.url;
callback(null); callback(null);
@ -298,7 +298,7 @@ export function cloneProject(id) {
}, (err) => { }, (err) => {
// if not errors in duplicating the files on S3, then duplicate it // if not errors in duplicating the files on S3, then duplicate it
const formParams = Object.assign({}, { name: `${state.project.name} copy` }, { files: newFiles }); 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) => { .then((response) => {
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`); browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch(setNewProject(response.data)); dispatch(setNewProject(response.data));
@ -337,7 +337,7 @@ export function setProjectSavedTime(updatedAt) {
export function changeProjectName(id, newName) { export function changeProjectName(id, newName) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
axios.put(`${ROOT_URL}/projects/${id}`, { name: newName }, { withCredentials: true }) apiClient.put(`/projects/${id}`, { name: newName })
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
dispatch({ dispatch({
@ -364,7 +364,7 @@ export function changeProjectName(id, newName) {
export function deleteProject(id) { export function deleteProject(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true }) apiClient.delete(`/projects/${id}`)
.then(() => { .then(() => {
const state = getState(); const state = getState();
if (id === state.project.id) { if (id === state.project.id) {

View file

@ -1,21 +1,18 @@
import axios from 'axios'; import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants'; import * as ActionTypes from '../../../constants';
import { startLoader, stopLoader } from './loader'; import { startLoader, stopLoader } from './loader';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
// eslint-disable-next-line // eslint-disable-next-line
export function getProjects(username) { export function getProjects(username) {
return (dispatch) => { return (dispatch) => {
dispatch(startLoader()); dispatch(startLoader());
let url; let url;
if (username) { if (username) {
url = `${ROOT_URL}/${username}/projects`; url = `/${username}/projects`;
} else { } else {
url = `${ROOT_URL}/projects`; url = '/projects';
} }
axios.get(url, { withCredentials: true }) apiClient.get(url)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.SET_PROJECTS, type: ActionTypes.SET_PROJECTS,

View file

@ -1,11 +1,10 @@
import axios from 'axios'; import apiClient from '../../../utils/apiClient';
import getConfig from '../../../utils/getConfig';
import { createFile } from './files'; import { createFile } from './files';
import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils'; import { TEXT_FILE_REGEX } from '../../../../server/utils/fileUtils';
const __process = (typeof global !== 'undefined' ? global : window).process; const s3BucketHttps = getConfig('S3_BUCKET_URL_BASE') ||
const s3BucketHttps = __process.env.S3_BUCKET_URL_BASE || `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`;
`https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`;
const ROOT_URL = __process.env.API_URL;
const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB
function localIntercept(file, options = {}) { function localIntercept(file, options = {}) {
@ -46,18 +45,13 @@ export function dropzoneAcceptCallback(userId, file, done) {
}); });
} else { } else {
file.postData = []; // eslint-disable-line file.postData = []; // eslint-disable-line
axios.post( apiClient.post('/S3/sign', {
`${ROOT_URL}/S3/sign`, { name: file.name,
name: file.name, type: file.type,
type: file.type, size: file.size,
size: file.size, userId
userId
// _csrf: document.getElementById('__createPostToken').value // _csrf: document.getElementById('__createPostToken').value
}, })
{
withCredentials: true
}
)
.then((response) => { .then((response) => {
file.custom_status = 'ready'; // eslint-disable-line file.custom_status = 'ready'; // eslint-disable-line
file.postData = response.data; // eslint-disable-line file.postData = response.data; // eslint-disable-line

View file

@ -3,8 +3,9 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
const __process = (typeof global !== 'undefined' ? global : window).process; import getConfig from '../../../utils/getConfig';
const limit = __process.env.UPLOAD_LIMIT || 250000000;
const limit = getConfig('UPLOAD_LIMIT') || 250000000;
const MAX_SIZE_B = limit; const MAX_SIZE_B = limit;
const formatPercent = (percent) => { const formatPercent = (percent) => {

View file

@ -4,11 +4,11 @@ import Dropzone from 'dropzone';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as UploaderActions from '../actions/uploader'; import * as UploaderActions from '../actions/uploader';
import getConfig from '../../../utils/getConfig';
import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils'; import { fileExtensionsAndMimeTypes } from '../../../../server/utils/fileUtils';
const __process = (typeof global !== 'undefined' ? global : window).process; const s3Bucket = getConfig('S3_BUCKET_URL_BASE') ||
const s3Bucket = __process.env.S3_BUCKET_URL_BASE || `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`;
`https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`;
class FileUploader extends React.Component { class FileUploader extends React.Component {
componentDidMount() { componentDidMount() {

View file

@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link } from 'react-router'; import { Link } from 'react-router';
import prettyBytes from 'pretty-bytes'; import prettyBytes from 'pretty-bytes';
import getConfig from '../../../utils/getConfig';
import FileUploader from './FileUploader'; import FileUploader from './FileUploader';
import { getreachedTotalSizeLimit } from '../selectors/users'; import { getreachedTotalSizeLimit } from '../selectors/users';
import ExitIcon from '../../../images/exit.svg'; import ExitIcon from '../../../images/exit.svg';
const __process = (typeof global !== 'undefined' ? global : window).process; const limit = getConfig('UPLOAD_LIMIT') || 250000000;
const limit = __process.env.UPLOAD_LIMIT || 250000000;
const limitText = prettyBytes(limit); const limitText = prettyBytes(limit);
class UploadFileModal extends React.Component { class UploadFileModal extends React.Component {

View file

@ -1,10 +1,10 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import getConfig from '../../../utils/getConfig';
const __process = (typeof global !== 'undefined' ? global : window).process;
const getAuthenticated = state => state.user.authenticated; const getAuthenticated = state => state.user.authenticated;
const getTotalSize = state => state.user.totalSize; const getTotalSize = state => state.user.totalSize;
const getAssetsTotalSize = state => state.assets.totalSize; const getAssetsTotalSize = state => state.assets.totalSize;
const limit = __process.env.UPLOAD_LIMIT || 250000000; const limit = getConfig('UPLOAD_LIMIT') || 250000000;
export const getCanUploadMedia = createSelector( export const getCanUploadMedia = createSelector(
getAuthenticated, getAuthenticated,

View file

@ -1,13 +1,9 @@
import { browserHistory } from 'react-router'; import { browserHistory } from 'react-router';
import axios from 'axios';
import * as ActionTypes from '../../constants'; import * as ActionTypes from '../../constants';
import apiClient from '../../utils/apiClient';
import { showErrorModal, justOpenedProject } from '../IDE/actions/ide'; import { showErrorModal, justOpenedProject } from '../IDE/actions/ide';
import { showToast, setToastText } from '../IDE/actions/toast'; 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) { export function authError(error) {
return { return {
type: ActionTypes.AUTH_ERROR, type: ActionTypes.AUTH_ERROR,
@ -17,7 +13,7 @@ export function authError(error) {
export function signUpUser(previousPath, formValues) { export function signUpUser(previousPath, formValues) {
return (dispatch) => { return (dispatch) => {
axios.post(`${ROOT_URL}/signup`, formValues, { withCredentials: true }) apiClient.post('/signup', formValues)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.AUTH_USER, type: ActionTypes.AUTH_USER,
@ -34,7 +30,7 @@ export function signUpUser(previousPath, formValues) {
} }
export function loginUser(formValues) { export function loginUser(formValues) {
return axios.post(`${ROOT_URL}/login`, formValues, { withCredentials: true }); return apiClient.post('/login', formValues);
} }
export function loginUserSuccess(user) { export function loginUserSuccess(user) {
@ -74,7 +70,7 @@ export function validateAndLoginUser(previousPath, formProps, dispatch) {
export function getUser() { export function getUser() {
return (dispatch) => { return (dispatch) => {
axios.get(`${ROOT_URL}/session`, { withCredentials: true }) apiClient.get('/session')
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.AUTH_USER, type: ActionTypes.AUTH_USER,
@ -95,7 +91,7 @@ export function getUser() {
export function validateSession() { export function validateSession() {
return (dispatch, getState) => { return (dispatch, getState) => {
axios.get(`${ROOT_URL}/session`, { withCredentials: true }) apiClient.get('/session')
.then((response) => { .then((response) => {
const state = getState(); const state = getState();
if (state.user.username !== response.data.username) { if (state.user.username !== response.data.username) {
@ -113,7 +109,7 @@ export function validateSession() {
export function logoutUser() { export function logoutUser() {
return (dispatch) => { return (dispatch) => {
axios.get(`${ROOT_URL}/logout`, { withCredentials: true }) apiClient.get('/logout')
.then(() => { .then(() => {
dispatch({ dispatch({
type: ActionTypes.UNAUTH_USER type: ActionTypes.UNAUTH_USER
@ -131,7 +127,7 @@ export function initiateResetPassword(formValues) {
dispatch({ dispatch({
type: ActionTypes.RESET_PASSWORD_INITIATE type: ActionTypes.RESET_PASSWORD_INITIATE
}); });
axios.post(`${ROOT_URL}/reset-password`, formValues, { withCredentials: true }) apiClient.post('/reset-password', formValues)
.then(() => { .then(() => {
// do nothing // do nothing
}) })
@ -150,7 +146,7 @@ export function initiateVerification() {
dispatch({ dispatch({
type: ActionTypes.EMAIL_VERIFICATION_INITIATE type: ActionTypes.EMAIL_VERIFICATION_INITIATE
}); });
axios.post(`${ROOT_URL}/verify/send`, {}, { withCredentials: true }) apiClient.post('/verify/send', {})
.then(() => { .then(() => {
// do nothing // do nothing
}) })
@ -170,7 +166,7 @@ export function verifyEmailConfirmation(token) {
type: ActionTypes.EMAIL_VERIFICATION_VERIFY, type: ActionTypes.EMAIL_VERIFICATION_VERIFY,
state: 'checking', state: 'checking',
}); });
return axios.get(`${ROOT_URL}/verify?t=${token}`, {}, { withCredentials: true }) return apiClient.get(`/verify?t=${token}`, {})
.then(response => dispatch({ .then(response => dispatch({
type: ActionTypes.EMAIL_VERIFICATION_VERIFIED, type: ActionTypes.EMAIL_VERIFICATION_VERIFIED,
message: response.data, message: response.data,
@ -194,7 +190,7 @@ export function resetPasswordReset() {
export function validateResetPasswordToken(token) { export function validateResetPasswordToken(token) {
return (dispatch) => { return (dispatch) => {
axios.get(`${ROOT_URL}/reset-password/${token}`) apiClient.get(`/reset-password/${token}`)
.then(() => { .then(() => {
// do nothing if the token is valid // do nothing if the token is valid
}) })
@ -206,7 +202,7 @@ export function validateResetPasswordToken(token) {
export function updatePassword(token, formValues) { export function updatePassword(token, formValues) {
return (dispatch) => { return (dispatch) => {
axios.post(`${ROOT_URL}/reset-password/${token}`, formValues) apiClient.post(`/reset-password/${token}`, formValues)
.then((response) => { .then((response) => {
dispatch(loginUserSuccess(response.data)); dispatch(loginUserSuccess(response.data));
browserHistory.push('/'); browserHistory.push('/');
@ -226,7 +222,7 @@ export function updateSettingsSuccess(user) {
export function updateSettings(formValues) { export function updateSettings(formValues) {
return dispatch => return dispatch =>
axios.put(`${ROOT_URL}/account`, formValues, { withCredentials: true }) apiClient.put('/account', formValues)
.then((response) => { .then((response) => {
dispatch(updateSettingsSuccess(response.data)); dispatch(updateSettingsSuccess(response.data));
browserHistory.push('/'); browserHistory.push('/');
@ -248,7 +244,7 @@ export function createApiKeySuccess(user) {
export function createApiKey(label) { export function createApiKey(label) {
return dispatch => return dispatch =>
axios.post(`${ROOT_URL}/account/api-keys`, { label }, { withCredentials: true }) apiClient.post('/account/api-keys', { label })
.then((response) => { .then((response) => {
dispatch(createApiKeySuccess(response.data)); dispatch(createApiKeySuccess(response.data));
}) })
@ -260,7 +256,7 @@ export function createApiKey(label) {
export function removeApiKey(keyId) { export function removeApiKey(keyId) {
return dispatch => return dispatch =>
axios.delete(`${ROOT_URL}/account/api-keys/${keyId}`, { withCredentials: true }) apiClient.delete(`/account/api-keys/${keyId}`)
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: ActionTypes.API_KEY_REMOVED, type: ActionTypes.API_KEY_REMOVED,

View file

@ -3,18 +3,15 @@ import React from 'react';
import { reduxForm } from 'redux-form'; import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import axios from 'axios';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions'; import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
import AccountForm from '../components/AccountForm'; import AccountForm from '../components/AccountForm';
import apiClient from '../../../utils/apiClient';
import { validateSettings } from '../../../utils/reduxFormUtils'; import { validateSettings } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton'; import SocialAuthButton from '../components/SocialAuthButton';
import APIKeyForm from '../components/APIKeyForm'; import APIKeyForm from '../components/APIKeyForm';
import Nav from '../../../components/Nav'; import Nav from '../../../components/Nav';
const __process = (typeof global !== 'undefined' ? global : window).process;
const ROOT_URL = __process.env.API_URL;
function SocialLoginPanel(props) { function SocialLoginPanel(props) {
return ( return (
<React.Fragment> <React.Fragment>
@ -96,7 +93,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {}; const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate]; queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = 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) => { .then((response) => {
if (response.data.exists) { if (response.data.exists) {
const error = {}; const error = {};

View file

@ -1,19 +1,16 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import axios from 'axios';
import { Link, browserHistory } from 'react-router'; import { Link, browserHistory } from 'react-router';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { reduxForm } from 'redux-form'; import { reduxForm } from 'redux-form';
import * as UserActions from '../actions'; import * as UserActions from '../actions';
import SignupForm from '../components/SignupForm'; import SignupForm from '../components/SignupForm';
import apiClient from '../../../utils/apiClient';
import { validateSignup } from '../../../utils/reduxFormUtils'; import { validateSignup } from '../../../utils/reduxFormUtils';
import SocialAuthButton from '../components/SocialAuthButton'; import SocialAuthButton from '../components/SocialAuthButton';
import Nav from '../../../components/Nav'; 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 { class SignupView extends React.Component {
gotoHomePage = () => { gotoHomePage = () => {
browserHistory.push('/'); browserHistory.push('/');
@ -86,7 +83,7 @@ function asyncValidate(formProps, dispatch, props) {
const queryParams = {}; const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate]; queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = 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) => { .then((response) => {
if (response.data.exists) { if (response.data.exists) {
errors[fieldToValidate] = response.data.message; errors[fieldToValidate] = response.data.message;

View file

@ -3,15 +3,14 @@ import thunk from 'redux-thunk';
import DevTools from './modules/App/components/DevTools'; import DevTools from './modules/App/components/DevTools';
import rootReducer from './reducers'; import rootReducer from './reducers';
import { clearState, loadState } from './persistState'; import { clearState, loadState } from './persistState';
import getConfig from './utils/getConfig';
const __process = (typeof global !== 'undefined' ? global : window).process;
export default function configureStore(initialState) { export default function configureStore(initialState) {
const enhancers = [ const enhancers = [
applyMiddleware(thunk), 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. // Enable DevTools only when rendering on client and during development.
enhancers.push(window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument()); enhancers.push(window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument());
} }

17
client/utils/apiClient.js Normal file
View file

@ -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();

24
client/utils/getConfig.js Normal file
View file

@ -0,0 +1,24 @@
function isTestEnvironment() {
// eslint-disable-next-line no-use-before-define
return getConfig('NODE_ENV', { warn: false }) === 'test';
}
/**
* Returns config item from environment
*/
function getConfig(key, options = { warn: !isTestEnvironment() }) {
if (key == null) {
throw new Error('"key" must be provided to getConfig()');
}
const env = (typeof global !== 'undefined' ? global : window)?.process?.env || {};
const value = env[key];
if (value == null && options?.warn !== false) {
console.warn(`getConfig("${key}") returned null`);
}
return value;
}
export default getConfig;

View file

@ -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();
});
});