import { AppState } from '@/ui_models/app_state'; import { SNApplication, SessionStrings, UuidString, isNullOrUndefined, RemoteSession, } from '@standardnotes/snjs'; import { FunctionComponent } from 'preact'; import { useState, useEffect, useRef, useMemo } from 'preact/hooks'; import { Dialog } from '@reach/dialog'; import { Alert } from '@reach/alert'; import { AlertDialog, AlertDialogDescription, AlertDialogLabel, } from '@reach/alert-dialog'; import { WebApplication } from '@/ui_models/application'; import { observer } from 'mobx-react-lite'; type Session = RemoteSession & { revoking?: true; }; function useSessions( application: SNApplication ): [ Session[], () => void, boolean, (uuid: UuidString) => Promise, string ] { const [sessions, setSessions] = useState([]); const [lastRefreshDate, setLastRefreshDate] = useState(Date.now()); const [refreshing, setRefreshing] = useState(true); const [errorMessage, setErrorMessage] = useState(''); useEffect(() => { (async () => { setRefreshing(true); const response = await application.getSessions(); if ('error' in response || isNullOrUndefined(response.data)) { if (response.error?.message) { setErrorMessage(response.error.message); } else { setErrorMessage('An unknown error occured while loading sessions.'); } } else { const sessions = response.data as RemoteSession[]; setSessions(sessions); setErrorMessage(''); } setRefreshing(false); })(); }, [application, lastRefreshDate]); function refresh() { setLastRefreshDate(Date.now()); } async function revokeSession(uuid: UuidString) { const sessionsBeforeRevoke = sessions; const responsePromise = application.revokeSession(uuid); const sessionsDuringRevoke = sessions.slice(); const toRemoveIndex = sessions.findIndex( (session) => session.uuid === uuid ); sessionsDuringRevoke[toRemoveIndex] = { ...sessionsDuringRevoke[toRemoveIndex], revoking: true, }; setSessions(sessionsDuringRevoke); const response = await responsePromise; if (isNullOrUndefined(response)) { setSessions(sessionsBeforeRevoke); } else if ('error' in response) { if (response.error?.message) { setErrorMessage(response.error?.message); } else { setErrorMessage('An unknown error occured while revoking the session.'); } setSessions(sessionsBeforeRevoke); } else { setSessions(sessions.filter((session) => session.uuid !== uuid)); } } return [sessions, refresh, refreshing, revokeSession, errorMessage]; } const SessionsModalContent: FunctionComponent<{ appState: AppState; application: SNApplication; }> = ({ appState, application }) => { const close = () => appState.closeSessionsModal(); const [sessions, refresh, refreshing, revokeSession, errorMessage] = useSessions(application); const [confirmRevokingSessionUuid, setRevokingSessionUuid] = useState(''); const closeRevokeSessionAlert = () => setRevokingSessionUuid(''); const cancelRevokeRef = useRef(null); const formatter = useMemo( () => new Intl.DateTimeFormat(undefined, { year: 'numeric', month: 'long', day: 'numeric', weekday: 'long', hour: 'numeric', minute: 'numeric', }), [] ); return ( <>
Active Sessions
{refreshing ? ( <>

Loading sessions

) : ( <> {errorMessage && ( {errorMessage} )} {sessions.length > 0 && (
    {sessions.map((session) => (
  • {session.device_info}

    {session.current ? ( Current session ) : ( <>

    Signed in on{' '} {formatter.format(session.updated_at)}

    )}
  • ))}
)} )}
{confirmRevokingSessionUuid && ( { setRevokingSessionUuid(''); }} leastDestructiveRef={cancelRevokeRef} >
{SessionStrings.RevokeTitle}

{SessionStrings.RevokeText}

)} ); }; export const SessionsModal: FunctionComponent<{ appState: AppState; application: WebApplication; }> = observer(({ appState, application }) => { if (appState.isSessionsModalVisible) { return ( ); } else { return null; } });