204 lines
6.4 KiB
TypeScript
204 lines
6.4 KiB
TypeScript
|
|
import * as interfaces from '../ts_interfaces/index.js';
|
||
|
|
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||
|
|
import type { DcRouterApiClient } from './classes.dcrouterapiclient.js';
|
||
|
|
|
||
|
|
export class Route {
|
||
|
|
private clientRef: DcRouterApiClient;
|
||
|
|
|
||
|
|
// Data from IMergedRoute
|
||
|
|
public routeConfig: IRouteConfig;
|
||
|
|
public source: 'hardcoded' | 'programmatic';
|
||
|
|
public enabled: boolean;
|
||
|
|
public overridden: boolean;
|
||
|
|
public storedRouteId?: string;
|
||
|
|
public createdAt?: number;
|
||
|
|
public updatedAt?: number;
|
||
|
|
|
||
|
|
// Convenience accessors
|
||
|
|
public get name(): string {
|
||
|
|
return this.routeConfig.name || '';
|
||
|
|
}
|
||
|
|
|
||
|
|
constructor(clientRef: DcRouterApiClient, data: interfaces.data.IMergedRoute) {
|
||
|
|
this.clientRef = clientRef;
|
||
|
|
this.routeConfig = data.route;
|
||
|
|
this.source = data.source;
|
||
|
|
this.enabled = data.enabled;
|
||
|
|
this.overridden = data.overridden;
|
||
|
|
this.storedRouteId = data.storedRouteId;
|
||
|
|
this.createdAt = data.createdAt;
|
||
|
|
this.updatedAt = data.updatedAt;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async update(changes: Partial<IRouteConfig>): Promise<void> {
|
||
|
|
if (!this.storedRouteId) {
|
||
|
|
throw new Error('Cannot update a hardcoded route. Use setOverride() instead.');
|
||
|
|
}
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_UpdateRoute>(
|
||
|
|
'updateRoute',
|
||
|
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId, route: changes }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to update route');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async delete(): Promise<void> {
|
||
|
|
if (!this.storedRouteId) {
|
||
|
|
throw new Error('Cannot delete a hardcoded route. Use setOverride() instead.');
|
||
|
|
}
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_DeleteRoute>(
|
||
|
|
'deleteRoute',
|
||
|
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to delete route');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async toggle(enabled: boolean): Promise<void> {
|
||
|
|
if (!this.storedRouteId) {
|
||
|
|
throw new Error('Cannot toggle a hardcoded route. Use setOverride() instead.');
|
||
|
|
}
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_ToggleRoute>(
|
||
|
|
'toggleRoute',
|
||
|
|
this.clientRef.buildRequestPayload({ id: this.storedRouteId, enabled }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to toggle route');
|
||
|
|
}
|
||
|
|
this.enabled = enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async setOverride(enabled: boolean): Promise<void> {
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_SetRouteOverride>(
|
||
|
|
'setRouteOverride',
|
||
|
|
this.clientRef.buildRequestPayload({ routeName: this.name, enabled }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to set route override');
|
||
|
|
}
|
||
|
|
this.overridden = true;
|
||
|
|
this.enabled = enabled;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async removeOverride(): Promise<void> {
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_RemoveRouteOverride>(
|
||
|
|
'removeRouteOverride',
|
||
|
|
this.clientRef.buildRequestPayload({ routeName: this.name }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to remove route override');
|
||
|
|
}
|
||
|
|
this.overridden = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export class RouteBuilder {
|
||
|
|
private clientRef: DcRouterApiClient;
|
||
|
|
private routeConfig: Partial<IRouteConfig> = {};
|
||
|
|
private isEnabled: boolean = true;
|
||
|
|
|
||
|
|
constructor(clientRef: DcRouterApiClient) {
|
||
|
|
this.clientRef = clientRef;
|
||
|
|
}
|
||
|
|
|
||
|
|
public setName(name: string): this {
|
||
|
|
this.routeConfig.name = name;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public setMatch(match: IRouteConfig['match']): this {
|
||
|
|
this.routeConfig.match = match;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public setAction(action: IRouteConfig['action']): this {
|
||
|
|
this.routeConfig.action = action;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public setTls(tls: IRouteConfig['action']['tls']): this {
|
||
|
|
if (!this.routeConfig.action) {
|
||
|
|
this.routeConfig.action = { type: 'forward' } as IRouteConfig['action'];
|
||
|
|
}
|
||
|
|
this.routeConfig.action!.tls = tls;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public setEnabled(enabled: boolean): this {
|
||
|
|
this.isEnabled = enabled;
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async save(): Promise<Route> {
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateRoute>(
|
||
|
|
'createRoute',
|
||
|
|
this.clientRef.buildRequestPayload({
|
||
|
|
route: this.routeConfig as IRouteConfig,
|
||
|
|
enabled: this.isEnabled,
|
||
|
|
}) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to create route');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Return a Route instance by re-fetching the list
|
||
|
|
// The created route is programmatic, so we find it by storedRouteId
|
||
|
|
const { routes } = await new RouteManager(this.clientRef).list();
|
||
|
|
const created = routes.find((r) => r.storedRouteId === response.storedRouteId);
|
||
|
|
if (created) {
|
||
|
|
return created;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fallback: construct from known data
|
||
|
|
return new Route(this.clientRef, {
|
||
|
|
route: this.routeConfig as IRouteConfig,
|
||
|
|
source: 'programmatic',
|
||
|
|
enabled: this.isEnabled,
|
||
|
|
overridden: false,
|
||
|
|
storedRouteId: response.storedRouteId,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export class RouteManager {
|
||
|
|
private clientRef: DcRouterApiClient;
|
||
|
|
|
||
|
|
constructor(clientRef: DcRouterApiClient) {
|
||
|
|
this.clientRef = clientRef;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async list(): Promise<{ routes: Route[]; warnings: interfaces.data.IRouteWarning[] }> {
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_GetMergedRoutes>(
|
||
|
|
'getMergedRoutes',
|
||
|
|
this.clientRef.buildRequestPayload() as any,
|
||
|
|
);
|
||
|
|
return {
|
||
|
|
routes: response.routes.map((r) => new Route(this.clientRef, r)),
|
||
|
|
warnings: response.warnings,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
public async create(routeConfig: IRouteConfig, enabled?: boolean): Promise<Route> {
|
||
|
|
const response = await this.clientRef.request<interfaces.requests.IReq_CreateRoute>(
|
||
|
|
'createRoute',
|
||
|
|
this.clientRef.buildRequestPayload({ route: routeConfig, enabled: enabled ?? true }) as any,
|
||
|
|
);
|
||
|
|
if (!response.success) {
|
||
|
|
throw new Error(response.message || 'Failed to create route');
|
||
|
|
}
|
||
|
|
return new Route(this.clientRef, {
|
||
|
|
route: routeConfig,
|
||
|
|
source: 'programmatic',
|
||
|
|
enabled: enabled ?? true,
|
||
|
|
overridden: false,
|
||
|
|
storedRouteId: response.storedRouteId,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public build(): RouteBuilder {
|
||
|
|
return new RouteBuilder(this.clientRef);
|
||
|
|
}
|
||
|
|
}
|