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'; // ============================================================================ // Domains State (DNS providers + domains + records) // ============================================================================ export interface IDomainsState { providers: interfaces.data.IDnsProviderPublic[]; domains: interfaces.data.IDomain[]; records: interfaces.data.IDnsRecord[]; /** id of the currently-selected domain in the DNS records subview. */ selectedDomainId: string | null; isLoading: boolean; error: string | null; lastUpdated: number; } export const domainsStatePart = await appState.getStatePart( 'domains', { providers: [], domains: [], records: [], selectedDomainId: null, isLoading: false, error: null, lastUpdated: 0, }, 'soft', ); export const fetchDomainsAndProvidersAction = domainsStatePart.createAction( async (statePartArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const ticket = captureRealtimeTicket('dns'); try { const providersRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetDnsProviders >('/typedrequest', 'getDnsProviders'); const domainsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetDomains >('/typedrequest', 'getDomains'); const [providersResponse, domainsResponse] = await Promise.all([ providersRequest.fire({ identity: context.identity }), domainsRequest.fire({ identity: context.identity }), ]); if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, providers: providersResponse.providers, domains: domainsResponse.domains, isLoading: false, error: null, lastUpdated: Date.now(), }; } catch (error: unknown) { if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, isLoading: false, error: error instanceof Error ? error.message : 'Failed to fetch domains/providers', }; } }, ); export const fetchDnsRecordsForDomainAction = domainsStatePart.createAction<{ domainId: string }>( async (statePartArg, dataArg): Promise => { const context = getActionContext(); const currentState = statePartArg.getState()!; if (!context.identity) return currentState; const ticket = captureRealtimeTicket('dns'); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_GetDnsRecords >('/typedrequest', 'getDnsRecords'); const response = await request.fire({ identity: context.identity, domainId: dataArg.domainId, }); if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, records: response.records, selectedDomainId: dataArg.domainId, error: null, }; } catch (error: unknown) { if (!isRealtimeTicketCurrent(ticket)) return statePartArg.getState()!; return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to fetch DNS records', }; } }, ); export const createDnsProviderAction = domainsStatePart.createAction<{ name: string; type: interfaces.data.TDnsProviderType; credentials: interfaces.data.TDnsProviderCredentials; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateDnsProvider >('/typedrequest', 'createDnsProvider'); const response = await request.fire({ identity: context.identity!, name: dataArg.name, type: dataArg.type, credentials: dataArg.credentials, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to create provider', }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to create provider', }; } }); export const updateDnsProviderAction = domainsStatePart.createAction<{ id: string; name?: string; credentials?: interfaces.data.TDnsProviderCredentials; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateDnsProvider >('/typedrequest', 'updateDnsProvider'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, name: dataArg.name, credentials: dataArg.credentials, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to update provider', }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to update provider', }; } }); export const deleteDnsProviderAction = domainsStatePart.createAction<{ id: string; force?: boolean }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteDnsProvider >('/typedrequest', 'deleteDnsProvider'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, force: dataArg.force, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to delete provider', }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to delete provider', }; } }, ); export const testDnsProviderAction = domainsStatePart.createAction<{ id: string }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_TestDnsProvider >('/typedrequest', 'testDnsProvider'); await request.fire({ identity: context.identity!, id: dataArg.id }); return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to test provider', }; } }, ); /** One-shot fetch for the import-domain modal. Does NOT modify state. */ export async function fetchProviderDomains( providerId: string, ): Promise<{ success: boolean; domains?: interfaces.data.IProviderDomainListing[]; message?: string }> { const context = getActionContext(); if (!context.identity) return { success: false, message: 'Not authenticated' }; const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ListProviderDomains >('/typedrequest', 'listProviderDomains'); return await request.fire({ identity: context.identity, providerId }); } export const createDcrouterDomainAction = domainsStatePart.createAction<{ name: string; description?: string; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateDomain >('/typedrequest', 'createDomain'); const response = await request.fire({ identity: context.identity!, name: dataArg.name, description: dataArg.description, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to create domain' }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to create domain', }; } }); export const importDomainsFromProviderAction = domainsStatePart.createAction<{ providerId: string; domainNames: string[]; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_ImportDomain >('/typedrequest', 'importDomain'); const response = await request.fire({ identity: context.identity!, providerId: dataArg.providerId, domainNames: dataArg.domainNames, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to import domains' }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to import domains', }; } }); export const deleteDomainAction = domainsStatePart.createAction<{ id: string }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteDomain >('/typedrequest', 'deleteDomain'); const response = await request.fire({ identity: context.identity!, id: dataArg.id }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to delete domain' }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to delete domain', }; } }, ); export const syncDomainAction = domainsStatePart.createAction<{ id: string }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_SyncDomain >('/typedrequest', 'syncDomain'); const response = await request.fire({ identity: context.identity!, id: dataArg.id }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to sync domain' }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to sync domain', }; } }, ); export const migrateDomainAction = domainsStatePart.createAction<{ id: string; targetSource: interfaces.data.TDomainSource; targetProviderId?: string; deleteExistingProviderRecords?: boolean; }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_MigrateDomain >('/typedrequest', 'migrateDomain'); const response = await request.fire({ identity: context.identity!, ...dataArg }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Migration failed' }; } return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Migration failed', }; } }, ); export const createDnsRecordAction = domainsStatePart.createAction<{ domainId: string; name: string; type: interfaces.data.TDnsRecordType; value: string; ttl?: number; proxied?: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_CreateDnsRecord >('/typedrequest', 'createDnsRecord'); const response = await request.fire({ identity: context.identity!, domainId: dataArg.domainId, name: dataArg.name, type: dataArg.type, value: dataArg.value, ttl: dataArg.ttl, proxied: dataArg.proxied, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to create record' }; } return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId }); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to create record', }; } }); export const updateDnsRecordAction = domainsStatePart.createAction<{ id: string; domainId: string; name?: string; value?: string; ttl?: number; proxied?: boolean; }>(async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_UpdateDnsRecord >('/typedrequest', 'updateDnsRecord'); const response = await request.fire({ identity: context.identity!, id: dataArg.id, name: dataArg.name, value: dataArg.value, ttl: dataArg.ttl, proxied: dataArg.proxied, }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to update record' }; } return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId }); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to update record', }; } }); export const deleteDnsRecordAction = domainsStatePart.createAction<{ id: string; domainId: string }>( async (statePartArg, dataArg, actionContext): Promise => { const context = getActionContext(); try { const request = new plugins.domtools.plugins.typedrequest.TypedRequest< interfaces.requests.IReq_DeleteDnsRecord >('/typedrequest', 'deleteDnsRecord'); const response = await request.fire({ identity: context.identity!, id: dataArg.id }); if (!response.success) { return { ...statePartArg.getState()!, error: response.message || 'Failed to delete record' }; } return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId }); } catch (error: unknown) { return { ...statePartArg.getState()!, error: error instanceof Error ? error.message : 'Failed to delete record', }; } }, ); // ============================================================================