import { AppState } from '@/UIModels/AppState' 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 '@/UIModels/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) })().catch(console.error) }, [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 } })