// SPDX-FileCopyrightText: © 2017 EteSync Authors // SPDX-License-Identifier: AGPL-3.0-only import * as React from "react"; import { useSelector } 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, 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; } interface SelectorProps { etesync: CredentialsData; journals: JournalsData; entries: EntriesData; userInfo: UserInfoData; } const syncInfoSelector = createSelector( (props: SelectorProps) => props.etesync, (props: SelectorProps) => props.journals!, (props: SelectorProps) => props.entries, (props: SelectorProps) => 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); // FIXME: this and withRouters are only needed here because of https://github.com/ReactTraining/react-router/issues/5795 export default withRouter(function SyncGate(props: RouteComponentProps<{}> & PropsType) { const etesync = props.etesync; const settings = useSelector((state: StoreState) => state.settings); const fetchCount = useSelector((state: StoreState) => state.fetchCount); const userInfo = useSelector((state: StoreState) => state.cache.userInfo); const journals = useSelector((state: StoreState) => state.cache.journals); const entries = useSelector((state: StoreState) => state.cache.entries); React.useEffect(() => { const me = etesync.credentials.email; const syncAll = () => { store.dispatch(fetchAll(etesync, entries)).then((haveJournals: boolean) => { if (haveJournals) { return; } [ { type: "ADDRESS_BOOK", name: "My Contacts", }, { type: "CALENDAR", name: "My Calendar", }, { type: "TASKS", name: "My Tasks", }, ].forEach((collectionDesc) => { const collection = new EteSync.CollectionInfo(); collection.uid = EteSync.genUid(); collection.type = collectionDesc.type; collection.displayName = collectionDesc.name; const journal = new EteSync.Journal({ uid: collection.uid }); const cryptoManager = new EteSync.CryptoManager(etesync.encryptionKey, collection.uid); journal.setInfo(cryptoManager, collection); (async () => { try { const addedJournalAction = addJournal(etesync, journal); await addedJournalAction.payload; store.dispatch(addedJournalAction); store.dispatch(fetchEntries(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 (userInfo) { syncAll(); } else { const fetching = fetchUserInfo(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(etesync.encryptionKey); userInfo.setKeyPair(cryptoManager, keyPair); store.dispatch(createUserInfo(etesync, userInfo)).then(syncAll); }); } }, []); const entryArrays = entries; if ((userInfo === null) || (journals === null) || ((fetchCount > 0) && ((entryArrays.size === 0) || entryArrays.some((x) => (x.size === 0)))) ) { return (); } // FIXME: Shouldn't be here moment.locale(settings.locale); const journalMap = syncInfoSelector({ etesync, userInfo, journals, entries }); return ( ( )} /> ( <> )} /> ( )} /> ( )} /> ( )} /> ); });