diff --git a/package.json b/package.json index 5439a05..2124c78 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,13 @@ "@material-ui/lab": "^4.0.0-alpha.47", "@material-ui/pickers": "^3.2.10", "@material-ui/styles": "^4.6.0", + "etebase": "^0.7.4", "etesync": "^0.3.1", "fuse.js": "^5.0.9-beta", "ical.js": "^1.4.0", "immutable": "^4.0.0-rc.12", "localforage": "^1.9.0", + "memoizee": "^0.4.14", "moment": "^2.27.0", "react": "^16.13.1", "react-big-calendar": "^0.26.0", @@ -46,6 +48,7 @@ "@testing-library/user-event": "^7.1.2", "@types/color": "^3.0.1", "@types/jest": "^24.0.4", + "@types/memoizee": "^0.4.4", "@types/node": "^11.9.3", "@types/node-rsa": "^1.0.0", "@types/react": "^16.9.0", diff --git a/src/LoginGate.tsx b/src/LoginGate.tsx index 4ae4387..f98e6a0 100644 --- a/src/LoginGate.tsx +++ b/src/LoginGate.tsx @@ -3,116 +3,41 @@ import * as React from "react"; -import { Action } from "redux-actions"; +import { useDispatch } from "react-redux"; import Container from "./widgets/Container"; import ExternalLink from "./widgets/ExternalLink"; import SyncGate from "./SyncGate"; import LoginForm from "./components/LoginForm"; -import EncryptionLoginForm from "./components/EncryptionLoginForm"; -import { store, StoreState, CredentialsDataRemote } from "./store"; -import { deriveKey, fetchCredentials, fetchUserInfo, logout } from "./store/actions"; +import { login } from "./store/actions"; -import * as EteSync from "etesync"; 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"; +import { useCredentials } from "./credentials"; -function EncryptionPart(props: { credentials: CredentialsDataRemote }) { - const [fetched, setFetched] = React.useState(false); - const [userInfo, setUserInfo] = React.useState(); - const [error, setError] = React.useState(); - - const credentials = props.credentials; - - React.useEffect(() => { - store.dispatch(fetchUserInfo(credentials, credentials.credentials.email)).then((fetchedUserInfo: Action) => { - setUserInfo(fetchedUserInfo.payload); - }).catch((e: Error) => { - // Do nothing. - if (e instanceof EteSync.HTTPError) { - if (e.status === 404) { - // Do nothing - } else if (e.status === 401) { - store.dispatch(logout(credentials)); - } else { - setError(e); - } - } - }).finally(() => { - setFetched(true); - }); - }, [credentials]); - - if (!fetched) { - return ; - } - - function onEncryptionFormSubmit(encryptionPassword: string) { - const derivedAction = deriveKey(credentials.credentials.email, encryptionPassword); - if (userInfo) { - const userInfoCryptoManager = userInfo.getCryptoManager(derivedAction.payload!); - try { - userInfo.verify(userInfoCryptoManager); - } catch (e) { - setError(new EteSync.EncryptionPasswordError("Wrong encryption password")); - return; - } - } - store.dispatch(derivedAction); - } - - const isNewUser = !userInfo; - - return ( - -

Encryption Password

