import * as plugins from '../plugins.js'; import * as interfaces from '../../ts_interfaces/index.js'; import { appState } from './shared.js'; import { getActionContext } from './login.js'; // ============================================================================ // VPN State // ============================================================================ export interface IVpnState { clients: interfaces.data.IVpnClient[]; connectedClients: interfaces.data.IVpnConnectedClient[]; status: interfaces.data.IVpnServerStatus | null; isLoading: boolean; error: string | null; lastUpdated: number; /** WireGuard config shown after create/rotate (only shown once) */ newClientConfig: string | null; } export const vpnStatePart = await appState.getStatePart( 'vpn', { clients: [], connectedClients: [], status: null, isLoading: false, error: null, lastUpdated: 0, newClientConfig: null, }, 'soft' ); // ============================================================================ // VPN Actions // ============================================================================ export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; try { const clientsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetVpnClients >('/typedrequest', 'getVpnClients'); const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetVpnStatus >('/typedrequest', 'getVpnStatus'); const connectedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetVpnConnectedClients >('/typedrequest', 'getVpnConnectedClients'); const [clientsResponse, statusResponse, connectedResponse] = await Promise.all([ clientsRequest.fire({ identity: context.identity }), statusRequest.fire({ identity: context.identity }), connectedRequest.fire({ identity: context.identity }), ]); return { ...currentState, clients: clientsResponse.clients, connectedClients: connectedResponse.connectedClients, status: statusResponse.status, isLoading: false, error: null, lastUpdated: Date.now(), }; } catch (error) { return { ...currentState, isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch VPN data', }; } }); export const createVpnClientAction = vpnStatePart.createAction<{ clientId: string; targetProfileIds?: string[]; description?: string; destinationAllowList?: string[]; destinationBlockList?: string[]; useHostIp?: boolean; useDhcp?: boolean; staticIp?: string; forceVlan?: boolean; vlanId?: number; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateVpnClient >('/typedrequest', 'createVpnClient'); const response = await request.fire({ identity: context.identity!, clientId: dataArg.clientId, targetProfileIds: dataArg.targetProfileIds, description: dataArg.description, destinationAllowList: dataArg.destinationAllowList, destinationBlockList: dataArg.destinationBlockList, useHostIp: dataArg.useHostIp, useDhcp: dataArg.useDhcp, staticIp: dataArg.staticIp, forceVlan: dataArg.forceVlan, vlanId: dataArg.vlanId, }); if (!response.success) { return { ...currentState, error: response.message || 'Failed to create client' }; } const refreshed = await actionContext!.dispatch(fetchVpnAction, null); return { ...refreshed, newClientConfig: response.wireguardConfig || null, }; } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to create VPN client', }; } }); export const deleteVpnClientAction = vpnStatePart.createAction( async (statePartArg, clientId, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteVpnClient >('/typedrequest', 'deleteVpnClient'); await request.fire({ identity: context.identity!, clientId }); return await actionContext!.dispatch(fetchVpnAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to delete VPN client', }; } }, ); export const toggleVpnClientAction = vpnStatePart.createAction<{ clientId: string; enabled: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const method = dataArg.enabled ? 'enableVpnClient' : 'disableVpnClient'; type TReq = interfaces.requests.IReq_EnableVpnClient | interfaces.requests.IReq_DisableVpnClient; const request = new plugins.domtools.plugins.typedrequest.TypedRequest( '/typedrequest', method, ); await request.fire({ identity: context.identity!, clientId: dataArg.clientId }); return await actionContext!.dispatch(fetchVpnAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to toggle VPN client', }; } }); export const updateVpnClientAction = vpnStatePart.createAction<{ clientId: string; description?: string; targetProfileIds?: string[]; destinationAllowList?: string[]; destinationBlockList?: string[]; useHostIp?: boolean; useDhcp?: boolean; staticIp?: string; forceVlan?: boolean; vlanId?: number; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateVpnClient >('/typedrequest', 'updateVpnClient'); const response = await request.fire({ identity: context.identity!, clientId: dataArg.clientId, description: dataArg.description, targetProfileIds: dataArg.targetProfileIds, destinationAllowList: dataArg.destinationAllowList, destinationBlockList: dataArg.destinationBlockList, useHostIp: dataArg.useHostIp, useDhcp: dataArg.useDhcp, staticIp: dataArg.staticIp, forceVlan: dataArg.forceVlan, vlanId: dataArg.vlanId, }); if (!response.success) { return { ...currentState, error: response.message || 'Failed to update client' }; } return await actionContext!.dispatch(fetchVpnAction, null); } catch (error: unknown) { return { ...currentState, error: error instanceof Error ? error.message : 'Failed to update VPN client', }; } }); export const clearNewClientConfigAction = vpnStatePart.createAction( async (statePartArg): Promise => { return { ...statePartArg.getState()!, newClientConfig: null }; }, ); // ============================================================================