import * as React from 'react'; import { connect } from 'react-redux'; import { Route, Switch, Redirect, RouteComponentProps, withRouter } from 'react-router'; import moment from 'moment'; import 'moment/locale/en-gb'; import { List, Map } from 'immutable'; import { createSelector } from 'reselect'; import { routeResolver } from './App'; import AppBarOverride from './widgets/AppBarOverride'; import LoadingIndicator from './widgets/LoadingIndicator'; import Journals from './Journals'; import Settings from './Settings'; import Debug from './Debug'; import Pim from './Pim'; import * as EteSync from 'etesync'; import { CURRENT_VERSION } from 'etesync'; import { store, SettingsType, JournalsData, EntriesData, StoreState, CredentialsData, UserInfoData } from './store'; import { addJournal, fetchAll, fetchEntries, fetchUserInfo, createUserInfo } from './store/actions'; export interface SyncInfoJournal { journal: EteSync.Journal; journalEntries: List; collection: EteSync.CollectionInfo; entries: List; } export type SyncInfo = Map; interface PropsType { etesync: CredentialsData; } type PropsTypeInner = RouteComponentProps<{}> & PropsType & { settings: SettingsType; journals: JournalsData; entries: EntriesData; userInfo: UserInfoData; fetchCount: number; }; const syncInfoSelector = createSelector( (props: PropsTypeInner) => props.etesync, (props: PropsTypeInner) => props.journals!, (props: PropsTypeInner) => props.entries, (props: PropsTypeInner) => props.userInfo, (etesync, journals, entries, userInfo) => { const derived = etesync.encryptionKey; const userInfoCryptoManager = userInfo.getCryptoManager(etesync.encryptionKey); try { userInfo.verify(userInfoCryptoManager); } catch (error) { if (error instanceof EteSync.IntegrityError) { throw new EteSync.EncryptionPasswordError(error.message); } else { throw error; } } return journals.reduce( (ret, journal) => { const journalEntries = entries.get(journal.uid); let prevUid: string | null = null; if (!journalEntries) { return ret; } const keyPair = userInfo.getKeyPair(userInfoCryptoManager); const cryptoManager = journal.getCryptoManager(derived, keyPair); const collectionInfo = journal.getInfo(cryptoManager); const syncEntries = journalEntries.map((entry: EteSync.Entry) => { const syncEntry = entry.getSyncEntry(cryptoManager, prevUid); prevUid = entry.uid; return syncEntry; }); return ret.set(journal.uid, { entries: syncEntries, collection: collectionInfo, journal, journalEntries, }); }, Map() ); } ); const PimRouter = withRouter(Pim); class SyncGate extends React.PureComponent { public componentDidMount() { const me = this.props.etesync.credentials.email; const syncAll = () => { store.dispatch(fetchAll(this.props.etesync, this.props.entries)).then((haveJournals: boolean) => { if (haveJournals) { return; } ['ADDRESS_BOOK', 'CALENDAR', 'TASKS'].forEach((collectionType) => { const collection = new EteSync.CollectionInfo(); collection.uid = EteSync.genUid(); collection.type = collectionType; collection.displayName = 'Default'; const journal = new EteSync.Journal({ uid: collection.uid }); const cryptoManager = new EteSync.CryptoManager(this.props.etesync.encryptionKey, collection.uid); journal.setInfo(cryptoManager, collection); (async () => { try { const addedJournalAction = addJournal(this.props.etesync, journal); await addedJournalAction.payload; store.dispatch(addedJournalAction); store.dispatch(fetchEntries(this.props.etesync, collection.uid)); } catch (e) { // FIXME: Limit based on error code to only ignore for associates console.warn(`Failed creating journal for ${collection.type}. Associate?`); } })(); }); }); }; if (this.props.userInfo) { syncAll(); } else { const fetching = fetchUserInfo(this.props.etesync, me); fetching.payload?.then(() => { store.dispatch(fetching); syncAll(); }).catch(() => { const userInfo = new EteSync.UserInfo(me, CURRENT_VERSION); const keyPair = EteSync.AsymmetricCryptoManager.generateKeyPair(); const cryptoManager = userInfo.getCryptoManager(this.props.etesync.encryptionKey); userInfo.setKeyPair(cryptoManager, keyPair); store.dispatch(createUserInfo(this.props.etesync, userInfo)).then(syncAll); }); } } public render() { const entryArrays = this.props.entries; const journals = this.props.journals; if ((this.props.userInfo === null) || (journals === null) || ((this.props.fetchCount > 0) && ((entryArrays.size === 0) || entryArrays.some((x) => (x.size === 0)))) ) { return (); } // FIXME: Shouldn't be here moment.locale(this.props.settings.locale); const journalMap = syncInfoSelector(this.props); return ( ( )} /> ( <> )} /> ( )} /> ( )} /> ( )} /> ); } } const mapStateToProps = (state: StoreState, _props: PropsType) => { return { settings: state.settings, journals: state.cache.journals, entries: state.cache.entries, userInfo: state.cache.userInfo, fetchCount: state.fetchCount, }; }; // FIXME: this and withRouters are only needed here because of https://github.com/ReactTraining/react-router/issues/5795 export default withRouter(connect( mapStateToProps )(SyncGate));