diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 82c461c7..93c4b27b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ -custom: https://processingfoundation.org/support +github: processing +custom: https://processingfoundation.org/ 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/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 c7a0139f..eca1ffe6 100644 --- a/client/modules/IDE/actions/uploader.js +++ b/client/modules/IDE/actions/uploader.js @@ -1,11 +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 = __process.env.S3_BUCKET_URL_BASE || - `https://s3-${__process.env.AWS_REGION}.amazonaws.com/${__process.env.S3_BUCKET}/`; -const ROOT_URL = __process.env.API_URL; +const s3BucketHttps = getConfig('S3_BUCKET_URL_BASE') || + `https://s3-${getConfig('AWS_REGION')}.amazonaws.com/${getConfig('S3_BUCKET')}/`; const MAX_LOCAL_FILE_SIZE = 80000; // bytes, aka 80 KB function localIntercept(file, options = {}) { @@ -46,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/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/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/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/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(); diff --git a/client/utils/consoleUtils.js b/client/utils/consoleUtils.js index 2f8ea1be..a6013924 100644 --- a/client/utils/consoleUtils.js +++ b/client/utils/consoleUtils.js @@ -60,7 +60,7 @@ export const getAllScriptOffsets = (htmlFile) => { if (ind === -1) { foundJSScript = false; } else { - endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 3); + endFilenameInd = htmlFile.indexOf('.js', ind + startTag.length + 1); filename = htmlFile.substring(ind + startTag.length, endFilenameInd); lineOffset = htmlFile.substring(0, ind).split('\n').length + hijackConsoleErrorsScriptLength; offs.push([lineOffset, filename]); diff --git a/client/utils/getConfig.js b/client/utils/getConfig.js new file mode 100644 index 00000000..43c050ae --- /dev/null +++ b/client/utils/getConfig.js @@ -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; 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(); + }); +}); diff --git a/developer_docs/release.md b/developer_docs/release.md index 302c8019..aa06293a 100644 --- a/developer_docs/release.md +++ b/developer_docs/release.md @@ -13,19 +13,19 @@ This project release guide is based on 1. `$ git checkout develop` 2. `$ git checkout -b release-` 3. Do all of the release branch testing necessary. This could be as simple as running `npm test:ci`, or it could take user testing over a few days. -4. `$ git checkout release` -5. `$ git merge --no-ff release-` -6. `$ npm version ` (see [npm-version](https://docs.npmjs.com/cli/version) for valid values of ). +4. `$ npm version ` (see [npm-version](https://docs.npmjs.com/cli/version) for valid values of ). +5. `$ git checkout release` +6. `$ git merge --no-ff release-` 7. `$ git push && git push --tags` 8. `$ git checkout develop` -9. `$ git merge --no-ff release-` -10. Create a release on GitHub. You can do this in one of two ways: +9. Create a release on GitHub. Make sure that you release from the `release` branch! You can do this in one of two ways: 1. (Preferred) Use the [`hub` command line tool](https://hub.github.com/). You can automate adding all commit messages since the last release with the following command: ```sh $ hub release create -d -m "" -m "$(git log `git describe --tags --abbrev=0 HEAD^`..HEAD --oneline)" ` ``` Note that this creates a draft release, which you can then edit on GitHub. This allows you to create release notes from the list of commit messages, but then edit these notes as you wish. 2. [Draft a new release on Github](https://github.com/processing/p5.js-web-editor/releases/new). +10. `$ git merge --no-ff release-` Travis CI will automatically deploy the release to production, as well as push a production tagged Docker image to DockerHub. diff --git a/package-lock.json b/package-lock.json index a061d25f..2030d887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "0.0.1", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e318886d..033afdcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "0.0.1", + "version": "1.0.3", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist",