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
parent
5301b546df
commit
118d26ce83
32
src/App.tsx
32
src/App.tsx
|
@ -3,7 +3,6 @@ import { List as ImmutableList } from 'immutable';
|
|||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import { MuiThemeProvider as ThemeProvider, createMuiTheme } from '@material-ui/core/styles'; // v1.x
|
||||
import amber from '@material-ui/core/colors/amber';
|
||||
import lightBlue from '@material-ui/core/colors/lightBlue';
|
||||
|
@ -32,6 +31,8 @@ import { RouteResolver } from './routes';
|
|||
import * as store from './store';
|
||||
import * as actions from './store/actions';
|
||||
|
||||
import { credentialsSelector } from './login';
|
||||
|
||||
import { History } from 'history';
|
||||
|
||||
const muiTheme = createMuiTheme({
|
||||
|
@ -170,7 +171,7 @@ class App extends React.PureComponent {
|
|||
};
|
||||
|
||||
public props: {
|
||||
credentials: store.CredentialsType;
|
||||
credentials: store.CredentialsData;
|
||||
entries: store.EntriesType;
|
||||
fetchCount: number;
|
||||
errors: ImmutableList<Error>;
|
||||
|
@ -186,7 +187,7 @@ class App extends React.PureComponent {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const credentials = (this.props.credentials) ? this.props.credentials.value : null;
|
||||
const credentials = this.props.credentials ?? null;
|
||||
|
||||
const errors = this.props.errors;
|
||||
const fetching = this.props.fetchCount > 0;
|
||||
|
@ -251,7 +252,7 @@ class App extends React.PureComponent {
|
|||
</Drawer>
|
||||
|
||||
<ErrorBoundary>
|
||||
<LoginGate credentials={this.props.credentials} />
|
||||
<LoginGate />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
|
@ -268,31 +269,10 @@ class App extends React.PureComponent {
|
|||
}
|
||||
|
||||
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) => {
|
||||
return {
|
||||
credentials: credentialsSelector(state),
|
||||
|
|
|
@ -8,7 +8,7 @@ import SyncGate from './SyncGate';
|
|||
import LoginForm from './components/LoginForm';
|
||||
import EncryptionLoginForm from './components/EncryptionLoginForm';
|
||||
|
||||
import { store, CredentialsType } from './store';
|
||||
import { store, StoreState, CredentialsDataRemote } from './store';
|
||||
import { deriveKey, fetchCredentials, fetchUserInfo } from './store/actions';
|
||||
|
||||
import * as EteSync from 'etesync';
|
||||
|
@ -16,14 +16,16 @@ import * as C from './constants';
|
|||
|
||||
import SignedPagesBadge from './images/signed-pages-badge.svg';
|
||||
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 [userInfo, setUserInfo] = React.useState<EteSync.UserInfo>();
|
||||
const [error, setError] = React.useState<Error>();
|
||||
|
||||
const credentials = props.credentials.value!;
|
||||
const credentials = props.credentials;
|
||||
|
||||
React.useEffect(() => {
|
||||
// FIXME: verify the error is a 404
|
||||
|
@ -41,7 +43,7 @@ function EncryptionPart(props: { credentials: CredentialsType }) {
|
|||
}
|
||||
|
||||
function onEncryptionFormSubmit(encryptionPassword: string) {
|
||||
const derivedAction = deriveKey(props.credentials.value!.credentials.email, encryptionPassword);
|
||||
const derivedAction = deriveKey(credentials.credentials.email, encryptionPassword);
|
||||
if (userInfo) {
|
||||
const userInfoCryptoManager = userInfo.getCryptoManager(derivedAction.payload!);
|
||||
try {
|
||||
|
@ -81,18 +83,25 @@ function EncryptionPart(props: { credentials: CredentialsType }) {
|
|||
);
|
||||
}
|
||||
|
||||
interface PropsType {
|
||||
credentials: CredentialsType;
|
||||
}
|
||||
export default function LoginGate() {
|
||||
const remoteCredentials = useRemoteCredentials();
|
||||
const credentials = useCredentials();
|
||||
const fetchCount = useSelector((state: StoreState) => state.fetchCount);
|
||||
const [fetchError, setFetchError] = React.useState<Error>();
|
||||
|
||||
export default function LoginGate(props: PropsType) {
|
||||
|
||||
function onFormSubmit(username: string, password: string, serviceApiUrl?: string) {
|
||||
async function onFormSubmit(username: string, password: string, serviceApiUrl?: string) {
|
||||
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 = {
|
||||
isSafe: {
|
||||
textDecoration: 'none',
|
||||
|
@ -109,8 +118,8 @@ export default function LoginGate(props: PropsType) {
|
|||
<h2>Please Log In</h2>
|
||||
<LoginForm
|
||||
onSubmit={onFormSubmit}
|
||||
error={props.credentials.error}
|
||||
loading={props.credentials.fetching}
|
||||
error={fetchError}
|
||||
loading={fetchCount > 0}
|
||||
/>
|
||||
<hr style={style.divider} />
|
||||
<ExternalLink style={style.isSafe} href="https://www.etesync.com/faq/#signed-pages">
|
||||
|
@ -127,13 +136,13 @@ export default function LoginGate(props: PropsType) {
|
|||
</ul>
|
||||
</Container>
|
||||
);
|
||||
} else if (props.credentials.value.encryptionKey === null) {
|
||||
} else if (credentials === null) {
|
||||
return (
|
||||
<EncryptionPart credentials={props.credentials} />
|
||||
<EncryptionPart credentials={remoteCredentials} />
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
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,
|
||||
} from './reducers';
|
||||
|
||||
export interface StoreState {
|
||||
fetchCount: number;
|
||||
credentials: CredentialsTypeRemote;
|
||||
credentials: CredentialsDataRemote;
|
||||
settings: SettingsType;
|
||||
encryptionKey: {key: string};
|
||||
cache: {
|
||||
|
|
|
@ -34,9 +34,6 @@ interface BaseModel {
|
|||
uid: string;
|
||||
}
|
||||
|
||||
export type CredentialsType = FetchType<CredentialsData>;
|
||||
export type CredentialsTypeRemote = FetchType<CredentialsDataRemote>;
|
||||
|
||||
export type JournalsData = ImmutableMap<string, EteSync.Journal>;
|
||||
export const JournalsFetchRecord = fetchTypeRecord<JournalsData>();
|
||||
export type JournalsType = FetchType<JournalsData>;
|
||||
|
@ -97,32 +94,24 @@ export const encryptionKeyReducer = handleActions(
|
|||
export const credentials = handleActions(
|
||||
{
|
||||
[actions.fetchCredentials.toString()]: (
|
||||
_state: CredentialsTypeRemote, action: any) => {
|
||||
state: CredentialsDataRemote, action: any) => {
|
||||
if (action.error) {
|
||||
return {
|
||||
value: null,
|
||||
error: action.payload,
|
||||
};
|
||||
return state;
|
||||
} else if (action.payload === undefined) {
|
||||
return {
|
||||
fetching: true,
|
||||
value: null,
|
||||
};
|
||||
return state;
|
||||
} else {
|
||||
const {
|
||||
encryptionKey, // We don't want to set encryption key here.
|
||||
...payload
|
||||
} = action.payload;
|
||||
return {
|
||||
value: payload,
|
||||
};
|
||||
return payload;
|
||||
}
|
||||
},
|
||||
[actions.logout.toString()]: (_state: CredentialsTypeRemote, _action: any) => {
|
||||
return { out: true, value: null };
|
||||
[actions.logout.toString()]: (_state: CredentialsDataRemote, _action: any) => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
{ value: null }
|
||||
{} as CredentialsDataRemote
|
||||
);
|
||||
|
||||
const setMapModelReducer = <T extends Record<any>, V extends BaseModel>(state: T, action: any) => {
|
||||
|
|
Loading…
Reference in New Issue