import { AppState } from '@/ui_models/app_state'; import { PureViewCtrl } from '@/views'; import { SNApplication, RemoteSession, SessionStrings, UuidString, } from '@standardnotes/snjs'; import { autorun, IAutorunOptions, IReactionPublic } from 'mobx'; import { render, FunctionComponent } from 'preact'; import { useState, useEffect, useRef } from 'preact/hooks'; import { Dialog } from '@reach/dialog'; import { Alert } from '@reach/alert'; import { AlertDialog, AlertDialogDescription, AlertDialogLabel, } from '@reach/alert-dialog'; function useAutorun(view: (r: IReactionPublic) => any, opts?: IAutorunOptions) { useEffect(() => autorun(view, opts), []); } 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) { if (response.error?.message) { setErrorMessage(response.error.message); } else { setErrorMessage('An unknown error occured while loading sessions.'); } } else { const sessions = response as Session[]; setSessions(sessions); setErrorMessage(''); } setRefreshing(false); })(); }, [lastRefreshDate]); function refresh() { setLastRefreshDate(Date.now()); } async function revokeSession(uuid: UuidString) { const responsePromise = application.revokeSession(uuid); let sessionsBeforeRevoke = sessions; 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 ('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 SessionsModal: 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(); const formatter = 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 && (
{SessionStrings.RevokeTitle}

{SessionStrings.RevokeText}

)} ); }; const Sessions: FunctionComponent<{ appState: AppState; application: SNApplication; }> = ({ appState, application }) => { const [showModal, setShowModal] = useState(false); useAutorun(() => setShowModal(appState.isSessionsModalVisible)); if (showModal) { return ; } else { return null; } }; class SessionsModalCtrl extends PureViewCtrl<{}, {}> { /* @ngInject */ constructor(private $element: JQLite, $timeout: ng.ITimeoutService) { super($timeout); this.$element = $element; } $onChanges() { render( , this.$element[0] ); } } export function SessionsModalDirective() { return { controller: SessionsModalCtrl, bindToController: true, scope: { application: '=', }, }; }