From 1cace5fd423cc18e65d52a93d284532c2309e038 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Wed, 9 Oct 2024 14:59:35 +0900 Subject: [PATCH 01/13] feat: consume user session POC --- src/components/menu-bar/menu-bar.jsx | 138 ++++----------------------- src/index.js | 5 +- src/lib/app-state-hoc.jsx | 8 +- src/lib/project-fetcher-hoc.jsx | 29 +++++- src/reducers/session.js | 48 ++++++++++ 5 files changed, 102 insertions(+), 126 deletions(-) create mode 100644 src/reducers/session.js diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index 126f0d460..f63dff3a4 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -625,126 +625,24 @@ class MenuBar extends React.Component { )} - {this.props.sessionExists ? ( - this.props.username ? ( - // ************ user is logged in ************ - - -
- -
-
- -
- ) : ( - // ********* user not logged in, but a session exists - // ********* so they can choose to log in - -
- -
-
- - -
-
- ) - ) : ( - // ******** no login session is available, so don't show login stuff - - {this.props.showComingSoon ? ( - - -
- -
-
- -
- - - {'scratch-cat'} - - -
-
-
- ) : []} -
- )} + {/* // ************ user is logged in ************ */} + + + {aboutButton} diff --git a/src/index.js b/src/index.js index 70dda5a90..2dfd82f7c 100644 --- a/src/index.js +++ b/src/index.js @@ -4,13 +4,15 @@ import GuiReducer, {guiInitialState, guiMiddleware, initEmbedded, initFullScreen import LocalesReducer, {localesInitialState, initLocale} from './reducers/locales'; import {ScratchPaintReducer} from 'scratch-paint'; import {setFullScreen, setPlayer} from './reducers/mode'; +import SessionReducer, { sessionInitialState } from "./reducers/session.js"; import {remixProject} from './reducers/project-state'; import {setAppElement} from 'react-modal'; const guiReducers = { locales: LocalesReducer, scratchGui: GuiReducer, - scratchPaint: ScratchPaintReducer + scratchPaint: ScratchPaintReducer, + session: SessionReducer, }; export { @@ -25,6 +27,7 @@ export { initFullScreen, initLocale, localesInitialState, + sessionInitialState, remixProject, setFullScreen, setPlayer diff --git a/src/lib/app-state-hoc.jsx b/src/lib/app-state-hoc.jsx index ff0ee0b5b..e790c5459 100644 --- a/src/lib/app-state-hoc.jsx +++ b/src/lib/app-state-hoc.jsx @@ -5,7 +5,7 @@ import {createStore, combineReducers, compose} from 'redux'; import ConnectedIntlProvider from './connected-intl-provider.jsx'; import localesReducer, {initLocale, localesInitialState} from '../reducers/locales'; - +import sessionReducer, { sessionInitialState } from "../reducers/session"; import {setPlayer, setFullScreen} from '../reducers/mode.js'; import locales from 'scratch-l10n'; @@ -69,11 +69,13 @@ const AppStateHOC = function (WrappedComponent, localesOnly) { reducers = { locales: localesReducer, scratchGui: guiReducer, - scratchPaint: ScratchPaintReducer + scratchPaint: ScratchPaintReducer, + session: sessionReducer, }; initialState = { locales: initializedLocales, - scratchGui: initializedGui + scratchGui: initializedGui, + session: sessionInitialState, }; enhancer = composeEnhancers(guiMiddleware); } diff --git a/src/lib/project-fetcher-hoc.jsx b/src/lib/project-fetcher-hoc.jsx index 3f4613719..7bf9d5f47 100644 --- a/src/lib/project-fetcher-hoc.jsx +++ b/src/lib/project-fetcher-hoc.jsx @@ -19,6 +19,7 @@ import { activateTab, BLOCKS_TAB_INDEX } from '../reducers/editor-tab'; +import { setSession } from "../reducers/session"; import log from './log'; import storage from './storage'; @@ -33,7 +34,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { constructor (props) { super(props); bindAll(this, [ - 'fetchProject' + 'fetchProject', 'fetchUserSessionFromApi' ]); storage.setProjectHost(props.projectHost); storage.setAssetHost(props.assetHost); @@ -51,6 +52,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { } } componentDidUpdate (prevProps) { + this.fetchUserSessionFromApi(); if (prevProps.projectHost !== this.props.projectHost) { storage.setProjectHost(this.props.projectHost); } @@ -84,6 +86,26 @@ const ProjectFetcherHOC = function (WrappedComponent) { log.error(err); }); } + fetchUserSessionFromApi() { + return fetch( + 'http://localhost:3000/user-session' + ) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + this.props.onSetSession(data.username); + }) + .catch((error) => { + console.error( + 'There was a problem with the fetch operation:', + error + ); + }); + } render () { const { /* eslint-disable no-unused-vars */ @@ -95,6 +117,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { onError: onErrorProp, onFetchedProjectData: onFetchedProjectDataProp, onProjectUnchanged, + onSetSession, projectHost, projectId, reduxProjectId, @@ -124,6 +147,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { onError: PropTypes.func, onFetchedProjectData: PropTypes.func, onProjectUnchanged: PropTypes.func, + onSetSession: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), @@ -148,7 +172,8 @@ const ProjectFetcherHOC = function (WrappedComponent) { onFetchedProjectData: (projectData, loadingState) => dispatch(onFetchedProjectData(projectData, loadingState)), setProjectId: projectId => dispatch(setProjectId(projectId)), - onProjectUnchanged: () => dispatch(setProjectUnchanged()) + onProjectUnchanged: () => dispatch(setProjectUnchanged()), + onSetSession: (username) => dispatch(setSession(username)), }); // Allow incoming props to override redux-provided props. Used to mock in tests. const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( diff --git a/src/reducers/session.js b/src/reducers/session.js new file mode 100644 index 000000000..fc452df92 --- /dev/null +++ b/src/reducers/session.js @@ -0,0 +1,48 @@ +const SET_SESSION = 'SET_SESSION'; +const GET_SESSION = 'GET_SESSION'; + +const initialState = { + session: { + user: { + username: 'Hello Kitty', + }, + }, +}; + +const reducer = function (state, action) { + if (typeof state === 'undefined') state = initialState; + switch (action.type) { + case SET_SESSION: + return Object.assign({}, state, { + session: { + user: { + username: action.username, + }, + }, + }); + case GET_SESSION: + return state; + default: + return state; + } +}; + +const getSession = function () { + return { + type: GET_SESSION, + }; +}; + +const setSession = function (username) { + return { + type: SET_SESSION, + username: username, + }; +}; + +export { + reducer as default, + initialState as sessionInitialState, + setSession, + getSession, +}; From d7658bd8be1f6ea812c0717fdc87b5c4e637c770 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Fri, 18 Oct 2024 15:55:11 +0900 Subject: [PATCH 02/13] feat(user session): get token from mock api --- package-lock.json | 15 +++++++++++++++ package.json | 1 + src/lib/project-fetcher-hoc.jsx | 6 +++--- src/lib/session-utils.js | 6 ++++++ src/reducers/session.js | 16 ++++++++++++---- 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/lib/session-utils.js diff --git a/package-lock.json b/package-lock.json index acfbdc72e..bb8fbd0f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "immutable": "3.8.2", "intl": "1.2.5", "js-base64": "2.4.9", + "jwt-decode": "^4.0.0", "keymirror": "0.1.1", "lodash.bindall": "4.4.0", "lodash.debounce": "4.0.8", @@ -13485,6 +13486,15 @@ "set-immediate-shim": "~1.0.1" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keymirror": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz", @@ -32699,6 +32709,11 @@ "set-immediate-shim": "~1.0.1" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "keymirror": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz", diff --git a/package.json b/package.json index 59e0f98c5..3aa4b98ba 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "immutable": "3.8.2", "intl": "1.2.5", "js-base64": "2.4.9", + "jwt-decode": "^4.0.0", "keymirror": "0.1.1", "lodash.bindall": "4.4.0", "lodash.debounce": "4.0.8", diff --git a/src/lib/project-fetcher-hoc.jsx b/src/lib/project-fetcher-hoc.jsx index 7bf9d5f47..06844c79e 100644 --- a/src/lib/project-fetcher-hoc.jsx +++ b/src/lib/project-fetcher-hoc.jsx @@ -88,7 +88,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { } fetchUserSessionFromApi() { return fetch( - 'http://localhost:3000/user-session' + 'http://localhost:3000/ccm/scratch-api/token' ) .then((response) => { if (!response.ok) { @@ -97,7 +97,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { return response.json(); }) .then((data) => { - this.props.onSetSession(data.username); + this.props.onSetSession(data.token); }) .catch((error) => { console.error( @@ -173,7 +173,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { dispatch(onFetchedProjectData(projectData, loadingState)), setProjectId: projectId => dispatch(setProjectId(projectId)), onProjectUnchanged: () => dispatch(setProjectUnchanged()), - onSetSession: (username) => dispatch(setSession(username)), + onSetSession: (token) => dispatch(setSession(token)), }); // Allow incoming props to override redux-provided props. Used to mock in tests. const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( diff --git a/src/lib/session-utils.js b/src/lib/session-utils.js new file mode 100644 index 000000000..101eb2cb1 --- /dev/null +++ b/src/lib/session-utils.js @@ -0,0 +1,6 @@ +import { jwtDecode } from "jwt-decode"; + +export const getUsernameFromToken = (token) => { + const decoded = jwtDecode(token); + return decoded.name; +} diff --git a/src/reducers/session.js b/src/reducers/session.js index fc452df92..cb9e4cf51 100644 --- a/src/reducers/session.js +++ b/src/reducers/session.js @@ -1,11 +1,16 @@ +import { getUsernameFromToken } from "../lib/session-utils"; + const SET_SESSION = 'SET_SESSION'; const GET_SESSION = 'GET_SESSION'; const initialState = { session: { - user: { - username: 'Hello Kitty', - }, + session: { + user: { + username: 'Hello Kitty', + token: '1234567890', + }, + } }, }; @@ -17,6 +22,7 @@ const reducer = function (state, action) { session: { user: { username: action.username, + token: action.token, }, }, }); @@ -33,10 +39,12 @@ const getSession = function () { }; }; -const setSession = function (username) { +const setSession = function (token) { + const username = getUsernameFromToken(token); return { type: SET_SESSION, username: username, + token: token, }; }; From edf9b2eb5d953a07a115ae5306865a0901a87400 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Fri, 18 Oct 2024 16:01:07 +0900 Subject: [PATCH 03/13] fix: session store --- src/reducers/session.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/reducers/session.js b/src/reducers/session.js index cb9e4cf51..aad2855ef 100644 --- a/src/reducers/session.js +++ b/src/reducers/session.js @@ -5,13 +5,12 @@ const GET_SESSION = 'GET_SESSION'; const initialState = { session: { - session: { - user: { - username: 'Hello Kitty', - token: '1234567890', - }, - } - }, + user: { + username: 'Hello Kitty', + token: '1234567890', + }, + } + }; const reducer = function (state, action) { From d6457552a57974b8f8c10f0ef7b7a971ff1f8597 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Fri, 18 Oct 2024 16:06:40 +0900 Subject: [PATCH 04/13] feat: return original menu-bar logic --- src/components/menu-bar/menu-bar.jsx | 138 +++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 18 deletions(-) diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index f63dff3a4..126f0d460 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -625,24 +625,126 @@ class MenuBar extends React.Component { )} - {/* // ************ user is logged in ************ */} - - - + {this.props.sessionExists ? ( + this.props.username ? ( + // ************ user is logged in ************ + + +
+ +
+
+ +
+ ) : ( + // ********* user not logged in, but a session exists + // ********* so they can choose to log in + +
+ +
+
+ + +
+
+ ) + ) : ( + // ******** no login session is available, so don't show login stuff + + {this.props.showComingSoon ? ( + + +
+ +
+
+ +
+ + + {'scratch-cat'} + + +
+
+
+ ) : []} +
+ )} {aboutButton} From 7528680c3607ce16208b0e9e8c7176022cf4b3da Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Tue, 22 Oct 2024 11:34:36 +0900 Subject: [PATCH 05/13] refactor: change place of token request to API --- src/containers/gui.jsx | 27 ++++++++++++++++++++++++++- src/lib/project-fetcher-hoc.jsx | 26 +------------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index c10df0fb3..138b23f3c 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -24,6 +24,7 @@ import { closeTelemetryModal, openExtensionLibrary } from '../reducers/modals'; +import { setSession } from "../reducers/session.js"; import FontLoaderHOC from '../lib/font-loader-hoc.jsx'; import LocalizationHOC from '../lib/localization-hoc.jsx'; @@ -42,6 +43,7 @@ import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; class GUI extends React.Component { componentDidMount () { + this.fetchUserSessionFromApi(); setIsScratchDesktop(this.props.isScratchDesktop); this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); @@ -56,6 +58,26 @@ class GUI extends React.Component { this.props.onProjectLoaded(); } } + fetchUserSessionFromApi() { + return fetch( + 'http://localhost:3000/ccm/scratch-api/token' + ) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + this.props.onSetSession(data.token); + }) + .catch((error) => { + console.error( + 'There was a problem with the fetch operation:', + error + ); + }); + } render () { if (this.props.isError) { throw new Error( @@ -73,6 +95,7 @@ class GUI extends React.Component { onStorageInit, onUpdateProjectId, onVmInit, + onSetSession, projectHost, projectId, /* eslint-enable no-unused-vars */ @@ -110,6 +133,7 @@ GUI.propTypes = { onStorageInit: PropTypes.func, onUpdateProjectId: PropTypes.func, onVmInit: PropTypes.func, + onSetSession: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), telemetryModalVisible: PropTypes.bool, @@ -161,7 +185,8 @@ const mapDispatchToProps = dispatch => ({ onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), - onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()) + onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()), + onSetSession: (token) => dispatch(setSession(token)), }); const ConnectedGUI = injectIntl(connect( diff --git a/src/lib/project-fetcher-hoc.jsx b/src/lib/project-fetcher-hoc.jsx index 06844c79e..ca99f96f4 100644 --- a/src/lib/project-fetcher-hoc.jsx +++ b/src/lib/project-fetcher-hoc.jsx @@ -19,7 +19,6 @@ import { activateTab, BLOCKS_TAB_INDEX } from '../reducers/editor-tab'; -import { setSession } from "../reducers/session"; import log from './log'; import storage from './storage'; @@ -34,7 +33,7 @@ const ProjectFetcherHOC = function (WrappedComponent) { constructor (props) { super(props); bindAll(this, [ - 'fetchProject', 'fetchUserSessionFromApi' + 'fetchProject' ]); storage.setProjectHost(props.projectHost); storage.setAssetHost(props.assetHost); @@ -52,7 +51,6 @@ const ProjectFetcherHOC = function (WrappedComponent) { } } componentDidUpdate (prevProps) { - this.fetchUserSessionFromApi(); if (prevProps.projectHost !== this.props.projectHost) { storage.setProjectHost(this.props.projectHost); } @@ -86,26 +84,6 @@ const ProjectFetcherHOC = function (WrappedComponent) { log.error(err); }); } - fetchUserSessionFromApi() { - return fetch( - 'http://localhost:3000/ccm/scratch-api/token' - ) - .then((response) => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then((data) => { - this.props.onSetSession(data.token); - }) - .catch((error) => { - console.error( - 'There was a problem with the fetch operation:', - error - ); - }); - } render () { const { /* eslint-disable no-unused-vars */ @@ -147,7 +125,6 @@ const ProjectFetcherHOC = function (WrappedComponent) { onError: PropTypes.func, onFetchedProjectData: PropTypes.func, onProjectUnchanged: PropTypes.func, - onSetSession: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), reduxProjectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), @@ -173,7 +150,6 @@ const ProjectFetcherHOC = function (WrappedComponent) { dispatch(onFetchedProjectData(projectData, loadingState)), setProjectId: projectId => dispatch(setProjectId(projectId)), onProjectUnchanged: () => dispatch(setProjectUnchanged()), - onSetSession: (token) => dispatch(setSession(token)), }); // Allow incoming props to override redux-provided props. Used to mock in tests. const mergeProps = (stateProps, dispatchProps, ownProps) => Object.assign( From c55f1caa0e9ad8187883262dddcda87dc708c480 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Tue, 22 Oct 2024 13:19:26 +0900 Subject: [PATCH 06/13] feat: show error page when token is not returned --- src/containers/gui.jsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 138b23f3c..e577feb55 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -9,7 +9,8 @@ import {injectIntl, intlShape} from 'react-intl'; import ErrorBoundaryHOC from '../lib/error-boundary-hoc.jsx'; import { getIsError, - getIsShowingProject + getIsShowingProject, + projectError } from '../reducers/project-state'; import { activateTab, @@ -73,9 +74,10 @@ class GUI extends React.Component { }) .catch((error) => { console.error( - 'There was a problem with the fetch operation:', + 'There was a problem with the fetch operation when fetching a token:', error ); + this.props.onProjectError(error); }); } render () { @@ -96,6 +98,7 @@ class GUI extends React.Component { onUpdateProjectId, onVmInit, onSetSession, + onProjectError, projectHost, projectId, /* eslint-enable no-unused-vars */ @@ -134,6 +137,7 @@ GUI.propTypes = { onUpdateProjectId: PropTypes.func, onVmInit: PropTypes.func, onSetSession: PropTypes.func, + onProjectError: PropTypes.func, projectHost: PropTypes.string, projectId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), telemetryModalVisible: PropTypes.bool, @@ -187,6 +191,7 @@ const mapDispatchToProps = dispatch => ({ onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), onRequestCloseTelemetryModal: () => dispatch(closeTelemetryModal()), onSetSession: (token) => dispatch(setSession(token)), + onProjectError: error => dispatch(projectError(error)), }); const ConnectedGUI = injectIntl(connect( From a2f9640e890ba3609839eefc1de2d1c519071037 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Thu, 26 Dec 2024 17:53:09 +0900 Subject: [PATCH 07/13] feat: consume token API --- src/components/menu-bar/login-dropdown.css | 4 - src/components/menu-bar/login-dropdown.jsx | 82 ------------ src/components/menu-bar/menu-bar.jsx | 138 ++++----------------- src/containers/gui.jsx | 2 +- src/lib/session-utils.js | 2 +- 5 files changed, 25 insertions(+), 203 deletions(-) delete mode 100644 src/components/menu-bar/login-dropdown.css delete mode 100644 src/components/menu-bar/login-dropdown.jsx diff --git a/src/components/menu-bar/login-dropdown.css b/src/components/menu-bar/login-dropdown.css deleted file mode 100644 index 8b16b7fdb..000000000 --- a/src/components/menu-bar/login-dropdown.css +++ /dev/null @@ -1,4 +0,0 @@ - -.login { - padding: .625rem; -} diff --git a/src/components/menu-bar/login-dropdown.jsx b/src/components/menu-bar/login-dropdown.jsx deleted file mode 100644 index fcfcd8117..000000000 --- a/src/components/menu-bar/login-dropdown.jsx +++ /dev/null @@ -1,82 +0,0 @@ -/* -NOTE: this file only temporarily resides in scratch-gui. -Nearly identical code appears in scratch-www, and the two should -eventually be consolidated. -*/ - -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React from 'react'; -import {defineMessages} from 'react-intl'; - -import MenuBarMenu from './menu-bar-menu.jsx'; - -import styles from './login-dropdown.css'; - -// these are here as a hack to get them translated, so that equivalent messages will be translated -// when passed in from www via gui's renderLogin() function -const LoginDropdownMessages = defineMessages({ // eslint-disable-line no-unused-vars - username: { - defaultMessage: 'Username', - description: 'Label for login username input', - id: 'general.username' - }, - password: { - defaultMessage: 'Password', - description: 'Label for login password input', - id: 'general.password' - }, - signin: { - defaultMessage: 'Sign in', - description: 'Button text for user to sign in', - id: 'general.signIn' - }, - needhelp: { - defaultMessage: 'Need Help?', - description: 'Button text for user to indicate that they need help', - id: 'login.needHelp' - }, - validationRequired: { - defaultMessage: 'This field is required', - description: 'Message to tell user they must enter text in a form field', - id: 'form.validationRequired' - } -}); - - -const LoginDropdown = ({ - className, - isOpen, - isRtl, - onClose, - renderLogin -}) => ( - -
- {renderLogin({ - onClose: onClose - })} -
-
-); - -LoginDropdown.propTypes = { - className: PropTypes.string, - isOpen: PropTypes.bool, - isRtl: PropTypes.bool, - onClose: PropTypes.func, - renderLogin: PropTypes.func -}; - -export default LoginDropdown; diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx index fc00b7a41..4617c59be 100644 --- a/src/components/menu-bar/menu-bar.jsx +++ b/src/components/menu-bar/menu-bar.jsx @@ -23,7 +23,6 @@ import {MenuItem, MenuSection} from '../menu/menu.jsx'; import ProjectTitleInput from './project-title-input.jsx'; import AuthorInfo from './author-info.jsx'; import AccountNav from '../../containers/account-nav.jsx'; -import LoginDropdown from './login-dropdown.jsx'; import SB3Downloader from '../../containers/sb3-downloader.jsx'; import DeletionRestorer from '../../containers/deletion-restorer.jsx'; import TurboMode from '../../containers/turbo-mode.jsx'; @@ -632,127 +631,36 @@ class MenuBar extends React.Component { )} - {this.props.sessionExists ? ( - this.props.username ? ( - // ************ user is logged in ************ - - -
- -
-
- -
- ) : ( - // ********* user not logged in, but a session exists - // ********* so they can choose to log in - -
- -
+ {this.props.sessionExists && + +
- -
-
- ) - ) : ( - // ******** no login session is available, so don't show login stuff - - {this.props.showComingSoon ? ( - - -
- -
-
- -
- {/* hide user icon for first release */} - {/* */} - - {'username'} - - -
-
-
- ) : []} -
- )} +
+ +
} {aboutButton} diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index e0f8bca82..db79075b1 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -66,7 +66,7 @@ class GUI extends React.Component { } fetchUserSessionFromApi() { return fetch( - 'http://localhost:3000/ccm/scratch-api/token' + 'http://adventure-lab-cms.docker.amazee.io/md/api/auth/refresh', {method: 'POST'} ) .then((response) => { if (!response.ok) { diff --git a/src/lib/session-utils.js b/src/lib/session-utils.js index 101eb2cb1..814ad58dc 100644 --- a/src/lib/session-utils.js +++ b/src/lib/session-utils.js @@ -2,5 +2,5 @@ import { jwtDecode } from "jwt-decode"; export const getUsernameFromToken = (token) => { const decoded = jwtDecode(token); - return decoded.name; + return decoded.user; } From 6db3c5cd85d0078b890560b05f81e5bdbffadc50 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Thu, 26 Dec 2024 17:54:42 +0900 Subject: [PATCH 08/13] refactor(session.js): change default value --- src/reducers/session.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reducers/session.js b/src/reducers/session.js index aad2855ef..4b3731cd1 100644 --- a/src/reducers/session.js +++ b/src/reducers/session.js @@ -6,8 +6,8 @@ const GET_SESSION = 'GET_SESSION'; const initialState = { session: { user: { - username: 'Hello Kitty', - token: '1234567890', + username: '', + token: '', }, } From 805ca2a34c3779b4462db2ebe9cd2264e9eebad0 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Fri, 27 Dec 2024 10:45:00 +0900 Subject: [PATCH 09/13] refactor(constants.js): create utils that contain constants --- src/config/constants.js | 1 + src/containers/gui.jsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 src/config/constants.js diff --git a/src/config/constants.js b/src/config/constants.js new file mode 100644 index 000000000..f7a52c47f --- /dev/null +++ b/src/config/constants.js @@ -0,0 +1 @@ +export const BASE_API_URL = "http://adventure-lab-cms.docker.amazee.io"; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index db79075b1..1df31a1c9 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -42,6 +42,7 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; import { setModalExtension } from '../reducers/modal-choose-extension.js'; +import { BASE_API_URL } from '../config/constants.js'; class GUI extends React.Component { constructor (props) { @@ -66,7 +67,7 @@ class GUI extends React.Component { } fetchUserSessionFromApi() { return fetch( - 'http://adventure-lab-cms.docker.amazee.io/md/api/auth/refresh', {method: 'POST'} + `${BASE_API_URL}/md/api/auth/refresh`, {method: 'POST'} ) .then((response) => { if (!response.ok) { From 2e61562bb33b7b77ad26d7f201745fb44b5c4933 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Fri, 27 Dec 2024 11:23:01 +0900 Subject: [PATCH 10/13] refactor(extension-modal): rename variable that indicates modal has already been opened --- src/components/gui/gui.jsx | 6 +++--- src/containers/gui.jsx | 6 +++--- src/reducers/gui.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx index 599cf8ca9..b6a6b446a 100644 --- a/src/components/gui/gui.jsx +++ b/src/components/gui/gui.jsx @@ -99,7 +99,7 @@ class GUIComponent extends React.Component { loading, logo, renderLogin, - modalChooseExtensionAlreadyBeenOpened, + isExtensionModalAlreadyOpened, onClickAbout, onClickAccountNav, onCloseAccountNav, @@ -407,7 +407,7 @@ GUIComponent.propTypes = { isShared: PropTypes.bool, loading: PropTypes.bool, logo: PropTypes.string, - modalChooseExtensionAlreadyBeenOpened: PropTypes.bool, + isExtensionModalAlreadyOpened: PropTypes.bool, onShowExtension: PropTypes.func, onActivateCostumesTab: PropTypes.func, onActivateSoundsTab: PropTypes.func, @@ -466,7 +466,7 @@ const mapStateToProps = state => { // This is the button's mode, as opposed to the actual current state stageSizeMode: state.scratchGui.stageSize.stageSize, projectId: state.scratchGui.projectState.projectId, - modalChooseExtensionAlreadyBeenOpened: state.scratchGui.modalChooseExtensionAlreadyBeenOpened + isExtensionModalAlreadyOpened: state.scratchGui.isExtensionModalAlreadyOpened } }; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 1df31a1c9..d04fb7fa9 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -90,7 +90,7 @@ class GUI extends React.Component { if(isFromButtonClick) { this.props.showExtension(); } - else if(this.props.projectId === '0' && !this.props.modalChooseExtensionAlreadyBeenOpened) { + else if(this.props.projectId === '0' && !this.props.isExtensionModalAlreadyOpened) { this.props.setModalExtensionVisibility(true); this.props.showExtension(); } @@ -148,7 +148,7 @@ GUI.propTypes = { isScratchDesktop: PropTypes.bool, isShowingProject: PropTypes.bool, loadingStateVisible: PropTypes.bool, - modalChooseExtensionAlreadyBeenOpened: PropTypes.bool, + isExtensionModalAlreadyOpened: PropTypes.bool, onProjectLoaded: PropTypes.func, onSeeCommunity: PropTypes.func, onStorageInit: PropTypes.func, @@ -190,7 +190,7 @@ const mapStateToProps = state => { isRtl: state.locales.isRtl, isShowingProject: getIsShowingProject(loadingState), loadingStateVisible: state.scratchGui.modals.loadingProject, - modalChooseExtensionAlreadyBeenOpened: state.scratchGui.modalChooseExtensionAlreadyBeenOpened, + isExtensionModalAlreadyOpened: state.scratchGui.isExtensionModalAlreadyOpened, projectId: state.scratchGui.projectState.projectId, soundsTabVisible: state.scratchGui.editorTab.activeTabIndex === SOUNDS_TAB_INDEX, targetIsStage: ( diff --git a/src/reducers/gui.js b/src/reducers/gui.js index 5187010d3..127e79a7c 100644 --- a/src/reducers/gui.js +++ b/src/reducers/gui.js @@ -54,7 +54,7 @@ const guiInitialState = { projectState: projectStateInitialState, projectTitle: projectTitleInitialState, fontsLoaded: fontsLoadedInitialState, - modalChooseExtensionAlreadyBeenOpened: modalChooseExtensionInitialState, + isExtensionModalAlreadyOpened: modalChooseExtensionInitialState, restoreDeletion: restoreDeletionInitialState, targets: targetsInitialState, timeout: timeoutInitialState, @@ -154,7 +154,7 @@ const guiReducer = combineReducers({ projectState: projectStateReducer, projectTitle: projectTitleReducer, fontsLoaded: fontsLoadedReducer, - modalChooseExtensionAlreadyBeenOpened: modalChooseExtensionReducer, + isExtensionModalAlreadyOpened: modalChooseExtensionReducer, restoreDeletion: restoreDeletionReducer, targets: targetReducer, timeout: timeoutReducer, From b9f0cb52c0f8a97381c2c930039b4cb9662025b4 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Mon, 6 Jan 2025 11:20:19 +0900 Subject: [PATCH 11/13] feat: create custom fetch to refresh token --- src/api/customFetch.js | 45 ++++++++++++++++++++++++++++++ src/containers/gui.jsx | 23 --------------- src/reducers/session.js | 13 +++------ src/{config => utils}/constants.js | 0 4 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 src/api/customFetch.js rename src/{config => utils}/constants.js (100%) diff --git a/src/api/customFetch.js b/src/api/customFetch.js new file mode 100644 index 000000000..c2c83dbf5 --- /dev/null +++ b/src/api/customFetch.js @@ -0,0 +1,45 @@ +import store from ''; +import { setSession } from './reducers/session' +import BASE_API_URL from '../utils/constants'; + +const customFetch = async (url) => { + const token = store.getState().session.token; + console.log('[token]', token); + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }; + + const response = await fetch(url, { + headers + }); + + if (response.status === 401) { + // Token is expired, try to refresh it + const refreshResponse = await fetch(`${BASE_API_URL}/md/api/auth/refresh`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + header: `Bearer ${token}` + }, + }); + + if (refreshResponse.ok) { + const data = await refreshResponse.json(); + store.dispatch(setSession(data.token)); + + // Repetir a requisição original com o novo token + headers['Authorization'] = `Bearer ${data.token}`; + return fetch(url, { + headers + }); + } else { + // Redirect to login page + console.error('Failed to refresh token'); + } + } + + return response; +}; + +export default customFetch; diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index d04fb7fa9..38dbc807f 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -42,7 +42,6 @@ import cloudManagerHOC from '../lib/cloud-manager-hoc.jsx'; import GUIComponent from '../components/gui/gui.jsx'; import {setIsScratchDesktop} from '../lib/isScratchDesktop.js'; import { setModalExtension } from '../reducers/modal-choose-extension.js'; -import { BASE_API_URL } from '../config/constants.js'; class GUI extends React.Component { constructor (props) { @@ -50,7 +49,6 @@ class GUI extends React.Component { this.handleShowExtension = this.handleShowExtension.bind(this); } componentDidMount () { - this.fetchUserSessionFromApi(); setIsScratchDesktop(this.props.isScratchDesktop); this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); @@ -65,27 +63,6 @@ class GUI extends React.Component { this.props.onProjectLoaded(); } } - fetchUserSessionFromApi() { - return fetch( - `${BASE_API_URL}/md/api/auth/refresh`, {method: 'POST'} - ) - .then((response) => { - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then((data) => { - this.props.onSetSession(data.token); - }) - .catch((error) => { - console.error( - 'There was a problem with the fetch operation when fetching a token:', - error - ); - this.props.onProjectError(error); - }); - } handleShowExtension(isFromButtonClick = false) { if(isFromButtonClick) { this.props.showExtension(); diff --git a/src/reducers/session.js b/src/reducers/session.js index 4b3731cd1..9bb0677a7 100644 --- a/src/reducers/session.js +++ b/src/reducers/session.js @@ -5,12 +5,9 @@ const GET_SESSION = 'GET_SESSION'; const initialState = { session: { - user: { - username: '', - token: '', - }, + username: '', + token: '', } - }; const reducer = function (state, action) { @@ -19,10 +16,8 @@ const reducer = function (state, action) { case SET_SESSION: return Object.assign({}, state, { session: { - user: { - username: action.username, - token: action.token, - }, + username: action.username, + token: action.token, }, }); case GET_SESSION: diff --git a/src/config/constants.js b/src/utils/constants.js similarity index 100% rename from src/config/constants.js rename to src/utils/constants.js From a26e91aee6c55f0903cb97f007e8d80f78f8624a Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Mon, 6 Jan 2025 11:25:21 +0900 Subject: [PATCH 12/13] feat: redirect to login page when token refresh fails --- src/api/customFetch.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/customFetch.js b/src/api/customFetch.js index c2c83dbf5..ed57c6699 100644 --- a/src/api/customFetch.js +++ b/src/api/customFetch.js @@ -36,6 +36,8 @@ const customFetch = async (url) => { } else { // Redirect to login page console.error('Failed to refresh token'); + // FIXME: check if login page url is correct + window.location.href = `${BASE_API_URL}/md/api/login`; } } From fa952c62c470de27f0dc673d7220b7bf7ab3dd86 Mon Sep 17 00:00:00 2001 From: Aquino Luane Date: Mon, 6 Jan 2025 11:30:22 +0900 Subject: [PATCH 13/13] feat: make api call to get token when app starts --- src/containers/gui.jsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx index 38dbc807f..d0e45ed49 100644 --- a/src/containers/gui.jsx +++ b/src/containers/gui.jsx @@ -49,6 +49,7 @@ class GUI extends React.Component { this.handleShowExtension = this.handleShowExtension.bind(this); } componentDidMount () { + this.fetchTokenFromApi(); setIsScratchDesktop(this.props.isScratchDesktop); this.props.onStorageInit(storage); this.props.onVmInit(this.props.vm); @@ -63,6 +64,28 @@ class GUI extends React.Component { this.props.onProjectLoaded(); } } + fetchTokenFromApi() { + // FIXME: replace with api url to get new token + return fetch( + `${BASE_API_URL}/md/api/auth`, {method: 'GET'} + ) + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + this.props.onSetSession(data.token); + }) + .catch((error) => { + console.error( + 'There was a problem with the fetch operation when fetching a token:', + error + ); + this.props.onProjectError(error); + }); + } handleShowExtension(isFromButtonClick = false) { if(isFromButtonClick) { this.props.showExtension();