import * as plugins from './cloudflare.plugins.js'; import * as interfaces from './interfaces/index.js'; import { WorkerManager } from './cloudflare.classes.workermanager.js'; import { logger } from './cloudflare.logger.js'; export interface IWorkerRoute extends interfaces.ICflareWorkerRoute { zoneName: string; } export interface IWorkerRouteDefinition { zoneName: string; pattern: string; } export class CloudflareWorker { // STATIC public static async fromApiObject( workerManager: WorkerManager, apiObject ): Promise { const newWorker = new CloudflareWorker(workerManager); Object.assign(newWorker, apiObject); await newWorker.getRoutes(); return newWorker; } // INSTANCE private workerManager: WorkerManager; public script: string; public id: string; public etag: string; // tslint:disable-next-line: variable-name public created_on: string; // tslint:disable-next-line: variable-name public modified_on: string; public routes: IWorkerRoute[] = []; constructor(workerManagerArg: WorkerManager) { this.workerManager = workerManagerArg; } /** * gets all routes for a worker */ public async getRoutes() { try { this.routes = []; // Reset routes before fetching // Get all zones using the async iterator const zones: plugins.ICloudflareTypes['Zone'][] = []; for await (const zone of this.workerManager.cfAccount.apiAccount.zones.list()) { zones.push(zone); } if (zones.length === 0) { logger.log('warn', 'No zones found for the account'); return; } for (const zone of zones) { try { if (!zone || !zone.id) { logger.log('warn', 'Zone is missing ID property'); continue; } // Get worker routes for this zone const apiRoutes = []; for await (const route of this.workerManager.cfAccount.apiAccount.workers.routes.list({ zone_id: zone.id })) { apiRoutes.push(route); } // Filter for routes that match this worker's ID for (const route of apiRoutes) { if (route.script === this.id) { logger.log('debug', `Found route for worker ${this.id}: ${route.pattern}`); this.routes.push({ ...route, zoneName: zone.name }); } } } catch (error) { logger.log('error', `Failed to get worker routes for zone ${zone.name || zone.id}: ${error.message}`); } } logger.log('info', `Found ${this.routes.length} routes for worker ${this.id}`); } catch (error) { logger.log('error', `Failed to get routes for worker ${this.id}: ${error.message}`); // Initialize routes as empty array in case of error this.routes = []; } } /** * Sets routes for this worker * @param routeArray Array of route definitions */ public async setRoutes(routeArray: IWorkerRouteDefinition[]) { // First get all existing routes to determine what we need to create/update await this.getRoutes(); for (const newRoute of routeArray) { // Determine whether a route is new, needs an update, or is already up to date let routeStatus: 'new' | 'needsUpdate' | 'alreadyUpToDate' = 'new'; let existingRouteId: string; for (const existingRoute of this.routes) { if (existingRoute.pattern === newRoute.pattern) { routeStatus = 'needsUpdate'; existingRouteId = existingRoute.id; if (existingRoute.script === this.id) { routeStatus = 'alreadyUpToDate'; logger.log('info', `Route ${newRoute.pattern} already exists, no update needed`); } } } try { // Get the zone ID const zone = await this.workerManager.cfAccount.zoneManager.getZoneByName(newRoute.zoneName); if (!zone) { logger.log('error', `Zone ${newRoute.zoneName} not found`); continue; } // Handle route creation, update, or skip if already up to date if (routeStatus === 'new') { await this.workerManager.cfAccount.apiAccount.workers.routes.create({ zone_id: zone.id, pattern: newRoute.pattern, script: this.id }); logger.log('info', `Created new route ${newRoute.pattern} for worker ${this.id}`); } else if (routeStatus === 'needsUpdate') { await this.workerManager.cfAccount.apiAccount.workers.routes.update(existingRouteId, { zone_id: zone.id, pattern: newRoute.pattern, script: this.id }); logger.log('info', `Updated route ${newRoute.pattern} for worker ${this.id}`); } } catch (error) { logger.log('error', `Failed to set route ${newRoute.pattern}: ${error.message}`); } } // Refresh routes after all changes await this.getRoutes(); } /** * Upload or update worker script content * @param scriptContent The worker script content * @returns Updated worker object */ public async updateScript(scriptContent: string): Promise { if (!this.workerManager.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } try { logger.log('info', `Updating script for worker ${this.id}`); // Use the official client to update the script (upload new content) // Build params as any to include the script form part without TS errors const updateParams: any = { account_id: this.workerManager.cfAccount.preselectedAccountId, metadata: { body_part: 'script' }, }; updateParams['CF-WORKER-BODY-PART'] = 'script'; updateParams['script'] = scriptContent; const updatedWorker = await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(this.id, updateParams); // Update this instance with new data if (updatedWorker && typeof updatedWorker === 'object') { Object.assign(this, updatedWorker); } // Always ensure the script property is updated this.script = scriptContent; return this; } catch (error) { logger.log('error', `Failed to update worker script: ${error.message}`); throw error; } } /** * Delete this worker script * @returns True if deletion was successful */ public async delete(): Promise { if (!this.workerManager.cfAccount.preselectedAccountId) { throw new Error('No account selected. Please select it first on the account.'); } try { logger.log('info', `Deleting worker ${this.id}`); // Use the official client to delete the worker await this.workerManager.cfAccount.apiAccount.workers.scripts.delete(this.id, { account_id: this.workerManager.cfAccount.preselectedAccountId }); return true; } catch (error) { logger.log('error', `Failed to delete worker: ${error.message}`); return false; } } }