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'; // ============================================================================ // Route Management State // ============================================================================ export interface IRouteManagementState { mergedRoutes: interfaces.data.IMergedRoute[]; warnings: interfaces.data.IRouteWarning[]; httpRedirects: interfaces.data.IHttpRedirectInfo[]; apiTokens: interfaces.data.IApiTokenInfo[]; gatewayClients: interfaces.data.IGatewayClient[]; isLoading: boolean; error: string | null; lastUpdated: number; } export const routeManagementStatePart = await appState.getStatePart( 'routeManagement', { mergedRoutes: [], warnings: [], httpRedirects: [], apiTokens: [], gatewayClients: [], isLoading: false, error: null, lastUpdated: 0, }, 'soft' ); // Route Management Actions // ============================================================================ export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const ticket = captureRealtimeTicket('routes'); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetMergedRoutes >('/typedrequest', 'getMergedRoutes'); const response = await request.fire({ identity: context.identity, }); if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, mergedRoutes: response.routes, warnings: response.warnings, isLoading: false, error: null, lastUpdated: Date.now(), }; } catch (error) { if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch routes', }; } }); export const fetchHttpRedirectsAction = routeManagementStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const ticket = captureRealtimeTicket('routes'); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetHttpRedirects >('/typedrequest', 'getHttpRedirects'); const response = await request.fire({ identity: context.identity, }); if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, httpRedirects: response.redirects, isLoading: false, error: null, lastUpdated: Date.now(), }; } catch (error) { if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch HTTP redirects', }; } }); export const createRouteAction = routeManagementStatePart.createAction<{ route: any; enabled?: boolean; metadata?: any; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateRoute >('/typedrequest', 'createRoute'); await request.fire({ identity: context.identity!, route: dataArg.route, enabled: dataArg.enabled, metadata: dataArg.metadata, }); return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to create route', }; } }); export const updateRouteAction = routeManagementStatePart.createAction<{ id: string; route?: any; enabled?: boolean; metadata?: any; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateRoute >('/typedrequest', 'updateRoute'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, route: dataArg.route, enabled: dataArg.enabled, metadata: dataArg.metadata, }); if (!response.success) { throw new Error(response.message || 'Failed to update route'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to update route', }; } }); export const deleteRouteAction = routeManagementStatePart.createAction( async (statePartArg, routeId, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteRoute >('/typedrequest', 'deleteRoute'); const response = await request.fire({ identity: context.identity!, id: routeId, }); if (!response.success) { throw new Error(response.message || 'Failed to delete route'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to delete route', }; } } ); export const toggleRouteAction = routeManagementStatePart.createAction<{ id: string; enabled: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ToggleRoute >('/typedrequest', 'toggleRoute'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, enabled: dataArg.enabled, }); if (!response.success) { throw new Error(response.message || 'Failed to toggle route'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to toggle route', }; } }); export const createLetsEncryptHttp01ForwardAction = routeManagementStatePart.createAction<{ config: interfaces.data.ILetsEncryptHttp01ForwardConfig; enabled?: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateLetsEncryptHttp01Forward >('/typedrequest', 'createLetsEncryptHttp01Forward'); const response = await request.fire({ identity: context.identity!, config: dataArg.config, enabled: dataArg.enabled, }); if (!response.success) { throw new Error(response.message || 'Failed to create Special Forward'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to create Special Forward', }; } }); export const updateLetsEncryptHttp01ForwardAction = routeManagementStatePart.createAction<{ id: string; config: interfaces.data.ILetsEncryptHttp01ForwardConfig; enabled?: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateLetsEncryptHttp01Forward >('/typedrequest', 'updateLetsEncryptHttp01Forward'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, config: dataArg.config, enabled: dataArg.enabled, }); if (!response.success) { throw new Error(response.message || 'Failed to update Special Forward'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to update Special Forward', }; } }); export const deleteLetsEncryptHttp01ForwardAction = routeManagementStatePart.createAction( async (statePartArg, routeId, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteLetsEncryptHttp01Forward >('/typedrequest', 'deleteLetsEncryptHttp01Forward'); const response = await request.fire({ identity: context.identity!, id: routeId, }); if (!response.success) { throw new Error(response.message || 'Failed to delete Special Forward'); } return await actionContext!.dispatch(fetchMergedRoutesAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to delete Special Forward', }; } }, ); // ============================================================================ // API Token Actions // ============================================================================ export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ListApiTokens >('/typedrequest', 'listApiTokens'); const response = await request.fire({ identity: context.identity, }); return { ...currentState, apiTokens: response.tokens, }; } catch (error) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to fetch tokens', }; } }); export const fetchGatewayClientsAction = routeManagementStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ListGatewayClients >('/typedrequest', 'listGatewayClients'); const response = await request.fire({ identity: context.identity }); return { ...currentState, gatewayClients: response.gatewayClients, error: null, lastUpdated: Date.now(), }; } catch (error) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to fetch gateway clients', }; } }); export async function createGatewayClient(data: { id?: string; type: interfaces.data.IGatewayClient['type']; name: string; description?: string; hostnamePatterns?: string[]; allowedRouteTargets?: interfaces.data.IGatewayClient['allowedRouteTargets']; }) { const context = getActionContext(); const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateGatewayClient >('/typedrequest', 'createGatewayClient'); return request.fire({ identity: context.identity!, capabilities: { readDomains: true, readDnsRecords: true, syncRoutes: true, syncDnsRecords: false, requestCertificates: false, }, ...data, }); } export const updateGatewayClientAction = routeManagementStatePart.createAction<{ id: string; name?: string; description?: string; hostnamePatterns?: string[]; allowedRouteTargets?: interfaces.data.IGatewayClient['allowedRouteTargets']; enabled?: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateGatewayClient >('/typedrequest', 'updateGatewayClient'); await request.fire({ identity: context.identity!, ...dataArg }); return await actionContext!.dispatch(fetchGatewayClientsAction, null); } catch (error) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to update gateway client', }; } }); export const deleteGatewayClientAction = routeManagementStatePart.createAction( async (statePartArg, gatewayClientId, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteGatewayClient >('/typedrequest', 'deleteGatewayClient'); await request.fire({ identity: context.identity!, id: gatewayClientId }); return await actionContext!.dispatch(fetchGatewayClientsAction, null); } catch (error) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to delete gateway client', }; } }, ); export async function createGatewayClientToken( gatewayClientId: string, name?: string, expiresInDays?: number | null, ) { const context = getActionContext(); const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateGatewayClientToken >('/typedrequest', 'createGatewayClientToken'); return request.fire({ identity: context.identity!, gatewayClientId, name, expiresInDays, }); } // Users export async function createApiToken( name: string, scopes: interfaces.data.TApiTokenScope[], expiresInDays?: number | null, policy?: any, ) { const context = getActionContext(); const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateApiToken >('/typedrequest', 'createApiToken'); return request.fire({ identity: context.identity!, name, scopes, policy, expiresInDays, }); } export async function rollApiToken(id: string) { const context = getActionContext(); const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_RollApiToken >('/typedrequest', 'rollApiToken'); return request.fire({ identity: context.identity!, id, }); } export const revokeApiTokenAction = routeManagementStatePart.createAction( async (statePartArg, tokenId, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_RevokeApiToken >('/typedrequest', 'revokeApiToken'); await request.fire({ identity: context.identity!, id: tokenId, }); return await actionContext!.dispatch(fetchApiTokensAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to revoke token', }; } } ); export const toggleApiTokenAction = routeManagementStatePart.createAction<{ id: string; enabled: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ToggleApiToken >('/typedrequest', 'toggleApiToken'); await request.fire({ identity: context.identity!, id: dataArg.id, enabled: dataArg.enabled, }); return await actionContext!.dispatch(fetchApiTokensAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to toggle token', }; } });