219 lines
7.1 KiB
TypeScript
219 lines
7.1 KiB
TypeScript
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<CloudflareWorker> {
|
|
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<CloudflareWorker> {
|
|
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)
|
|
const updatedWorker = await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(this.id, {
|
|
account_id: this.workerManager.cfAccount.preselectedAccountId,
|
|
// name the multipart part for the updated script code
|
|
metadata: { body_part: 'script' },
|
|
/* header to indicate which part contains the script */
|
|
'CF-WORKER-BODY-PART': 'script',
|
|
// include the new script as a form part named 'script'
|
|
script: scriptContent,
|
|
});
|
|
|
|
// 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<boolean> {
|
|
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;
|
|
}
|
|
}
|
|
} |