Simplify the fetch credentials structure to not use the fetch record.

It was thrashing the redux store. We made the same change for the ios app.
master
Tom Hacohen 5 years ago
parent 5301b546df
commit 118d26ce83

@ -3,7 +3,6 @@ import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
import { MuiThemeProvider as ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; // v1.x import { MuiThemeProvider as ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; // v1.x
import amber from '@material-ui/core/colors/amber'; import amber from '@material-ui/core/colors/amber';
import lightBlue from '@material-ui/core/colors/lightBlue'; import lightBlue from '@material-ui/core/colors/lightBlue';
@ -32,6 +31,8 @@ import { RouteResolver } from './routes';
import * as store from './store'; import * as store from './store';
import * as actions from './store/actions'; import * as actions from './store/actions';
import { credentialsSelector } from './login';
import { History } from 'history'; import { History } from 'history';
const muiTheme = createMuiTheme({ const muiTheme = createMuiTheme({
@ -170,7 +171,7 @@ class App extends React.PureComponent {
}; };
public props: { public props: {
credentials: store.CredentialsType; credentials: store.CredentialsData;
entries: store.EntriesType; entries: store.EntriesType;
fetchCount: number; fetchCount: number;
errors: ImmutableList<Error>; errors: ImmutableList<Error>;
@ -186,7 +187,7 @@ class App extends React.PureComponent {
} }
public render() { public render() {
const credentials = (this.props.credentials) ? this.props.credentials.value : null; const credentials = this.props.credentials ?? null;
const errors = this.props.errors; const errors = this.props.errors;
const fetching = this.props.fetchCount > 0; const fetching = this.props.fetchCount > 0;
@ -251,7 +252,7 @@ class App extends React.PureComponent {
</Drawer> </Drawer>
<ErrorBoundary> <ErrorBoundary>
<LoginGate credentials={this.props.credentials} /> <LoginGate />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
</BrowserRouter> </BrowserRouter>
@ -268,31 +269,10 @@ class App extends React.PureComponent {
} }
private refresh() { private refresh() {
store.store.dispatch<any>(actions.fetchAll(this.props.credentials.value!, this.props.entries)); store.store.dispatch<any>(actions.fetchAll(this.props.credentials, this.props.entries));
} }
} }
const credentialsSelector = createSelector(
(state: store.StoreState) => state.credentials.value,
(state: store.StoreState) => state.credentials.error,
(state: store.StoreState) => state.credentials.fetching,
(state: store.StoreState) => state.encryptionKey.key,
(value, error, fetching, encryptionKey) => {
if (value === null) {
return { value, error, fetching };
}
return {
error,
fetching,
value: {
...value,
encryptionKey,
},
};
}
);
const mapStateToProps = (state: store.StoreState) => { const mapStateToProps = (state: store.StoreState) => {
return { return {
credentials: credentialsSelector(state), credentials: credentialsSelector(state),

@ -8,7 +8,7 @@ import SyncGate from './SyncGate';
import LoginForm from './components/LoginForm'; import LoginForm from './components/LoginForm';
import EncryptionLoginForm from './components/EncryptionLoginForm'; import EncryptionLoginForm from './components/EncryptionLoginForm';
import { store, CredentialsType } from './store'; import { store, StoreState, CredentialsDataRemote } from './store';
import { deriveKey, fetchCredentials, fetchUserInfo } from './store/actions'; import { deriveKey, fetchCredentials, fetchUserInfo } from './store/actions';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
@ -16,14 +16,16 @@ import * as C from './constants';
import SignedPagesBadge from './images/signed-pages-badge.svg'; import SignedPagesBadge from './images/signed-pages-badge.svg';
import LoadingIndicator from './widgets/LoadingIndicator'; import LoadingIndicator from './widgets/LoadingIndicator';
import { useCredentials, useRemoteCredentials } from './login';
import { useSelector } from 'react-redux';
function EncryptionPart(props: { credentials: CredentialsType }) { function EncryptionPart(props: { credentials: CredentialsDataRemote }) {
const [fetched, setFetched] = React.useState(false); const [fetched, setFetched] = React.useState(false);
const [userInfo, setUserInfo] = React.useState<EteSync.UserInfo>(); const [userInfo, setUserInfo] = React.useState<EteSync.UserInfo>();
const [error, setError] = React.useState<Error>(); const [error, setError] = React.useState<Error>();
const credentials = props.credentials.value!; const credentials = props.credentials;
React.useEffect(() => { React.useEffect(() => {
// FIXME: verify the error is a 404 // FIXME: verify the error is a 404
@ -41,7 +43,7 @@ function EncryptionPart(props: { credentials: CredentialsType }) {
} }
function onEncryptionFormSubmit(encryptionPassword: string) { function onEncryptionFormSubmit(encryptionPassword: string) {
const derivedAction = deriveKey(props.credentials.value!.credentials.email, encryptionPassword); const derivedAction = deriveKey(credentials.credentials.email, encryptionPassword);
if (userInfo) { if (userInfo) {
const userInfoCryptoManager = userInfo.getCryptoManager(derivedAction.payload!); const userInfoCryptoManager = userInfo.getCryptoManager(derivedAction.payload!);
try { try {
@ -81,18 +83,25 @@ function EncryptionPart(props: { credentials: CredentialsType }) {
); );
} }
interface PropsType { export default function LoginGate() {
credentials: CredentialsType; const remoteCredentials = useRemoteCredentials();
} const credentials = useCredentials();
const fetchCount = useSelector((state: StoreState) => state.fetchCount);
export default function LoginGate(props: PropsType) { const [fetchError, setFetchError] = React.useState<Error>();
function onFormSubmit(username: string, password: string, serviceApiUrl?: string) { async function onFormSubmit(username: string, password: string, serviceApiUrl?: string) {
serviceApiUrl = serviceApiUrl ? serviceApiUrl : C.serviceApiBase; serviceApiUrl = serviceApiUrl ? serviceApiUrl : C.serviceApiBase;
store.dispatch<any>(fetchCredentials(username, password, serviceApiUrl)); try {
setFetchError(undefined);
const ret = fetchCredentials(username, password, serviceApiUrl);
await ret.payload;
store.dispatch(ret);
} catch (e) {
setFetchError(e);
}
} }
if (props.credentials.value === null) { if (remoteCredentials === null) {
const style = { const style = {
isSafe: { isSafe: {
textDecoration: 'none', textDecoration: 'none',
@ -109,8 +118,8 @@ export default function LoginGate(props: PropsType) {
<h2>Please Log In</h2> <h2>Please Log In</h2>
<LoginForm <LoginForm
onSubmit={onFormSubmit} onSubmit={onFormSubmit}
error={props.credentials.error} error={fetchError}
loading={props.credentials.fetching} loading={fetchCount > 0}
/> />
<hr style={style.divider} /> <hr style={style.divider} />
<ExternalLink style={style.isSafe} href="https://www.etesync.com/faq/#signed-pages"> <ExternalLink style={style.isSafe} href="https://www.etesync.com/faq/#signed-pages">
@ -127,13 +136,13 @@ export default function LoginGate(props: PropsType) {
</ul> </ul>
</Container> </Container>
); );
} else if (props.credentials.value.encryptionKey === null) { } else if (credentials === null) {
return ( return (
<EncryptionPart credentials={props.credentials} /> <EncryptionPart credentials={remoteCredentials} />
); );
} }
return ( return (
<SyncGate etesync={props.credentials.value} /> <SyncGate etesync={credentials} />
); );
} }

@ -0,0 +1,44 @@
import { shallowEqual, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import * as store from '../store';
export const remoteCredentialsSelector = createSelector(
(state: store.StoreState) => state.credentials.credentials,
(state: store.StoreState) => state.credentials.serviceApiUrl,
(credentials, serviceApiUrl) => {
if (!credentials) {
return null;
}
const ret: store.CredentialsDataRemote = {
credentials,
serviceApiUrl,
};
return ret;
}
);
export function useRemoteCredentials() {
return useSelector(remoteCredentialsSelector, shallowEqual);
}
export const credentialsSelector = createSelector(
(state: store.StoreState) => remoteCredentialsSelector(state),
(state: store.StoreState) => state.encryptionKey.key,
(remoteCredentials, encryptionKey) => {
if (!remoteCredentials || !encryptionKey) {
return null;
}
const ret: store.CredentialsData = {
...remoteCredentials,
encryptionKey,
};
return ret;
}
);
export function useCredentials() {
return useSelector(credentialsSelector, shallowEqual);
}

@ -8,13 +8,13 @@ import { List, Map as ImmutableMap } from 'immutable';
import * as EteSync from 'etesync'; import * as EteSync from 'etesync';
import { import {
JournalsData, FetchType, EntriesData, EntriesFetchRecord, UserInfoData, JournalsFetchRecord, UserInfoFetchRecord, JournalsData, FetchType, EntriesData, EntriesFetchRecord, UserInfoData, JournalsFetchRecord, UserInfoFetchRecord,
CredentialsTypeRemote, JournalsType, EntriesType, UserInfoType, SettingsType, CredentialsDataRemote, JournalsType, EntriesType, UserInfoType, SettingsType,
fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer, fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer,
} from './reducers'; } from './reducers';
export interface StoreState { export interface StoreState {
fetchCount: number; fetchCount: number;
credentials: CredentialsTypeRemote; credentials: CredentialsDataRemote;
settings: SettingsType; settings: SettingsType;
encryptionKey: {key: string}; encryptionKey: {key: string};
cache: { cache: {

@ -34,9 +34,6 @@ interface BaseModel {
uid: string; uid: string;
} }
export type CredentialsType = FetchType<CredentialsData>;
export type CredentialsTypeRemote = FetchType<CredentialsDataRemote>;
export type JournalsData = ImmutableMap<string, EteSync.Journal>; export type JournalsData = ImmutableMap<string, EteSync.Journal>;
export const JournalsFetchRecord = fetchTypeRecord<JournalsData>(); export const JournalsFetchRecord = fetchTypeRecord<JournalsData>();
export type JournalsType = FetchType<JournalsData>; export type JournalsType = FetchType<JournalsData>;
@ -97,32 +94,24 @@ export const encryptionKeyReducer = handleActions(
export const credentials = handleActions( export const credentials = handleActions(
{ {
[actions.fetchCredentials.toString()]: ( [actions.fetchCredentials.toString()]: (
_state: CredentialsTypeRemote, action: any) => { state: CredentialsDataRemote, action: any) => {
if (action.error) { if (action.error) {
return { return state;
value: null,
error: action.payload,
};
} else if (action.payload === undefined) { } else if (action.payload === undefined) {
return { return state;
fetching: true,
value: null,
};
} else { } else {
const { const {
encryptionKey, // We don't want to set encryption key here. encryptionKey, // We don't want to set encryption key here.
...payload ...payload
} = action.payload; } = action.payload;
return { return payload;
value: payload,
};
} }
}, },
[actions.logout.toString()]: (_state: CredentialsTypeRemote, _action: any) => { [actions.logout.toString()]: (_state: CredentialsDataRemote, _action: any) => {
return { out: true, value: null }; return {};
}, },
}, },
{ value: null } {} as CredentialsDataRemote
); );
const setMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => { const setMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => {

Loading…
Cancel
Save