add client boot screen options for clear cache and logout
This commit is contained in:
parent
5d6dab9cb9
commit
5d9c9d588d
6 changed files with 196 additions and 53 deletions
|
|
@ -14,6 +14,7 @@ import { ClientContext } from '../../../client/context';
|
|||
import { ClientContextProvider } from '../../hooks/useClient';
|
||||
import { InitialSyncSplashScreen } from './InitialSyncSplashScreen';
|
||||
import { AutoDiscovery } from './AutoDiscovery';
|
||||
import { ClientRootOptions } from './ClientRootOptions';
|
||||
|
||||
type ClientRootProps = {
|
||||
children: ReactNode;
|
||||
|
|
@ -46,6 +47,7 @@ export function ClientRoot({ children }: ClientRootProps) {
|
|||
|
||||
return (
|
||||
<AutoDiscovery userId={userId!} baseUrl={baseUrl!}>
|
||||
{initState.status !== AsyncStatus.Success && <ClientRootOptions userId={userId!} />}
|
||||
<SpecVersions baseUrl={baseUrl!}>
|
||||
{initState.status === AsyncStatus.Error && (
|
||||
<SplashScreen>
|
||||
|
|
@ -96,7 +98,7 @@ export function ClientRoot({ children }: ClientRootProps) {
|
|||
)}
|
||||
{initState.status === AsyncStatus.Success && (
|
||||
<ClientContextProvider value={initState.data}>
|
||||
<InitialSyncSplashScreen sync={initState.data.sync}>
|
||||
<InitialSyncSplashScreen>
|
||||
<SyncStatus />
|
||||
<ServerConfigsLoader>
|
||||
{(serverConfigs) => (
|
||||
|
|
|
|||
106
src/app/pages/client/ClientRootOptions.tsx
Normal file
106
src/app/pages/client/ClientRootOptions.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
Box,
|
||||
config,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Menu,
|
||||
MenuItem,
|
||||
PopOut,
|
||||
RectCords,
|
||||
Text,
|
||||
} from 'folds';
|
||||
import React, { MouseEventHandler, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { ClientContext } from '../../../client/context';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { deleteClientStorage } from '../../../client/storage';
|
||||
import { clearNavToActivePathStore } from '../../state/navToActivePath';
|
||||
import { deleteClientCryptoStorage } from '../../../client/crypto';
|
||||
|
||||
export function ClientRootOptions({ userId, ct }: { userId: string; ct?: ClientContext }) {
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
|
||||
const handleToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const cords = evt.currentTarget.getBoundingClientRect();
|
||||
setMenuAnchor((currentState) => {
|
||||
if (currentState) return undefined;
|
||||
return cords;
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearCache = () => {
|
||||
clearNavToActivePathStore(userId);
|
||||
if (ct) {
|
||||
ct.clearCacheAndReload();
|
||||
return;
|
||||
}
|
||||
deleteClientStorage(userId);
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
if (ct) {
|
||||
ct.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.clear();
|
||||
await deleteClientCryptoStorage(userId);
|
||||
await deleteClientStorage(userId);
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: config.space.S100,
|
||||
right: config.space.S100,
|
||||
}}
|
||||
variant="Background"
|
||||
fill="None"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<Icon size="200" src={Icons.VerticalDots} />
|
||||
<PopOut
|
||||
anchor={menuAnchor}
|
||||
position="Bottom"
|
||||
align="End"
|
||||
offset={6}
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
returnFocusOnDeactivate: false,
|
||||
onDeactivate: () => setMenuAnchor(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
|
||||
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem onClick={handleClearCache} size="300" radii="300">
|
||||
<Text as="span" size="T300" truncate>
|
||||
Clear Cache and Reload
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={handleLogout}
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
>
|
||||
<Text as="span" size="T300" truncate>
|
||||
Logout
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ import { useObservableValue } from '../../hooks/useObservableValue';
|
|||
import { createFakeProgress } from '../../../client/responseProgress';
|
||||
import { bytesToSize } from '../../utils/common';
|
||||
import { SyncContext, SyncProgress } from '../../../client/sync';
|
||||
import { ClientRootOptions } from './ClientRootOptions';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
|
||||
function InitialSyncProgressScreen({
|
||||
sync,
|
||||
|
|
@ -112,17 +114,17 @@ function InitialSyncProgressScreen({
|
|||
);
|
||||
}
|
||||
|
||||
export function InitialSyncSplashScreen({
|
||||
sync,
|
||||
children,
|
||||
}: {
|
||||
sync: SyncContext;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const initialSync = useObservableValue(sync.controller.initialSync);
|
||||
export function InitialSyncSplashScreen({ children }: { children: ReactNode }) {
|
||||
const ct = useClient();
|
||||
const initialSync = useObservableValue(ct.sync.controller.initialSync);
|
||||
|
||||
if (initialSync) {
|
||||
return <InitialSyncProgressScreen sync={sync} progressObservable={initialSync.progress} />;
|
||||
return (
|
||||
<>
|
||||
<ClientRootOptions userId={ct.session.userId} />
|
||||
<InitialSyncProgressScreen sync={ct.sync} progressObservable={initialSync.progress} />;
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
|
|
|
|||
55
src/client/crypto.ts
Normal file
55
src/client/crypto.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { deleteDB } from 'idb';
|
||||
import { Machine, OutgoingRequestSender } from 'neurone/lib/crypto/machine';
|
||||
import { MatrixRequest } from 'neurone/lib/matrix-request';
|
||||
import { MatrixRequestRetrier, sleep } from 'neurone/lib/utils';
|
||||
import { Session } from '../app/state/sessions';
|
||||
|
||||
const getCryptoStoreName = (userId: string): string => `crypto${userId}`;
|
||||
|
||||
// the matrix-crypto-sdk add postfix to storeName
|
||||
const getCryptoDBName = (userId: string): string =>
|
||||
`${getCryptoStoreName(userId)}::matrix-sdk-crypto`;
|
||||
|
||||
let closeMachineRef: (() => void) | undefined;
|
||||
|
||||
export const initializeCrypto = async (
|
||||
session: Session,
|
||||
request: MatrixRequest
|
||||
): Promise<{
|
||||
machine: Machine;
|
||||
deleteCryptoStorage: () => Promise<void>;
|
||||
closeMachine: () => void;
|
||||
}> => {
|
||||
const storeName = getCryptoStoreName(session.userId);
|
||||
|
||||
const { olmMachine, closeMachine: closeMachineInner } = await Machine.init(
|
||||
session.userId,
|
||||
session.deviceId,
|
||||
storeName
|
||||
);
|
||||
|
||||
const closeMachine = () => {
|
||||
closeMachineInner();
|
||||
closeMachineRef = undefined;
|
||||
};
|
||||
|
||||
closeMachineRef = closeMachine;
|
||||
|
||||
const outgoingSenderAbortCont = new AbortController();
|
||||
const outgoingRequestRetrier = new MatrixRequestRetrier(sleep);
|
||||
const outgoingSender = new OutgoingRequestSender(request, outgoingSenderAbortCont.signal);
|
||||
|
||||
const machine = new Machine(olmMachine, outgoingSender, outgoingRequestRetrier);
|
||||
|
||||
const deleteCryptoStorage = async () => {
|
||||
closeMachine();
|
||||
await deleteDB(getCryptoDBName(session.userId));
|
||||
};
|
||||
|
||||
return { machine, deleteCryptoStorage, closeMachine };
|
||||
};
|
||||
|
||||
export const deleteClientCryptoStorage = async (userId: string): Promise<void> => {
|
||||
closeMachineRef?.();
|
||||
await deleteDB(getCryptoDBName(userId));
|
||||
};
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
import { Machine, OutgoingRequestSender } from 'neurone/lib/crypto/machine';
|
||||
import { MatrixRequestRetrier, sleep } from 'neurone/lib/utils';
|
||||
import { MatrixRequest } from 'neurone/lib/matrix-request';
|
||||
import { Disposer } from 'neurone/lib/utils/disposable';
|
||||
import { deleteDB } from 'idb';
|
||||
import { Session } from '../app/state/sessions';
|
||||
import {
|
||||
ClientContext,
|
||||
|
|
@ -34,43 +31,12 @@ import { SyncResponseManager } from './SyncResponseManager';
|
|||
import { LogoutManager } from './LogoutManager';
|
||||
import { MachineManager } from './MachineManager';
|
||||
import { DecryptionManager } from './decryption';
|
||||
|
||||
const initializeCrypto = async (
|
||||
session: Session,
|
||||
request: MatrixRequest
|
||||
): Promise<{
|
||||
machine: Machine;
|
||||
deleteCryptoStorage: () => Promise<void>;
|
||||
}> => {
|
||||
const storeName = `crypto${session.userId}`;
|
||||
|
||||
const { olmMachine, closeMachine } = await Machine.init(
|
||||
session.userId,
|
||||
session.deviceId,
|
||||
storeName
|
||||
);
|
||||
|
||||
const outgoingSenderAbortCont = new AbortController();
|
||||
const outgoingRequestRetrier = new MatrixRequestRetrier(sleep);
|
||||
const outgoingSender = new OutgoingRequestSender(request, outgoingSenderAbortCont.signal);
|
||||
|
||||
const machine = new Machine(olmMachine, outgoingSender, outgoingRequestRetrier);
|
||||
|
||||
const deleteCryptoStorage = async () => {
|
||||
closeMachine();
|
||||
|
||||
// the matrix-crypto-sdk add postfix to storeName
|
||||
const realStoreName = `${storeName}::matrix-sdk-crypto`;
|
||||
await deleteDB(realStoreName);
|
||||
};
|
||||
|
||||
return { machine, deleteCryptoStorage };
|
||||
};
|
||||
import { initializeCrypto } from './crypto';
|
||||
|
||||
export const initializeClient = async (session: Session): Promise<ClientContext> => {
|
||||
const disposer = new Disposer();
|
||||
|
||||
const { storage, deleteStorage } = await createStorage('client', session.userId);
|
||||
const { storage, deleteStorage } = await createStorage(session.userId);
|
||||
|
||||
const requestAuthJSON: MatrixRequest = getAuthJSONRequest(session.baseUrl, session.accessToken);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,16 +78,20 @@ export interface Storage {
|
|||
presence: PresenceCollection;
|
||||
}
|
||||
|
||||
const createDBName = (storageName: string, userId: string): string => `${storageName}${userId}`;
|
||||
const getDBName = (userId: string): string => `client${userId}`;
|
||||
|
||||
// create a reference to opened database close callback.
|
||||
// used in clearing cache if client failed to initilize.
|
||||
let disconnectStorageRef: (() => void) | undefined;
|
||||
|
||||
export const createStorage = async (
|
||||
storageName: string,
|
||||
userId: string
|
||||
): Promise<{
|
||||
storage: Storage;
|
||||
deleteStorage: () => Promise<void>;
|
||||
disconnectStorage: () => void;
|
||||
}> => {
|
||||
const dbName = createDBName(storageName, userId);
|
||||
const dbName = getDBName(userId);
|
||||
|
||||
// TODO: handle all openDB callbacks.
|
||||
const idb = await openDB(dbName, DATABASE_VERSION, {
|
||||
|
|
@ -118,6 +122,13 @@ export const createStorage = async (
|
|||
},
|
||||
});
|
||||
|
||||
const disconnectStorage = () => {
|
||||
idb.close();
|
||||
disconnectStorageRef = undefined;
|
||||
};
|
||||
|
||||
disconnectStorageRef = disconnectStorage;
|
||||
|
||||
const driver = new IndexedDBStorageDriver(unwrap(idb));
|
||||
|
||||
const storage: Storage = {
|
||||
|
|
@ -147,14 +158,15 @@ export const createStorage = async (
|
|||
};
|
||||
|
||||
const deleteStorage = async () => {
|
||||
idb.close();
|
||||
disconnectStorage();
|
||||
await deleteDB(dbName);
|
||||
};
|
||||
|
||||
return { storage, deleteStorage };
|
||||
return { storage, deleteStorage, disconnectStorage };
|
||||
};
|
||||
|
||||
export const deleteClientStorage = async (storageName: string, userId: string) => {
|
||||
const dbName = createDBName(storageName, userId);
|
||||
export const deleteClientStorage = async (userId: string) => {
|
||||
disconnectStorageRef?.();
|
||||
const dbName = getDBName(userId);
|
||||
await deleteDB(dbName);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue