import * as plugins from '../plugins.js'; import * as interfaces from '../../ts_interfaces/index.js'; import { appState } from './shared.js'; import { getActionContext } from './login.js'; import { captureRealtimeTicket, isRealtimeTicketCurrent } from './realtime.js'; export interface IEmailOpsState { emails: interfaces.requests.IEmail[]; traffic: interfaces.requests.IEmailLogTraffic | null; searchQuery: string; selectedRange: { from: number; to: number } | null; securityFindings: interfaces.requests.IEmailSecurityFinding[]; securityFindingsLoading: boolean; securityFindingsError: string | null; isLoading: boolean; error: string | null; lastUpdated: number; lastSecurityUpdated: number; } export const emailOpsStatePart = await appState.getStatePart( 'emailOps', { emails: [], traffic: null, searchQuery: '', selectedRange: null, securityFindings: [], securityFindingsLoading: false, securityFindingsError: null, isLoading: false, error: null, lastUpdated: 0, lastSecurityUpdated: 0, }, 'soft' ); // Email Operations Actions // ============================================================================ let activeSearchQuery = ''; let activeSelectedRange: { from: number; to: number } | null = null; let emailListRequestGeneration = 0; let securityFindingsRequestGeneration = 0; export interface IEmailOpsFetchOptions { searchQuery?: string; selectedRange?: { from: number; to: number } | null; } export function setActiveEmailSearchQuery(searchQueryArg: string): string { const nextSearchQuery = searchQueryArg.trim().slice(0, 200); if (nextSearchQuery === activeSearchQuery) return activeSearchQuery; activeSearchQuery = nextSearchQuery; emailListRequestGeneration++; const currentState = emailOpsStatePart.getState()!; emailOpsStatePart.setState({ ...currentState, searchQuery: activeSearchQuery, }); return activeSearchQuery; } export function setActiveEmailSelectedRange( rangeArg: { from: number; to: number } | null, ): { from: number; to: number } | null { if (rangeArg && ( !Number.isSafeInteger(rangeArg.from) || !Number.isSafeInteger(rangeArg.to) || rangeArg.from < 0 || rangeArg.to <= rangeArg.from )) { throw new Error('Invalid email time range'); } activeSelectedRange = rangeArg ? { ...rangeArg } : null; emailListRequestGeneration++; const currentState = emailOpsStatePart.getState()!; emailOpsStatePart.setState({ ...currentState, selectedRange: activeSelectedRange, }); return activeSelectedRange; } // Fetch All Emails Action export const fetchAllEmailsAction = emailOpsStatePart.createAction(async ( statePartArg, dataArg, ): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const searchQuery = dataArg?.searchQuery !== undefined ? setActiveEmailSearchQuery(dataArg.searchQuery) : activeSearchQuery; const selectedRange = dataArg?.selectedRange !== undefined ? setActiveEmailSelectedRange(dataArg.selectedRange) : activeSelectedRange; const requestGeneration = ++emailListRequestGeneration; const ticket = captureRealtimeTicket(['emailQueue', 'smtpAttempts']); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetAllEmails >('/typedrequest', 'getAllEmails'); const response = await request.fire({ identity: context.identity, ...(searchQuery ? { search: searchQuery } : {}), ...(selectedRange ? selectedRange : {}), }); if ( requestGeneration !== emailListRequestGeneration || !isRealtimeTicketCurrent(ticket) ) return statePartArg.getState()!; return { ...statePartArg.getState()!, emails: response.emails, traffic: response.traffic, searchQuery, isLoading: false, selectedRange, error: null, lastUpdated: Date.now(), }; } catch (error) { if ( requestGeneration !== emailListRequestGeneration || !isRealtimeTicketCurrent(ticket) ) return statePartArg.getState()!; return { ...statePartArg.getState()!, isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch emails', }; } }); export const fetchEmailSecurityFindingsAction = emailOpsStatePart.createAction(async ( statePartArg, ): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const requestGeneration = ++securityFindingsRequestGeneration; const ticket = captureRealtimeTicket(['emailQueue', 'smtpAttempts']); statePartArg.setState({ ...currentState, securityFindingsLoading: true, securityFindingsError: null, }); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetEmailSecurityFindings >('/typedrequest', 'getEmailSecurityFindings'); const response = await request.fire({ identity: context.identity, limit: 200, }); if ( requestGeneration !== securityFindingsRequestGeneration || !isRealtimeTicketCurrent(ticket) ) return statePartArg.getState()!; return { ...statePartArg.getState()!, securityFindings: response.findings, securityFindingsLoading: false, securityFindingsError: null, lastSecurityUpdated: Date.now(), }; } catch (error) { if ( requestGeneration !== securityFindingsRequestGeneration || !isRealtimeTicketCurrent(ticket) ) return statePartArg.getState()!; return { ...statePartArg.getState()!, securityFindingsLoading: false, securityFindingsError: error instanceof Error ? error.message : 'Failed to fetch email security findings', }; } }); // ============================================================================ export async function fetchEmailDetailSnapshot( emailIdArg: string, ): Promise { const context = getActionContext(); if (!context.identity) return null; const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetEmailDetail >('/typedrequest', 'getEmailDetail'); const response = await request.fire({ identity: context.identity, emailId: emailIdArg, }); return response.email; }