import { AppState } from '@/ui_models/app_state'; import { PureViewCtrl } from '@/views'; import { SNApplication, RemoteSession, 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), []); } function useSessions( application: SNApplication ): [ RemoteSession[], () => 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 RemoteSession[]; setSessions(sessions); setErrorMessage(''); } setRefreshing(false); })(); }, [lastRefreshDate]); function refresh() { setLastRefreshDate(Date.now()); } async function revokeSession(uuid: UuidString) { let sessionsBeforeRevoke = sessions; setSessions(sessions.filter((session) => session.uuid !== uuid)); const response = await application.revokeSession(uuid); if ('error' in response) { if (response.error?.message) { setErrorMessage(response.error?.message); } else { setErrorMessage('An unknown error occured while revoking the session.'); } setSessions(sessionsBeforeRevoke); } } 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 [revokingSessionUuid, 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)}

    )}
  • ))}
)} )}
{revokingSessionUuid && (
Revoke this session?

The associated app will be signed out and all data removed from the device when it is next launched. You can sign back in on that device at any time.

)} ); }; 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: '=', }, }; }