import * as plugins from './plugins.js'; export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig'; export interface ICloudlyApiClientTypedSocketOptions { autoReconnect?: boolean; maxRetries?: number; initialBackoffMs?: number; maxBackoffMs?: number; abortSignal?: AbortSignal; restoreConnection?: plugins.typedsocket.ITypedSocketClientOptions['restoreConnection']; } export interface ICloudlyApiClientOptions { registerAs: TClientType; cloudlyUrl?: string; typedSocketClientOptions?: ICloudlyApiClientTypedSocketOptions; } import { Image } from './classes.image.js'; import { Service } from './classes.service.js'; import { Cluster } from './classes.cluster.js'; import { SecretClient } from './classes.secret.js'; import { ExternalRegistry } from './classes.externalregistry.js'; import { Platform } from './classes.platform.js'; import { Backup } from './classes.backup.js'; import { PrivateNetworkClient } from './classes.privatenetwork.js'; import { CloudlyProtocolIncompatibleError, cloudlyClientProtocolOffer, requireCompatibleProtocol, rethrowProtocolRefusal, } from './protocol.js'; export class CloudlyApiClient { private cloudlyUrl: string; private registerAs: string; private typedSocketClientOptions?: ICloudlyApiClientTypedSocketOptions; private startPromise?: Promise; private stopPromise?: Promise; private socketAbortController?: AbortController; private authenticationAbortController = new AbortController(); private authenticationQueue: Promise = Promise.resolve(); private lifecycleGeneration = 0; private sessionCredential?: Readonly; public typedrouter = new plugins.typedrequest.TypedRouter(); public typedsocketClient?: plugins.typedsocket.TypedSocket; /** * The refusal that ended this client's socket lifecycle with no caller to reject. * * A refusal met while a reconnect restores the session reaches no caller — the transport * publishes it only as the cause of its own denied restoration — so it is kept here until the * next `start()` opens a new socket lifetime. */ public lastProtocolRefusal?: CloudlyProtocolIncompatibleError; public secrets: SecretClient; public readonly privateNetworks: PrivateNetworkClient; // Subjects public configUpdateSubject = new plugins.smartrx.rxjs.Subject< plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request'] >(); constructor(optionsArg: ICloudlyApiClientOptions) { this.registerAs = optionsArg.registerAs; const environmentCloudlyUrl = typeof process === 'undefined' ? undefined : process.env.CLOUDLY_URL; this.cloudlyUrl = optionsArg.cloudlyUrl || environmentCloudlyUrl || 'https://cloudly.layer.io:443'; this.typedSocketClientOptions = { ...optionsArg.typedSocketClientOptions }; this.secrets = new SecretClient( () => this.createIdentityCredential(this.identity), ( methodArg: T['method'], requestArg: T['request'], ) => this.fireRequest(methodArg, requestArg), ); this.privateNetworks = new PrivateNetworkClient( () => this.createIdentityCredential(this.identity), ( methodArg: T['method'], requestArg: T['request'], ) => this.fireRequest(methodArg, requestArg), ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => { this.configUpdateSubject.next(dataArg); return {}; }) ); } public requireTypedSocket(): plugins.typedsocket.TypedSocket { if (!this.typedsocketClient) throw new Error('Start the Cloudly client before using its socket.'); return this.typedsocketClient; } public requireIdentity(): plugins.servezoneInterfaces.data.IIdentity { if (!this.identity) throw new Error('Authenticate the Cloudly client before using its identity.'); return this.identity; } // Helper: resolve HTTP typedrequest endpoint private get httpEndpoint() { const base = (this.cloudlyUrl || '').replace(/\/$/, ''); return `${base}/typedrequest`; } // Helper: choose transport (WS if available, else HTTP) private createWsRequest( operation: T['method'], ) { return this.typedsocketClient?.createTypedRequest(operation); } private createHttpRequest( operation: T['method'], ) { return new plugins.typedrequest.TypedRequest(this.httpEndpoint, operation); } protected async fireRequest( methodArg: T['method'], requestArg: T['request'], ): Promise { const wsRequest = this.createWsRequest(methodArg); if (wsRequest) return await wsRequest.fire(requestArg, { maxRetries: 0 }); return await this.createHttpRequest(methodArg).fire(requestArg, { maxRetries: 0 }); } protected createIdentityCredential( identityArg: plugins.servezoneInterfaces.data.IIdentityCredential | undefined, ): plugins.servezoneInterfaces.data.IIdentityCredential { if (!identityArg?.jwt) { throw new Error('identity JWT is required. Either provide an identity or login first.'); } return { jwt: identityArg.jwt }; } public async start(optionsArg: ICloudlyApiClientTypedSocketOptions = {}): Promise { if (this.stopPromise) await this.stopPromise; if (this.startPromise) return this.startPromise; if (this.typedsocketClient) return; // A new socket lifetime supersedes the refusal that ended the previous one. this.lastProtocolRefusal = undefined; const typedSocketClientOptions = { ...this.typedSocketClientOptions, ...optionsArg, }; const generation = this.lifecycleGeneration; const abortController = new AbortController(); this.socketAbortController = abortController; const externalSignal = typedSocketClientOptions.abortSignal; const abort = () => abortController.abort(); externalSignal?.addEventListener('abort', abort, { once: true }); if (externalSignal?.aborted) abort(); const callerRestore = typedSocketClientOptions.restoreConnection; const startPromise = (async () => { try { const socket = await plugins.typedsocket.TypedSocket.createClient( this.typedrouter, this.cloudlyUrl, { ...typedSocketClientOptions, abortSignal: abortController.signal, // A refused offer leaves this restoration denied — TypedSocket does not reconnect // from that — and ends this client's own socket lifecycle with it. restoreConnection: async (contextArg) => { await this.serializeAuthentication(async () => { this.requireCurrentLifecycle(generation); contextArg.abortSignal.throwIfAborted(); if (this.sessionCredential) { await this.registerSession(contextArg.createTypedRequest, this.sessionCredential) .catch((errorArg) => this.endSocketLifecycleOnProtocolRefusal(errorArg, generation)); } contextArg.abortSignal.throwIfAborted(); this.requireCurrentLifecycle(generation); }); await callerRestore?.(contextArg); contextArg.abortSignal.throwIfAborted(); this.requireCurrentLifecycle(generation); }, }, ); if (abortController.signal.aborted || generation !== this.lifecycleGeneration) { await socket.stop(); throw new Error('Cloudly client startup was interrupted.'); } this.typedsocketClient = socket; } catch (errorArg) { externalSignal?.removeEventListener('abort', abort); throw errorArg; } })(); this.startPromise = startPromise; try { await startPromise; } finally { if (this.startPromise === startPromise) this.startPromise = undefined; // The caller signal remains attached for the active socket lifetime. if (!this.typedsocketClient) externalSignal?.removeEventListener('abort', abort); else this.socketAbortCleanup = () => externalSignal?.removeEventListener('abort', abort); } } private socketAbortCleanup?: () => void; public async stop(): Promise { this.lifecycleGeneration++; this.authenticationAbortController.abort(); this.authenticationAbortController = new AbortController(); if (this.stopPromise) return this.stopPromise; this.socketAbortController?.abort(); const startup = this.startPromise; const socket = this.typedsocketClient; this.typedsocketClient = undefined; const stopPromise = (async () => { await socket?.stop(); if (startup) await startup.catch(() => {}); this.socketAbortCleanup?.(); this.socketAbortCleanup = undefined; this.socketAbortController = undefined; })(); this.stopPromise = stopPromise; try { await stopPromise; } finally { if (this.stopPromise === stopPromise) this.stopPromise = undefined; } } private serializeAuthentication(actionArg: () => Promise): Promise { const result = this.authenticationQueue.then(actionArg); this.authenticationQueue = result.then(() => {}, () => {}); return result; } private requireCurrentLifecycle(generationArg: number): void { if (generationArg !== this.lifecycleGeneration) { throw new Error('Cloudly client authentication was interrupted.'); } } /** * End the socket lifecycle a restoration was refused in, then rethrow; other failures pass on. * * The transport ends its own client on a denied restoration and carries the refusal only as * that denial's cause; the session it carried is this class's. So this client releases the * socket it would otherwise hold dead — `start()` is a working recovery again and the refusal * stays readable here — and keeps the session credential, which that next start re-offers once * an operator upgraded a side. */ private endSocketLifecycleOnProtocolRefusal(errorArg: unknown, generationArg: number): never { if (errorArg instanceof CloudlyProtocolIncompatibleError && generationArg === this.lifecycleGeneration) { this.lastProtocolRefusal = errorArg; this.typedsocketClient = undefined; this.socketAbortController?.abort(); this.socketAbortController = undefined; this.socketAbortCleanup?.(); this.socketAbortCleanup = undefined; } throw errorArg; } /** The exact body a session registration sends, validated the way the wire validates it. */ private snapshotSessionRegistration( credentialArg: plugins.servezoneInterfaces.data.IIdentityCredential, ): plugins.servezoneInterfaces.data.IRegisterCloudlyClientSessionRequest { return plugins.servezoneInterfaces.data.snapshotRegisterCloudlyClientSessionRequest({ identity: this.createIdentityCredential(credentialArg), protocol: cloudlyClientProtocolOffer, }); } private async registerSession( createRequestArg: plugins.typedsocket.TTypedSocketRestoreRequestFactory, credentialArg: plugins.servezoneInterfaces.data.IIdentityCredential, ): Promise { const requestData = this.snapshotSessionRegistration(credentialArg); const request = createRequestArg( 'registerCloudlyClientSession', ); request.skipHooks = true; const response = await request .fire(requestData, { maxRetries: 0 }) .catch(rethrowProtocolRefusal); const registration = plugins.servezoneInterfaces.data .snapshotRegisterCloudlyClientSessionResponse(response); requireCompatibleProtocol(registration.protocol); } private async acceptIdentity( identityArg: plugins.servezoneInterfaces.data.IIdentity, optionsArg: { tagConnection: boolean; statefullIdentity: boolean }, generationArg: number, ): Promise { const credential = this.createIdentityCredential(identityArg); this.requireCurrentLifecycle(generationArg); if (optionsArg.tagConnection) { // Validate and snapshot before either registration or retention for a future socket. const session = this.snapshotSessionRegistration(credential).identity; if (this.typedsocketClient) { const socket = this.typedsocketClient; try { await this.registerSession((methodArg) => socket.createTypedRequest(methodArg), session); this.requireCurrentLifecycle(generationArg); } catch (errorArg) { // A lost or invalid ACK cannot leave an ambiguously authenticated socket usable. if (generationArg === this.lifecycleGeneration && this.typedsocketClient === socket) { await this.stop(); } // A refused offer is named to the caller: no credential of theirs was rejected. if (errorArg instanceof CloudlyProtocolIncompatibleError) throw errorArg; throw new Error('Cloudly socket authentication failed.', { cause: errorArg }); } } this.sessionCredential = Object.freeze(session); } if (optionsArg.statefullIdentity) this.identity = identityArg; } public identity?: plugins.servezoneInterfaces.data.IIdentity; /** Adopt an identity from an existing login or OIDC exchange. */ public async setIdentity(identityArg: plugins.servezoneInterfaces.data.IIdentity): Promise { const generation = this.lifecycleGeneration; const identity = { ...identityArg }; // Validate before waiting for startup or an earlier authentication operation. this.snapshotSessionRegistration(identity); if (this.startPromise) await this.startPromise; await this.serializeAuthentication(async () => { await this.acceptIdentity(identity, { tagConnection: true, statefullIdentity: true }, generation); }); } /** Forget authentication immediately, then close the connection that held it. */ public clearIdentity(): Promise { this.identity = undefined; this.sessionCredential = undefined; return this.stop(); } public async getIdentityByToken( token: string, optionsArg?: { tagConnection?: boolean; statefullIdentity?: boolean; } ): Promise { const options = { tagConnection: optionsArg?.tagConnection ?? false, statefullIdentity: optionsArg?.statefullIdentity ?? true, }; const generation = this.lifecycleGeneration; const abortSignal = this.authenticationAbortController.signal; if (this.startPromise) await this.startPromise; return this.serializeAuthentication(async () => { this.requireCurrentLifecycle(generation); if (!this.typedsocketClient) throw new Error('Start the Cloudly client before token authentication.'); const identityRequest = this.requireTypedSocket().createTypedRequest( 'getIdentityByToken', ); identityRequest.skipHooks = true; const response = await identityRequest.fire({ token }, { maxRetries: 0, abortSignal }); await this.acceptIdentity(response.identity, options, generation); return response.identity; }); } /** * will use statefull identity by default */ public async getClusterConfigFromCloudlyByIdentity( identityArg: plugins.servezoneInterfaces.data.IIdentityCredential = this.requireIdentity() ): Promise { const identity = this.createIdentityCredential(identityArg); const clusterConfigRequest = this.requireTypedSocket().createTypedRequest( 'getClusterConfig' ); const response = await clusterConfigRequest.fire({ identity, }); return response; } /** * gets a certificate for a domain used by a service */ public async getCertificateForDomain(optionsArg: { domainName: string; type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type']; identity?: plugins.servezoneInterfaces.data.IIdentity; }): Promise { optionsArg.identity = optionsArg.identity || this.identity; if (!optionsArg.identity) { throw new Error('identity is required. Either provide one or login first.'); } const typedCertificateRequest = this.requireTypedSocket().createTypedRequest( 'getCertificateForDomain' ); const typedResponse = await typedCertificateRequest.fire({ identity: optionsArg.identity, domainName: optionsArg.domainName, type: optionsArg.type, }); return typedResponse.certificate; } public externalRegistry = { // ExternalRegistry getRegistryById: async (registryNameArg: string) => { return ExternalRegistry.getExternalRegistryById(this, registryNameArg); }, updateRegistry: async (registryId: string, registryData: plugins.servezoneInterfaces.data.IExternalRegistry['data']): Promise<{ resultRegistry: plugins.servezoneInterfaces.data.IExternalRegistry }> => { const op = 'updateExternalRegistry'; const payload = { identity: this.requireIdentity(), registryId, registryData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteRegistry: async (registryId: string): Promise<{ ok: boolean }> => { const op = 'deleteExternalRegistryById'; const payload = { identity: this.requireIdentity(), registryId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getRegistries: async () => { return ExternalRegistry.getExternalRegistries(this); }, createRegistry: async (optionsArg: Parameters[1]) => { return ExternalRegistry.createExternalRegistry(this, optionsArg); }, verifyRegistry: async (registryId: string): Promise<{ success: boolean; message: string; registry?: ExternalRegistry }> => { const op = 'verifyExternalRegistry'; const wsReq = this.createWsRequest(op); const payload = { identity: this.requireIdentity(), registryId } as any; const resp = wsReq ? await wsReq.fire(payload) : await this.createHttpRequest(op).fire(payload); let registryInstance: ExternalRegistry | undefined; if (resp.registry) { registryInstance = new ExternalRegistry(this); Object.assign(registryInstance, resp.registry); } return { success: resp.success, message: resp.message ?? '', registry: registryInstance }; } } // Auth helpers public async loginWithUsernameAndPassword(username: string, password: string): Promise { const generation = this.lifecycleGeneration; const abortSignal = this.authenticationAbortController.signal; if (this.startPromise) await this.startPromise; return this.serializeAuthentication(async () => { this.requireCurrentLifecycle(generation); const request = this.createHttpRequest( 'adminLoginWithUsernameAndPassword', ); request.skipHooks = true; const response = await request.fire({ username, password }, { maxRetries: 0, abortSignal }); await this.acceptIdentity(response.identity, { tagConnection: true, statefullIdentity: true }, generation); return response.identity; }); } public async reconcileBaseServices(): Promise { const op = 'reconcileBaseServices'; const payload: plugins.servezoneInterfaces.requests.admin.IReq_Any_Cloudly_ReconcileBaseServices['request'] = { identity: this.requireIdentity(), }; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); } public image = { // Images getImageById: async (imageIdArg: string) => { return Image.getImageById(this, imageIdArg); }, getImages: async () => { return Image.getImages(this); }, createImage: async (optionsArg: Parameters[1]) => { return Image.createImage(this, optionsArg); }, deleteImage: async (imageId: string): Promise => { const op = 'deleteImage'; const payload = { identity: this.requireIdentity(), imageId } as any; const wsReq = this.createWsRequest(op); if (wsReq) { await wsReq.fire(payload); return; } await this.createHttpRequest(op).fire(payload); } } public services = { // Services getServiceById: async (serviceIdArg: string) => { return Service.getServiceById(this, serviceIdArg); }, getServices: async () => { return Service.getServices(this); }, createService: async (optionsArg: Parameters[1]) => { return Service.createService(this, optionsArg); }, getRegistryTarget: async (serviceId: string, tag = 'latest') => { return Service.getServiceRegistryTarget(this, serviceId, tag); }, updateService: async (serviceId: string, serviceData: plugins.servezoneInterfaces.data.IService['data']): Promise<{ service: plugins.servezoneInterfaces.data.IService }> => { const op = 'updateService'; const payload = { identity: this.requireIdentity(), serviceId, serviceData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteService: async (serviceId: string): Promise => { const op = 'deleteServiceById'; const payload = { identity: this.requireIdentity(), serviceId } as any; const wsReq = this.createWsRequest(op); if (wsReq) { await wsReq.fire(payload); return; } await this.createHttpRequest(op).fire(payload); } } public cluster = { // Clusters getClusterById: async (clusterIdArg: string) => { return Cluster.getClusterById(this, clusterIdArg); }, getClusters: async () => { return Cluster.getClusters(this); }, createCluster: async (optionsArg: Parameters[1]) => { return Cluster.createCluster(this, optionsArg); }, createClusterAdvanced: async (clusterName: string, setupMode?: 'manual' | 'hetzner' | 'aws' | 'digitalocean') => { const op = 'createCluster'; const payload: any = { identity: this.requireIdentity(), clusterName }; if (setupMode) payload.setupMode = setupMode; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); } } public node = { getNodes: async (optionsArg: { clusterId?: string; } = {}): Promise => { const op = 'getNodes'; const payload: plugins.servezoneInterfaces.requests.node.IReq_Any_Cloudly_GetNodes['request'] = { identity: this.requireIdentity(), ...optionsArg }; const wsReq = this.createWsRequest(op); const response = wsReq ? await wsReq.fire(payload) : await this.createHttpRequest(op).fire(payload); return response.nodes; }, getNodeById: async (nodeId: string): Promise => { const op = 'getNodeById'; const payload: plugins.servezoneInterfaces.requests.node.IReq_Any_Cloudly_GetNodeById['request'] = { identity: this.requireIdentity(), nodeId }; const wsReq = this.createWsRequest(op); const response = wsReq ? await wsReq.fire(payload) : await this.createHttpRequest(op).fire(payload); return response.node; }, getNodeDeletionImpact: async (nodeId: string): Promise => { const op = 'getNodeDeletionImpact'; const payload: plugins.servezoneInterfaces.requests.node.IReq_Any_Cloudly_GetNodeDeletionImpact['request'] = { identity: this.requireIdentity(), nodeId }; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteNodeById: async (nodeId: string): Promise => { const op = 'deleteNodeById'; const payload: plugins.servezoneInterfaces.requests.node.IReq_Any_Cloudly_DeleteNodeById['request'] = { identity: this.requireIdentity(), nodeId }; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createNodeJumpCommand: async (optionsArg: { clusterId: string; role?: plugins.servezoneInterfaces.data.IClusterNode['data']['role']; nodeType?: plugins.servezoneInterfaces.data.IClusterNode['data']['nodeType']; ttlMs?: number; }): Promise => { const op = 'createNodeJumpCommand'; const payload = { identity: this.requireIdentity(), ...optionsArg } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, } public platform = { getPlatformDesiredState: async () => { return Platform.getPlatformDesiredState(this); }, getPlatformCapabilities: async () => { return Platform.getPlatformCapabilities(this); }, getPlatformProviderConfigs: async ( capability?: plugins.servezoneInterfaces.platform.TPlatformCapability, ) => { return Platform.getPlatformProviderConfigs(this, capability); }, upsertPlatformProviderConfig: async ( providerConfig: plugins.servezoneInterfaces.platform.IPlatformProviderConfig, ) => { return Platform.upsertPlatformProviderConfig(this, providerConfig); }, deletePlatformProviderConfigById: async (providerConfigId: string) => { return Platform.deletePlatformProviderConfigById(this, providerConfigId); }, getPlatformBindings: async ( optionsArg: { serviceId?: string; capability?: plugins.servezoneInterfaces.platform.TPlatformCapability; } = {}, ) => { return Platform.getPlatformBindings(this, optionsArg); }, upsertPlatformBinding: async ( binding: plugins.servezoneInterfaces.platform.IPlatformBinding, ) => { return Platform.upsertPlatformBinding(this, binding); }, updatePlatformBindingStatus: async ( optionsArg: Omit< plugins.servezoneInterfaces.requests.platform.IReq_Any_Cloudly_UpdatePlatformBindingStatus['request'], 'identity' >, ) => { return Platform.updatePlatformBindingStatus(this, optionsArg); }, deletePlatformBindingById: async (bindingId: string) => { return Platform.deletePlatformBindingById(this, bindingId); }, } public backup = { createServiceBackup: async (optionsArg: Parameters[1]) => { return Backup.createServiceBackup(this, optionsArg); }, getServiceBackups: async (optionsArg: Parameters[1] = {}) => { return Backup.getServiceBackups(this, optionsArg); }, getBackupById: async (backupIdArg: string) => { return Backup.getBackupById(this, backupIdArg); }, restoreServiceBackup: async (optionsArg: Parameters[1]) => { return Backup.restoreServiceBackup(this, optionsArg); }, createIsolatedRestore: async ( optionsArg: Parameters[1], ) => { return Backup.createIsolatedRestore(this, optionsArg); }, getIsolatedRestores: async ( optionsArg: Parameters[1] = {}, ) => { return Backup.getIsolatedRestores(this, optionsArg); }, getIsolatedRestoreById: async (restoreIdArg: string) => { return Backup.getIsolatedRestoreById(this, restoreIdArg); }, cleanupIsolatedRestore: async ( optionsArg: Parameters[1], ) => { return Backup.cleanupIsolatedRestore(this, optionsArg); }, } // Settings API public settings = { getSettings: async (): Promise<{ settings: plugins.servezoneInterfaces.data.ICloudlySettings }> => { const op = 'getSettings'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity() }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity() }); }, updateSettings: async (updates: Partial): Promise<{ success: boolean; message: string; }> => { const op = 'updateSettings'; const wsReq = this.createWsRequest(op); const payload = { identity: this.requireIdentity(), updates } as any; if (wsReq) { return wsReq.fire(payload); } const httpReq = this.createHttpRequest(op); return httpReq.fire(payload); }, testProviderConnection: async (provider: string): Promise<{ connectionValid: boolean; message: string; }> => { const op = 'testProviderConnection'; const wsReq = this.createWsRequest(op); const payload = { identity: this.requireIdentity(), provider: provider as any } as any; if (wsReq) { return wsReq.fire(payload); } const httpReq = this.createHttpRequest(op); return httpReq.fire(payload); } } // Task API public tasks = { getTasks: async (): Promise<{ tasks: Array<{ name: string; description: string; category: 'maintenance' | 'deployment' | 'backup' | 'monitoring' | 'cleanup' | 'system' | 'security'; schedule?: string; lastRun?: number; enabled: boolean; }> }> => { const op = 'getTasks'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity() }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity() }); }, getTaskExecutions: async (filter?: any): Promise<{ executions: plugins.servezoneInterfaces.data.ITaskExecution[]; }> => { const op = 'getTaskExecutions'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity(), filter }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity(), filter }); }, getTaskExecutionById: async (executionId: string): Promise<{ execution: plugins.servezoneInterfaces.data.ITaskExecution }> => { const op = 'getTaskExecutionById'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity(), executionId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity(), executionId }); }, triggerTask: async (taskName: string, userId?: string): Promise<{ execution: plugins.servezoneInterfaces.data.ITaskExecution }> => { const op = 'triggerTask'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity(), taskName, userId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity(), taskName, userId }); }, cancelTask: async (executionId: string): Promise<{ success: boolean }> => { const op = 'cancelTask'; const wsReq = this.createWsRequest(op); if (wsReq) { return wsReq.fire({ identity: this.requireIdentity(), executionId }); } const httpReq = this.createHttpRequest(op); return httpReq.fire({ identity: this.requireIdentity(), executionId }); } } // Domain API public domains = { getDomains: async (): Promise<{ domains: plugins.servezoneInterfaces.data.IDomain[] }> => { const op = 'getDomains'; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire({ identity: this.requireIdentity() }); return this.createHttpRequest(op).fire({ identity: this.requireIdentity() }); }, getDomainById: async (domainId: string): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'getDomainById'; const payload = { identity: this.requireIdentity(), domainId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDomain: async (domainData: plugins.servezoneInterfaces.data.IDomain['data']): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'createDomain'; const payload = { identity: this.requireIdentity(), domainData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDomain: async (domainId: string, domainData: Partial): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain }> => { const op = 'updateDomain'; const payload = { identity: this.requireIdentity(), domainId, domainData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDomain: async (domainId: string): Promise<{ success: boolean }> => { const op = 'deleteDomain'; const payload = { identity: this.requireIdentity(), domainId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, verifyDomain: async (domainId: string, verificationMethod?: 'dns' | 'http' | 'email' | 'manual'): Promise<{ domain: plugins.servezoneInterfaces.data.IDomain; verificationResult: any }> => { const op = 'verifyDomain'; const payload = { identity: this.requireIdentity(), domainId, verificationMethod } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; // DNS API public dns = { getDnsEntries: async (zone?: string): Promise<{ dnsEntries: plugins.servezoneInterfaces.data.IDnsEntry[] }> => { const op = 'getDnsEntries'; const payload = { identity: this.requireIdentity(), zone } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDnsEntryById: async (dnsEntryId: string): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'getDnsEntryById'; const payload = { identity: this.requireIdentity(), dnsEntryId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDnsEntry: async (dnsEntryData: plugins.servezoneInterfaces.data.IDnsEntry['data']): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'createDnsEntry'; const payload = { identity: this.requireIdentity(), dnsEntryData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDnsEntry: async (dnsEntryId: string, dnsEntryData: plugins.servezoneInterfaces.data.IDnsEntry['data']): Promise<{ dnsEntry: plugins.servezoneInterfaces.data.IDnsEntry }> => { const op = 'updateDnsEntry'; const payload = { identity: this.requireIdentity(), dnsEntryId, dnsEntryData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDnsEntry: async (dnsEntryId: string): Promise<{ success: boolean }> => { const op = 'deleteDnsEntry'; const payload = { identity: this.requireIdentity(), dnsEntryId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDnsZones: async (): Promise<{ zones: string[] }> => { const op = 'getDnsZones'; const payload = { identity: this.requireIdentity() } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; // Deployment API public deployments = { getDeployments: async (optionsArg?: { includeArchived?: boolean }): Promise<{ deployments: plugins.servezoneInterfaces.data.IDeployment[] }> => { const op = 'getDeployments'; const payload = { identity: this.requireIdentity(), includeArchived: optionsArg?.includeArchived } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, getDeploymentById: async (deploymentId: string): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'getDeploymentById'; const payload = { identity: this.requireIdentity(), deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, createDeployment: async (deploymentData: Partial): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'createDeployment'; const payload = { identity: this.requireIdentity(), deploymentData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, updateDeployment: async (deploymentId: string, deploymentData: Partial): Promise<{ deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'updateDeployment'; const payload = { identity: this.requireIdentity(), deploymentId, deploymentData } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, deleteDeployment: async (deploymentId: string): Promise<{ success: boolean }> => { const op = 'deleteDeploymentById'; const payload = { identity: this.requireIdentity(), deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, restartDeployment: async (deploymentId: string): Promise<{ success: boolean; deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'restartDeployment'; const payload = { identity: this.requireIdentity(), deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, killDeployment: async (deploymentId: string): Promise<{ success: boolean; deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'killDeployment'; const payload = { identity: this.requireIdentity(), deploymentId } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, scaleDeployment: async (deploymentId: string, replicas: number): Promise<{ success: boolean; deployment: plugins.servezoneInterfaces.data.IDeployment }> => { const op = 'scaleDeployment'; const payload = { identity: this.requireIdentity(), deploymentId, replicas } as any; const wsReq = this.createWsRequest(op); if (wsReq) return wsReq.fire(payload); return this.createHttpRequest(op).fire(payload); }, }; }