- {(isNewUser) ? -
-

Welcome to EteSync!

-

- Please set your encryption password below, and make sure you got it right, as it can't be recovered if lost! -

-
- : -

- You are logged in as {credentials.credentials.email}. - Please enter your encryption password to continue, or log out from the side menu. -

- } - - -
- ); -} - export default function LoginGate() { - const remoteCredentials = useRemoteCredentials(); const credentials = useCredentials(); - const fetchCount = useSelector((state: StoreState) => state.fetchCount); + const dispatch = useDispatch(); const [fetchError, setFetchError] = React.useState(); async function onFormSubmit(username: string, password: string, serviceApiUrl?: string) { serviceApiUrl = serviceApiUrl ? serviceApiUrl : C.serviceApiBase; try { setFetchError(undefined); - const ret = fetchCredentials(username, password, serviceApiUrl); + const ret = login(username, password, serviceApiUrl); await ret.payload; - store.dispatch(ret); + dispatch(ret); } catch (e) { setFetchError(e); } } - if (remoteCredentials === null) { + const loading = credentials === undefined; + + if (!credentials) { const style = { isSafe: { textDecoration: "none", @@ -130,7 +55,7 @@ export default function LoginGate() { 0} + loading={loading} />
@@ -147,13 +72,9 @@ export default function LoginGate() { ); - } else if (credentials === null) { - return ( - - ); } return ( - + ); } diff --git a/src/SideMenu/index.tsx b/src/SideMenu/index.tsx index c790a75..ddbe23a 100644 --- a/src/SideMenu/index.tsx +++ b/src/SideMenu/index.tsx @@ -35,7 +35,7 @@ export default function SideMenu(props: PropsType) { const history = useHistory(); function logoutDo() { - store.dispatch(logout(etesync!)); + store.dispatch(logout(etesync! as any)); props.onCloseDrawerRequest(); } diff --git a/src/components/EncryptionLoginForm.tsx b/src/components/EncryptionLoginForm.tsx deleted file mode 100644 index 835552e..0000000 --- a/src/components/EncryptionLoginForm.tsx +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-FileCopyrightText: © 2017 EteSync Authors -// SPDX-License-Identifier: AGPL-3.0-only - -import * as React from "react"; -import Button from "@material-ui/core/Button"; -import TextField from "@material-ui/core/TextField"; - -interface FormErrors { - errorEncryptionPassword?: string; -} - -class EncryptionLoginForm extends React.PureComponent { - public state: { - errors: FormErrors; - encryptionPassword: string; - }; - - public props: { - onSubmit: (encryptionPassword: string) => void; - loading?: boolean; - error?: Error; - }; - - constructor(props: any) { - super(props); - this.state = { - errors: {}, - encryptionPassword: "", - }; - this.generateEncryption = this.generateEncryption.bind(this); - this.handleInputChange = this.handleInputChange.bind(this); - } - - public handleInputChange(event: React.ChangeEvent) { - const name = event.target.name; - const value = event.target.value; - this.setState({ - [name]: value, - }); - } - - public generateEncryption(e: any) { - e.preventDefault(); - - const encryptionPassword = this.state.encryptionPassword; - - const errors: FormErrors = {}; - const fieldRequired = "This field is required!"; - if (!encryptionPassword) { - errors.errorEncryptionPassword = fieldRequired; - } - - if (Object.keys(errors).length) { - this.setState({ errors }); - return; - } else { - this.setState({ errors: {} }); - } - - this.props.onSubmit(encryptionPassword); - } - - public render() { - const styles = { - form: { - }, - submit: { - marginTop: 40, - textAlign: "right" as any, - }, - }; - - return ( - - {(this.props.error) && (
Error! {this.props.error.message}
)} -
- - -
- -
- -
- ); - } -} - -export default EncryptionLoginForm; diff --git a/src/credentials.tsx b/src/credentials.tsx new file mode 100644 index 0000000..bf8c1bd --- /dev/null +++ b/src/credentials.tsx @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: © 2017 Etebase Authors +// SPDX-License-Identifier: AGPL-3.0-only + +import { useSelector } from "react-redux"; +import { createSelector } from "reselect"; + +import * as Etebase from "etebase"; + +import * as store from "./store"; +import { usePromiseMemo } from "./helpers"; + +export const credentialsSelector = createSelector( + (state: store.StoreState) => state.credentials2.storedSession, + (storedSession) => { + if (storedSession) { + return Etebase.Account.restore(storedSession); + } else { + return Promise.resolve(null); + } + } +); + +export function useCredentials() { + const credentialsPromise = useSelector(credentialsSelector); + return usePromiseMemo(credentialsPromise, [credentialsPromise]); +} diff --git a/src/etebase-helpers.ts b/src/etebase-helpers.ts new file mode 100644 index 0000000..d38f7c1 --- /dev/null +++ b/src/etebase-helpers.ts @@ -0,0 +1,75 @@ +import memoize from "memoizee"; + +import * as Etebase from "etebase"; +import { useSelector } from "react-redux"; + +import { StoreState } from "./store"; +import { CacheCollectionsData, CacheItems, CacheItemsData } from "./store/reducers"; +import { usePromiseMemo } from "./helpers"; + +export const getCollections = memoize(async function (cachedCollections: CacheCollectionsData, etebase: Etebase.Account) { + const colMgr = getCollectionManager(etebase); + const ret: Etebase.Collection[] = []; + for (const cached of cachedCollections.values()) { + ret.push(await colMgr.cacheLoad(Etebase.fromBase64(cached))); + } + return ret; +}, { length: 1 }); + +export const getCollectionsByType = memoize(async function (cachedCollections: CacheCollectionsData, colType: string, etebase: Etebase.Account) { + const collections = await getCollections(cachedCollections, etebase); + const ret: Etebase.Collection[] = []; + for (const col of collections) { + const meta = await col.getMeta(); + if (meta.type === colType) { + ret.push(col); + } + } + return ret; +}, { length: 2 }); + +export const getItems = memoize(async function (cachedItems: CacheItems, itemMgr: Etebase.CollectionItemManager) { + const ret = new Map(); + for (const cached of cachedItems.values()) { + const item = await itemMgr.cacheLoad(Etebase.fromBase64(cached)); + ret.set(item.uid, item); + } + return ret; +}, { length: 1 }); + +export const getItemsByType = memoize(async function (cachedCollections: CacheCollectionsData, cachedItems: CacheItemsData, colType: string, etebase: Etebase.Account) { + const colMgr = getCollectionManager(etebase); + const collections = await getCollectionsByType(cachedCollections, colType, etebase); + const ret = new Map>(); + for (const col of collections) { + const itemMgr = colMgr.getItemManager(col); + const cachedColItems = cachedItems.get(col.uid); + if (cachedColItems) { + const items = await getItems(cachedColItems, itemMgr); + ret.set(col.uid, items); + } + } + return ret; +}, { length: 3 }); + +export const getCollectionManager = memoize(function (etebase: Etebase.Account) { + return etebase.getCollectionManager(); +}); + +// React specific stuff +export function useCollections(etebase: Etebase.Account, colType: string) { + const cachedCollections = useSelector((state: StoreState) => state.cache2.collections); + return usePromiseMemo( + getCollectionsByType(cachedCollections, colType, etebase), + [etebase, cachedCollections, colType] + ); +} + +export function useItems(etebase: Etebase.Account, colType: string) { + const cachedCollections = useSelector((state: StoreState) => state.cache2.collections); + const cachedItems = useSelector((state: StoreState) => state.cache2.items); + return usePromiseMemo( + getItemsByType(cachedCollections, cachedItems, colType, etebase), + [etebase, cachedCollections, cachedItems, colType] + ); +} diff --git a/src/helpers.tsx b/src/helpers.tsx index 1ba4938..024154c 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -119,4 +119,24 @@ export function mapPriority(priority: number): TaskPriorityType { } else { return TaskPriorityType.Undefined; } -} \ No newline at end of file +} + +export function usePromiseMemo(promise: Promise | undefined | null, deps: React.DependencyList, initial: T | undefined = undefined): T | undefined { + const [val, setVal] = React.useState((promise as any)._returnedValue ?? initial); + React.useEffect(() => { + let cancel = false; + if (promise === undefined || promise === null) { + return undefined; + } + promise.then((val) => { + (promise as any)._returnedValue = val; + if (!cancel) { + setVal(val); + } + }); + return () => { + cancel = true; + }; + }, [...deps, promise]); + return val; +} diff --git a/src/store/actions.ts b/src/store/actions.ts index f93d6d4..52b37f5 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -3,10 +3,12 @@ import { Action, createAction, createActions } from "redux-actions"; +import * as Etebase from "etebase"; + import * as EteSync from "etesync"; import { UserInfo } from "etesync"; -import { CredentialsData, CredentialsDataRemote, EntriesData, SettingsType } from "./"; +import { CredentialsData, EntriesData, SettingsType } from "./"; export const { fetchCredentials } = createActions({ FETCH_CREDENTIALS: (username: string, password: string, server: string) => { @@ -32,21 +34,6 @@ export const { fetchCredentials } = createActions({ }, }); -export const logout = createAction( - "LOGOUT", - (etesync: CredentialsDataRemote) => { - (async () => { - const authenticator = new EteSync.Authenticator(etesync.serviceApiUrl); - try { - await authenticator.invalidateToken(etesync.credentials.authToken); - } catch { - // Ignore for now. It usually means the token was a legacy one. - } - })(); - return; // We are not waiting on the above on purpose for now, just invalidate the token in the background - } -); - export const { deriveKey } = createActions({ DERIVE_KEY: (username: string, encryptionPassword: string) => { return EteSync.deriveKey(username, encryptionPassword); @@ -60,13 +47,87 @@ export const resetKey = createAction( } ); -export const login = (username: string, password: string, encryptionPassword: string, server: string) => { - return (dispatch: any) => { - dispatch(fetchCredentials(username, password, server)).then(() => - dispatch(deriveKey(username, encryptionPassword)) - ); - }; -}; +export const logout = createAction( + "LOGOUT", + async (etebase: Etebase.Account) => { + await etebase.logout(); + } +); + +export const login = createAction( + "LOGIN", + async (username: string, password: string, server: string) => { + const etebase = await Etebase.Account.login(username, password, server); + return etebase.save(); + } +); + +export const setCacheCollection = createAction( + "SET_CACHE_COLLECTION", + async (colMgr: Etebase.CollectionManager, col: Etebase.Collection) => { + return Etebase.toBase64(await colMgr.cacheSave(col)); + }, + (_colMgr: Etebase.CollectionManager, col: Etebase.Collection) => { + return col.uid; + } +); + +export const unsetCacheCollection = createAction( + "UNSET_CACHE_COLLECTION", + (_colMgr: Etebase.CollectionManager, colUid: string) => { + return colUid; + } +); + +export const setCacheItem = createAction( + "SET_CACHE_ITEM", + async (_col: Etebase.Collection, itemMgr: Etebase.CollectionItemManager, item: Etebase.CollectionItem) => { + return Etebase.toBase64(await itemMgr.cacheSave(item)); + }, + (col: Etebase.Collection, _itemMgr: Etebase.CollectionItemManager, item: Etebase.CollectionItem) => { + return { + colUid: col.uid, + itemUid: item.uid, + }; + } +); + +export const unsetCacheItem = createAction( + "UNSET_CACHE_ITEM", + (_colUid: string, _itemMgr: Etebase.CollectionItemManager, itemUid: string) => { + return itemUid; + }, + (colUid: string, _itemMgr: Etebase.CollectionItemManager, itemUid: string) => { + return { + colUid, + itemUid, + }; + } +); + +export const setSyncCollection = createAction( + "SET_SYNC_COLLECTION", + (colUid: string, stoken: string) => { + return { + colUid, + stoken, + }; + } +); + +export const setSyncGeneral = createAction( + "SET_SYNC_GENERAL", + (stoken: string | null) => { + return stoken; + } +); + +export const performSync = createAction( + "PERFORM_SYNC", + (syncPromise: Promise) => { + return syncPromise; + } +); export const { fetchListJournal } = createActions({ FETCH_LIST_JOURNAL: (etesync: CredentialsData) => { diff --git a/src/store/construct.ts b/src/store/construct.ts index b9e0cfa..a7da7e8 100644 --- a/src/store/construct.ts +++ b/src/store/construct.ts @@ -13,13 +13,24 @@ import { JournalsData, EntriesData, UserInfoData, CredentialsDataRemote, SettingsType, fetchCount, journals, entries, credentials, userInfo, settingsReducer, encryptionKeyReducer, errorsReducer, + syncGeneral, syncCollections, collections, items, + SyncGeneralData, SyncCollectionsData, CacheCollectionsData, CacheItemsData, CredentialsData2, } from "./reducers"; export interface StoreState { fetchCount: number; credentials: CredentialsDataRemote; + credentials2: CredentialsData2; settings: SettingsType; encryptionKey: {key: string}; + sync: { + collections: SyncCollectionsData; + general: SyncGeneralData; + }; + cache2: { + collections: CacheCollectionsData; + items: CacheItemsData; + }; cache: { journals: JournalsData; entries: EntriesData; @@ -60,6 +71,12 @@ const credentialsPersistConfig = { migrate: createMigrate(credentialsMigrations, { debug: false }), }; +const credentials2PersistConfig = { + key: "credentials2", + version: 0, + storage: localforage, +}; + const encryptionKeyPersistConfig = { key: "encryptionKey", storage: session, @@ -183,11 +200,71 @@ const cachePersistConfig = { migrate: createMigrate(cacheMigrations, { debug: false }), }; +const syncSerialize = (state: any, key: string | number) => { + if (key === "collections") { + return state.toJS(); + } + + return state; +}; + +const syncDeserialize = (state: any, key: string | number) => { + if (key === "collections") { + return ImmutableMap(state); + } + + return state; +}; + +const syncPersistConfig = { + key: "sync", + storage: localforage, + transforms: [createTransform(syncSerialize, syncDeserialize)], +}; + +const cache2Serialize = (state: any, key: string | number) => { + if (key === "collections") { + return state.toJS(); + } else if (key === "items") { + return state.toJS(); + } + + return state; +}; + +const cache2Deserialize = (state: any, key: string | number) => { + if (key === "collections") { + return ImmutableMap(state); + } else if (key === "items") { + return ImmutableMap(state).map((item: any) => { + return ImmutableMap(item); + }); + } + + return state; +}; + +const cache2PersistConfig = { + key: "cache2", + version: 0, + storage: localforage, + transforms: [createTransform(cache2Serialize, cache2Deserialize)] as any, +}; + const reducers = combineReducers({ fetchCount, settings: persistReducer(settingsPersistConfig, settingsReducer), credentials: persistReducer(credentialsPersistConfig, credentials), + credentials2: persistReducer(credentials2PersistConfig, credentials), encryptionKey: persistReducer(encryptionKeyPersistConfig, encryptionKeyReducer), + sync: persistReducer(syncPersistConfig, combineReducers({ + collections: syncCollections, + general: syncGeneral, + })), + cache2: persistReducer(cache2PersistConfig, combineReducers({ + collections, + items, + })), cache: persistReducer(cachePersistConfig, combineReducers({ entries, journals, diff --git a/src/store/reducers.ts b/src/store/reducers.ts index 29edabc..5244368 100644 --- a/src/store/reducers.ts +++ b/src/store/reducers.ts @@ -10,6 +10,13 @@ import * as EteSync from "etesync"; import * as actions from "./actions"; +export type JournalsData = ImmutableMap; + +export type EntriesListData = List; +export type EntriesData = ImmutableMap; + +export type UserInfoData = EteSync.UserInfo; + export interface CredentialsDataRemote { serviceApiUrl: string; credentials: EteSync.Credentials; @@ -19,49 +26,131 @@ export interface CredentialsData extends CredentialsDataRemote { encryptionKey: string; } -export type JournalsData = ImmutableMap; +interface BaseModel { + uid: string; +} -export type EntriesListData = List; -export type EntriesData = ImmutableMap; +export interface SyncCollectionsEntryData extends BaseModel { + stoken: string; +} -export type UserInfoData = EteSync.UserInfo; +export type SyncCollectionsData = ImmutableMap; -export const encryptionKeyReducer = handleActions( - { - [actions.deriveKey.toString()]: (_state: { key: string | null }, action: any) => ( - { key: action.payload } - ), - [actions.resetKey.toString()]: (_state: { key: string | null }, _action: any) => ( - { key: null } - ), - [actions.logout.toString()]: (_state: { key: string | null }, _action: any) => { - return { out: true, key: null }; - }, - }, - { key: null } -); +export type CacheItem = string; +export type CacheItems = ImmutableMap; +export type CacheItemsData = ImmutableMap; +export type CacheCollection = CacheItem; +export type CacheCollectionsData = ImmutableMap; + +export type SyncGeneralData = { + stoken: string | null; + lastSyncDate: Date; +}; + +export interface CredentialsData2 { + storedSession?: string; +} export const credentials = handleActions( { - [actions.fetchCredentials.toString()]: ( - state: CredentialsDataRemote, action: any) => { + [actions.login.toString()]: ( + state: CredentialsData2, action: Action) => { if (action.error) { return state; } else if (action.payload === undefined) { return state; } else { - const { - encryptionKey, // We don't want to set encryption key here. - ...payload - } = action.payload; - return payload; + return { + storedSession: action.payload, + }; + } + }, + [actions.logout.toString()]: (_state: CredentialsData2, _action: any) => { + return { storedSession: undefined }; + }, + }, + { storedSession: undefined } +); + +export const encryptionKeyReducer = handleActions( + { + }, + { key: null } +); + +export const syncCollections = handleActions( + { + [actions.setSyncCollection.toString()]: (state: SyncCollectionsData, action: Action) => { + if (action.payload !== undefined) { + return state.set(action.payload.uid, action.payload); } + return state; }, - [actions.logout.toString()]: (_state: CredentialsDataRemote, _action: any) => { + [actions.logout.toString()]: (state: SyncCollectionsData, _action: any) => { + return state.clear(); + }, + }, + ImmutableMap({}) +); + +export const syncGeneral = handleActions( + { + [actions.setSyncGeneral.toString()]: (state: SyncGeneralData, action: Action) => { + if (action.payload !== undefined) { + return { + stoken: action.payload, + lastSyncDate: new Date(), + }; + } + return state; + }, + [actions.logout.toString()]: (_state: SyncGeneralData, _action: any) => { return {}; }, }, - {} as CredentialsDataRemote + {} +); + +export const collections = handleActions( + { + [actions.setCacheCollection.toString()]: (state: CacheCollectionsData, action: ActionMeta) => { + if (action.payload !== undefined) { + return state.set(action.meta.colUid, action.payload); + } + return state; + }, + [actions.unsetCacheCollection.toString()]: (state: CacheCollectionsData, action: ActionMeta) => { + if (action.payload !== undefined) { + return state.remove(action.meta.colUid); + } + return state; + }, + }, + ImmutableMap({}) +); + +export const items = handleActions( + { + [actions.setCacheItem.toString()]: (state: CacheItemsData, action: ActionMeta) => { + if (action.payload !== undefined) { + return state.setIn([action.meta.colUid, action.meta.itemUid], action.payload); + } + return state; + }, + [actions.unsetCacheItem.toString()]: (state: CacheItemsData, action: ActionMeta) => { + if (action.payload !== undefined) { + return state.removeIn([action.meta.colUid, action.meta.itemUid]); + } + return state; + }, + [actions.unsetCacheCollection.toString()]: (state: CacheItemsData, action: ActionMeta) => { + if (action.payload !== undefined) { + return state.remove(action.meta.colUid); + } + return state; + }, + }, + ImmutableMap({}) ); function entriesListSetExtend( diff --git a/src/worker.ts b/src/worker.ts new file mode 100644 index 0000000..a49cfbe --- /dev/null +++ b/src/worker.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: © 2017 EteSync Authors +// SPDX-License-Identifier: AGPL-3.0-only + +// SPDX-FileCopyrightText: © 2017 EteSync Authors +// SPDX-License-Identifier: AGPL-3.0-only + +// SPDX-FileCopyrightText: © 2019 EteSync Authors +// SPDX-License-Identifier: GPL-3.0-only + +import * as EteSync from "etesync"; + +export const deriveKey = EteSync.deriveKey; diff --git a/yarn.lock b/yarn.lock index abb28df..23c0cbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,6 +1477,11 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@msgpack/msgpack@^1.12.2": + version "1.12.2" + resolved "https://registry.yarnpkg.com/@msgpack/msgpack/-/msgpack-1.12.2.tgz#6a22e99a49b131a8789053d0b0903834552da36f" + integrity sha512-Vwhc3ObxmDZmA5hY8mfsau2rJ4vGPvzbj20QSZ2/E1GDPF61QVyjLfNHak9xmel6pW4heRt3v1fHa6np9Ehfeg== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" @@ -1766,6 +1771,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== +"@types/memoizee@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.4.tgz#a8a5e917ef70c79523b8b8d57f529e49616a760c" + integrity sha512-c9+1g6+6vEqcw5UuM0RbfQV0mssmZcoG9+hNC5ptDCsv4G+XJW1Z4pE13wV5zbc9e0+YrDydALBTiD3nWG1a3g== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -4355,7 +4365,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: +es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -4364,7 +4374,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.50: es6-symbol "~3.1.3" next-tick "~1.0.0" -es6-iterator@2.0.3, es6-iterator@~2.0.3: +es6-iterator@2.0.3, es6-iterator@^2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -4381,6 +4391,16 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +es6-weak-map@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -4636,6 +4656,18 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +etebase@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/etebase/-/etebase-0.7.4.tgz#ba7b952ce05a26819610ead153d17d1247c6ba64" + integrity sha512-Efxt7wNCNi5YH5Q7u01pX2D2FGx6Bw3i2l/WCKlgEQ/GGOzoWk0kaIAt5YmwA5oLtXc9pEhujMXWT7ocTy+jDw== + dependencies: + "@msgpack/msgpack" "^1.12.2" + libsodium-wrappers "^0.7.6" + node-fetch "^2.6.0" + urijs "^1.19.1" + optionalDependencies: + react-native-sodium "^0.3.8" + etesync@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/etesync/-/etesync-0.3.1.tgz#2af257d5617679177d93fb8f6bdaf64462aa15e0" @@ -4645,6 +4677,14 @@ etesync@^0.3.1: sjcl "git+https://github.com/etesync/sjcl" urijs "^1.19.1" +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= + dependencies: + d "1" + es5-ext "~0.10.14" + eventemitter3@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -6089,6 +6129,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-promise@^2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + is-regex@^1.0.4, is-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" @@ -6965,6 +7010,18 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +libsodium-wrappers@^0.7.6: + version "0.7.8" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.8.tgz#d95cdf3e7236c2aef76844bf8e1929ba9eef3e9e" + integrity sha512-PDhPWXBqd/SaqAFUBgH2Ux7b3VEEJgyD6BQB+VdNFJb9PbExGr/T/myc/MBoSvl8qLzfm0W0IVByOQS5L1MrCg== + dependencies: + libsodium "0.7.8" + +libsodium@0.7.8: + version "0.7.8" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.8.tgz#fbd12247b7b1353f88d8de1cbc66bc1a07b2e008" + integrity sha512-/Qc+APf0jbeWSaeEruH0L1/tbbT+sbf884ZL0/zV/0JXaDPBzYkKbyb/wmxMHgAHzm3t6gqe7bOOXAVwfqVikQ== + lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" @@ -7129,6 +7186,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-queue@0.1: + version "0.1.0" + resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" + integrity sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM= + dependencies: + es5-ext "~0.10.2" + make-dir@^2.0.0, make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -7213,6 +7277,20 @@ memoize-one@^5.1.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== +memoizee@^0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" + integrity sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg== + dependencies: + d "1" + es5-ext "^0.10.45" + es6-weak-map "^2.0.2" + event-emitter "^0.3.5" + is-promise "^2.1" + lru-queue "0.1" + next-tick "1" + timers-ext "^0.1.5" + memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -7516,6 +7594,11 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next-tick@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" @@ -7534,6 +7617,11 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" +node-fetch@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" + integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== + node-forge@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" @@ -9252,6 +9340,11 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-native-sodium@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/react-native-sodium/-/react-native-sodium-0.3.8.tgz#ccb86a67d2cd6856be227ed962ffc019b32e7642" + integrity sha512-G+ZrNUr8yWPOGYSzM+Gc+5wKeLcQBlN7SrFjt9JzbeuNM+cEJlj4ntdoaMe43cciWVPr4yQIpFOu2Qapnk5HRQ== + react-overlays@^2.0.0-0: version "2.1.1" resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-2.1.1.tgz#ffe2090c4a10da6b8947a1c7b1a67d0457648a0d" @@ -10776,6 +10869,14 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +timers-ext@^0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" + integrity sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ== + dependencies: + es5-ext "~0.10.46" + next-tick "1" + timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"