diff --git a/package.json b/package.json
index 46e2d70..91bb703 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,13 @@
"react": "^16.2.0",
"react-big-calendar": "^0.17.0",
"react-dom": "^16.2.0",
+ "react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"react-scripts-ts": "2.8.0",
+ "redux": "^3.7.2",
+ "redux-logger": "^3.0.6",
+ "redux-persist": "^5.4.0",
+ "redux-thunk": "^2.2.0",
"sjcl": "git+https://github.com/etesync/sjcl",
"urijs": "^1.16.1"
},
@@ -29,8 +34,10 @@
"@types/react": "^16.0.25",
"@types/react-big-calendar": "^0.15.0",
"@types/react-dom": "^16.0.3",
+ "@types/react-redux": "^5.0.14",
"@types/react-router": "^4.0.19",
"@types/react-router-dom": "^4.2.3",
+ "@types/redux-logger": "^3.0.5",
"@types/sjcl": "^1.0.28",
"@types/urijs": "^1.15.34"
}
diff --git a/src/EteSyncContext.tsx b/src/EteSyncContext.tsx
index 8b9f740..480d4b6 100644
--- a/src/EteSyncContext.tsx
+++ b/src/EteSyncContext.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
-import { Switch, Route, Redirect } from 'react-router';
+import { connect } from 'react-redux';
+import { Switch, Route, Redirect, withRouter } from 'react-router';
import Paper from 'material-ui/Paper';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
@@ -11,17 +12,10 @@ import { JournalView } from './JournalView';
import * as EteSync from './api/EteSync';
import { routeResolver, getPalette } from './App';
+import * as store from './store';
import * as C from './Constants';
-const CONTEXT_SESSION_KEY = 'EteSyncContext';
-
-enum LoadState {
- Initial = 'INIT',
- Working = 'WORKING',
- Done = 'DONE',
-}
-
export interface EteSyncContextType {
serviceApiUrl: string;
credentials: EteSync.Credentials;
@@ -35,12 +29,35 @@ interface FormErrors {
errorServer?: string;
}
-export class EteSyncContext extends React.Component {
+function fetchCredentials(username: string, password: string, encryptionPassword: string, server: string) {
+ const authenticator = new EteSync.Authenticator(server);
+
+ return (dispatch: any) => {
+ dispatch(store.credentialsRequest());
+
+ authenticator.getAuthToken(username, password).then(
+ (authToken) => {
+ const credentials = new EteSync.Credentials(username, authToken);
+ const derived = EteSync.deriveKey(username, encryptionPassword);
+
+ const context = {
+ serviceApiUrl: server,
+ credentials,
+ encryptionKey: derived,
+ };
+
+ dispatch(store.credentialsSuccess(context));
+ },
+ (error) => {
+ dispatch(store.credentialsFailure(error));
+ }
+ );
+ };
+}
+
+export class EteSyncContextInner extends React.Component {
state: {
- context?: EteSyncContextType;
- loadState: LoadState;
showAdvanced?: boolean;
- error?: Error;
errors: FormErrors;
server: string;
@@ -49,10 +66,13 @@ export class EteSyncContext extends React.Component {
encryptionPassword: string;
};
+ props: {
+ credentials: store.CredentialsType;
+ };
+
constructor(props: any) {
super(props);
this.state = {
- loadState: LoadState.Initial,
errors: {},
server: '',
username: '',
@@ -62,17 +82,6 @@ export class EteSyncContext extends React.Component {
this.generateEncryption = this.generateEncryption.bind(this);
this.toggleAdvancedSettings = this.toggleAdvancedSettings.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
-
- const contextStr = sessionStorage.getItem(CONTEXT_SESSION_KEY);
-
- if (contextStr !== null) {
- const context: EteSyncContextType = JSON.parse(contextStr);
-
- this.state = Object.assign({}, this.state, {
- loadState: LoadState.Done,
- context
- });
- }
}
handleInputChange(event: any) {
@@ -87,8 +96,6 @@ export class EteSyncContext extends React.Component {
e.preventDefault();
const server = this.state.showAdvanced ? this.state.server : C.serviceApiBase;
- let authenticator = new EteSync.Authenticator(server);
-
const username = this.state.username;
const password = this.state.password;
const encryptionPassword = this.state.encryptionPassword;
@@ -109,32 +116,7 @@ export class EteSyncContext extends React.Component {
return;
}
- this.setState({
- loadState: LoadState.Working
- });
-
- authenticator.getAuthToken(username, password).then((authToken) => {
- const credentials = new EteSync.Credentials(username, authToken);
- const derived = EteSync.deriveKey(username, encryptionPassword);
-
- const context = {
- serviceApiUrl: server,
- credentials,
- encryptionKey: derived,
- };
-
- sessionStorage.setItem(CONTEXT_SESSION_KEY, JSON.stringify(context));
-
- this.setState({
- loadState: LoadState.Done,
- context
- });
- }).catch((error) => {
- this.setState({
- loadState: LoadState.Initial,
- error
- });
- });
+ store.store.dispatch(fetchCredentials(username, password, encryptionPassword, server));
}
toggleAdvancedSettings() {
@@ -142,7 +124,10 @@ export class EteSyncContext extends React.Component {
}
render() {
- if (this.state.loadState === LoadState.Initial) {
+ if (((this.props.credentials.status === store.FetchStatus.Initial) &&
+ (this.props.credentials.credentials === undefined)) ||
+ (this.props.credentials.status === store.FetchStatus.Failure)) {
+
let advancedSettings = null;
if (this.state.showAdvanced) {
advancedSettings = (
@@ -187,7 +172,7 @@ export class EteSyncContext extends React.Component {
return (
- {(this.state.error !== undefined) && (Error! {this.state.error.message}
)}
+ {(this.props.credentials.error !== undefined) && (Error! {this.props.credentials.error.message}
)}
Please Log In
);
- } else if ((this.state.context === undefined) ||
- (this.state.loadState === LoadState.Working)) {
+ } else if (this.props.credentials.status === store.FetchStatus.Request) {
return (Loading
);
}
- let context: EteSyncContextType = this.state.context;
+ let context = this.props.credentials.credentials as store.CredentialsData;
return (
@@ -260,3 +244,13 @@ export class EteSyncContext extends React.Component {
);
}
}
+
+const mapStateToProps = (state: store.StoreState) => {
+ return {
+ credentials: state.credentials,
+ };
+};
+
+export const EteSyncContext = withRouter(connect(
+ mapStateToProps
+)(EteSyncContextInner));
diff --git a/src/index.tsx b/src/index.tsx
index 1c66245..6dbd0d0 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,11 +1,19 @@
import * as React from 'react';
import * as ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { PersistGate } from 'redux-persist/es/integration/react';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
+import { store, persistor } from './store';
+
ReactDOM.render(
-
,
+
+
+
+
+ ,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
diff --git a/src/redux-persist.d.ts b/src/redux-persist.d.ts
new file mode 100644
index 0000000..6d6e080
--- /dev/null
+++ b/src/redux-persist.d.ts
@@ -0,0 +1,3 @@
+declare module 'redux-persist';
+declare module 'redux-persist/lib/storage/session';
+declare module 'redux-persist/es/integration/react';
diff --git a/src/store.tsx b/src/store.tsx
new file mode 100644
index 0000000..3c5b26a
--- /dev/null
+++ b/src/store.tsx
@@ -0,0 +1,113 @@
+import { createStore, combineReducers, applyMiddleware } from 'redux';
+import { persistReducer, persistStore } from 'redux-persist';
+import session from 'redux-persist/lib/storage/session';
+import thunkMiddleware from 'redux-thunk';
+import { createLogger } from 'redux-logger';
+
+import * as EteSync from './api/EteSync';
+
+const loggerMiddleware = createLogger();
+
+enum Actions {
+ FETCH_CREDENTIALS = 'FETCH_CREDENTIALS',
+}
+
+export enum FetchStatus {
+ Initial = 'INITIAL',
+ Request = 'REQUEST',
+ Failure = 'FAILURE',
+ Success = 'SUCCESS',
+}
+
+export interface CredentialsData {
+ serviceApiUrl: string;
+ credentials: EteSync.Credentials;
+ encryptionKey: string;
+}
+
+export interface CredentialsType {
+ status: FetchStatus;
+ error?: Error;
+ credentials?: CredentialsData;
+}
+
+export interface StoreState {
+ fetchCount: number;
+ credentials: CredentialsData;
+}
+
+export function credentialsSuccess(creds: CredentialsData) {
+ return {
+ type: Actions.FETCH_CREDENTIALS,
+ status: FetchStatus.Success,
+ credentials: creds,
+ };
+}
+
+export function credentialsRequest() {
+ return {
+ type: Actions.FETCH_CREDENTIALS,
+ status: FetchStatus.Request,
+ };
+}
+
+export function credentialsFailure(error: Error) {
+ return {
+ type: Actions.FETCH_CREDENTIALS,
+ status: FetchStatus.Failure,
+ error
+ };
+}
+
+function credentials(state: CredentialsType = {status: FetchStatus.Initial}, action: any) {
+ switch (action.type) {
+ case Actions.FETCH_CREDENTIALS:
+ if (action.status === FetchStatus.Success) {
+ return {
+ status: action.status,
+ credentials: action.credentials,
+ };
+ } else {
+ return {
+ status: action.status,
+ };
+ }
+ default:
+ return state;
+ }
+}
+
+function fetchCount(state: number = 0, action: any) {
+ // FIXME: Make it automatic by action properties.
+ switch (action.type) {
+ case Actions.FETCH_CREDENTIALS:
+ if (action.status === FetchStatus.Request) {
+ return state + 1;
+ } else {
+ return state - 1;
+ }
+ default:
+ return state;
+ }
+}
+
+const credentialsPersistConfig = {
+ key: 'credentials',
+ storage: session,
+ whitelist: ['credentials'],
+};
+
+const reducers = combineReducers({
+ fetchCount,
+ credentials: persistReducer(credentialsPersistConfig, credentials),
+});
+
+export const store = createStore(
+ reducers,
+ applyMiddleware(
+ thunkMiddleware,
+ loggerMiddleware
+ )
+);
+
+export const persistor = persistStore(store);
diff --git a/yarn.lock b/yarn.lock
index 0416016..97063d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -48,6 +48,13 @@
"@types/node" "*"
"@types/react" "*"
+"@types/react-redux@^5.0.14":
+ version "5.0.14"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-5.0.14.tgz#f3fc30dcbb2d20455a714f591cc27f77b4df09bb"
+ dependencies:
+ "@types/react" "*"
+ redux "^3.6.0"
+
"@types/react-router-dom@^4.2.3":
version "4.2.3"
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-4.2.3.tgz#06e0b67ff536adc0681dffdbe592ae91fb85887d"
@@ -67,6 +74,12 @@
version "16.0.25"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.25.tgz#bf696b83fe480c5e0eff4335ee39ebc95884a1ed"
+"@types/redux-logger@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/redux-logger/-/redux-logger-3.0.5.tgz#d1a02758f90845899cd304aa0912daeba2028eb6"
+ dependencies:
+ redux "^3.6.0"
+
"@types/sjcl@^1.0.28":
version "1.0.28"
resolved "https://registry.yarnpkg.com/@types/sjcl/-/sjcl-1.0.28.tgz#4693eb6943e385e844a70fb25b4699db286c7214"
@@ -1310,6 +1323,10 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+deep-diff@^0.3.5:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84"
+
deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@@ -2297,7 +2314,7 @@ hoek@4.x.x:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
-hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1:
+hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@@ -2526,7 +2543,7 @@ interpret@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
-invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2:
+invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
@@ -3240,6 +3257,10 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
+lodash-es@^4.2.0, lodash-es@^4.2.1:
+ version "4.17.4"
+ resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7"
+
lodash._reinterpolate@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -3281,7 +3302,7 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0:
+"lodash@>=3.5 <5", lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -4513,6 +4534,17 @@ react-prop-types@^0.4.0:
dependencies:
warning "^3.0.0"
+react-redux@^5.0.6:
+ version "5.0.6"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946"
+ dependencies:
+ hoist-non-react-statics "^2.2.1"
+ invariant "^2.0.0"
+ lodash "^4.2.0"
+ lodash-es "^4.2.0"
+ loose-envify "^1.1.0"
+ prop-types "^15.5.10"
+
react-router-dom@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-4.2.2.tgz#c8a81df3adc58bba8a76782e946cbd4eae649b8d"
@@ -4694,6 +4726,29 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
+redux-logger@^3.0.6:
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf"
+ dependencies:
+ deep-diff "^0.3.5"
+
+redux-persist@^5.4.0:
+ version "5.4.0"
+ resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-5.4.0.tgz#a1062313546a9d4ca6f9271464d18f736e8ca394"
+
+redux-thunk@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
+
+redux@^3.6.0, redux@^3.7.2:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
+ dependencies:
+ lodash "^4.2.1"
+ lodash-es "^4.2.1"
+ loose-envify "^1.1.0"
+ symbol-observable "^1.0.3"
+
regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
@@ -5355,7 +5410,7 @@ sw-toolbox@^3.4.0:
path-to-regexp "^1.0.1"
serviceworker-cache-polyfill "^4.0.0"
-symbol-observable@^1.0.4:
+symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"