From 5fd9bbf549931ce9f527836138ff716586bf7bcb Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 26 May 2020 14:42:29 -0400 Subject: [PATCH 01/11] 1.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 04e10900..e82a8267 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.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fb960610..0f835ca1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "0.0.1", + "version": "1.0.0", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist", From c9f5c1973b3d129b95ccd9d939ce3a77819db1cb Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Tue, 26 May 2020 14:58:34 -0400 Subject: [PATCH 02/11] 1.0.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e82a8267..78d6b370 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0f835ca1..a73b758d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.0", + "version": "1.0.1", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist", From b1d2f628b9ec8148751c6e852ea9303ca12b9e05 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Mon, 1 Jun 2020 14:26:53 -0400 Subject: [PATCH 03/11] 1.0.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd82cc4a..3c31c53d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 02e7bc07..aaaaf770 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.1", + "version": "1.0.2", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist", From 0edaa5f55536209538a600dd7e2278d74a788ef7 Mon Sep 17 00:00:00 2001 From: Sebas Maagnaldii Date: Fri, 5 Jun 2020 02:51:58 -0300 Subject: [PATCH 04/11] Fixed a bug that appears on preview when a .js file has a 2 or less name length --- client/utils/consoleUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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]); From 65aefcd45b1caa253e0eac406d7c56d25fe19f16 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Mon, 8 Jun 2020 11:46:38 +0200 Subject: [PATCH 05/11] 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 06/11] 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 8e2b520f533b1c4be0ef7084c97014fe72711419 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Wed, 10 Jun 2020 14:34:13 -0400 Subject: [PATCH 07/11] 1.0.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3c31c53d..2030d887 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index aaaaf770..033afdcf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "p5.js-web-editor", - "version": "1.0.2", + "version": "1.0.3", "description": "The web editor for p5.js.", "scripts": { "clean": "rimraf dist", From d64d93c82812c725560bf312a5a41d46657b3d15 Mon Sep 17 00:00:00 2001 From: Cassie Tarakajian Date: Wed, 10 Jun 2020 14:43:13 -0400 Subject: [PATCH 08/11] [#1421] Correct order of release steps --- developer_docs/release.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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. From dc3f14083b1cbf0cd53a3a9cf3e83dc6e1e1d47e Mon Sep 17 00:00:00 2001 From: Casey Reas Date: Fri, 12 Jun 2020 12:09:58 -0700 Subject: [PATCH 09/11] Adding GitHub Sponsor to sponsorship options --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 82c461c7..2cf28e94 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: processing custom: https://processingfoundation.org/support From 8c33c6ebcadfd825745ef98b0192d6d10d0b1bb9 Mon Sep 17 00:00:00 2001 From: Casey Reas Date: Fri, 12 Jun 2020 15:06:24 -0700 Subject: [PATCH 10/11] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 2cf28e94..93c4b27b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: processing -custom: https://processingfoundation.org/support +custom: https://processingfoundation.org/ From 6d90dd2071f81eb2f6e8f78bc3791988ce8d193d Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Sat, 13 Jun 2020 12:51:26 +0200 Subject: [PATCH 11/11] 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;