Refactor VPN proxy client implementation
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import crypto from 'node:crypto';
|
||||
import type { ServerResponse } from 'node:http';
|
||||
|
||||
import { normalizeHarborError } from '../../shared/errors.js';
|
||||
|
||||
export function sendJson(res: ServerResponse, statusCode: number, payload: unknown) {
|
||||
res.writeHead(statusCode, { 'content-type': 'application/json; charset=utf-8' });
|
||||
res.end(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
function redactLogDetails(value: unknown) {
|
||||
return String(value || '').replace(/https?:\/\/\S+/gi, '[redacted-url]');
|
||||
}
|
||||
|
||||
export function sendError(res: ServerResponse, error: unknown) {
|
||||
const harborError = normalizeHarborError(error);
|
||||
const correlationId = crypto.randomUUID();
|
||||
const technical = harborError.cause instanceof Error
|
||||
? harborError.cause.message
|
||||
: harborError.details || error;
|
||||
const technicalMessage = technical instanceof Error
|
||||
? technical.message
|
||||
: technical;
|
||||
console.error(
|
||||
`[control] request failed [${correlationId}] ${harborError.code}: ${redactLogDetails(technicalMessage)}`,
|
||||
);
|
||||
return sendJson(res, harborError.status, {
|
||||
success: false,
|
||||
error: {
|
||||
code: harborError.code,
|
||||
message: harborError.message,
|
||||
retryable: harborError.retryable,
|
||||
correlationId,
|
||||
...(harborError.details ? { details: harborError.details } : {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { ConnectionService } from '../../features/connection/index.js';
|
||||
|
||||
interface ConnectionRuntimeRouteDependencies {
|
||||
connection: Pick<ConnectionService, 'stop' | 'restart'>;
|
||||
withOperation<T>(kind: string, operation: () => Promise<T>): Promise<T>;
|
||||
sendState(res: ServerResponse, extra: { singboxRunning: boolean }): Promise<void>;
|
||||
}
|
||||
|
||||
export function createConnectionRuntimeRoute(dependencies: ConnectionRuntimeRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method === 'POST' && req.url === '/api/singbox/stop') {
|
||||
await dependencies.withOperation('stop', () => dependencies.connection.stop());
|
||||
await dependencies.sendState(res, { singboxRunning: false });
|
||||
return true;
|
||||
}
|
||||
if (req.method === 'POST' && req.url === '/api/singbox/restart') {
|
||||
await dependencies.withOperation('start', () => dependencies.connection.restart());
|
||||
await dependencies.sendState(res, { singboxRunning: true });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { ConnectivityDiagnosticsUseCase } from '../../features/diagnostics/index.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface ConnectivityDiagnosticsRouteDependencies {
|
||||
diagnostics: Pick<ConnectivityDiagnosticsUseCase, 'run'>;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
export function createConnectivityDiagnosticsRoute(
|
||||
dependencies: ConnectivityDiagnosticsRouteDependencies,
|
||||
) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'POST' || req.url !== '/api/diagnostics/connectivity') return false;
|
||||
const { services = [], target = null } = await dependencies.readBody(req);
|
||||
const result = await dependencies.diagnostics.run(services, target);
|
||||
sendJson(res, 200, result);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { HarborError } from '../../../shared/errors.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface DeviceInventoryPort {
|
||||
snapshot(): unknown;
|
||||
refresh(): Promise<unknown>;
|
||||
update(deviceId: string, patch: Record<string, unknown>, expectedRevision: unknown): unknown;
|
||||
setPolicy(deviceId: string, mode: unknown, expectedRevision: unknown): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface DeviceInventoryRouteDependencies {
|
||||
deviceInventory: DeviceInventoryPort | null;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
const DEVICE_PATH = /^\/api\/devices\/(dev_[a-f0-9]{16})$/;
|
||||
const DEVICE_POLICY_PATH = /^\/api\/devices\/(dev_[a-f0-9]{16})\/policy$/;
|
||||
|
||||
export function createDeviceInventoryRoute(dependencies: DeviceInventoryRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
||||
|
||||
if (pathname === '/api/devices') {
|
||||
if (!dependencies.deviceInventory || req.method !== 'GET') {
|
||||
throw new HarborError('ENDPOINT_NOT_FOUND');
|
||||
}
|
||||
sendJson(res, 200, dependencies.deviceInventory.snapshot());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pathname === '/api/devices/refresh') {
|
||||
if (!dependencies.deviceInventory || req.method !== 'POST') {
|
||||
throw new HarborError('ENDPOINT_NOT_FOUND');
|
||||
}
|
||||
sendJson(res, 200, await dependencies.deviceInventory.refresh());
|
||||
return true;
|
||||
}
|
||||
|
||||
const deviceMatch = pathname.match(DEVICE_PATH);
|
||||
if (deviceMatch) {
|
||||
if (!dependencies.deviceInventory || req.method !== 'PUT') {
|
||||
throw new HarborError('ENDPOINT_NOT_FOUND');
|
||||
}
|
||||
const { expectedRevision, ...patch } = await dependencies.readBody(req);
|
||||
sendJson(
|
||||
res,
|
||||
200,
|
||||
dependencies.deviceInventory.update(deviceMatch[1], patch, expectedRevision),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const policyMatch = pathname.match(DEVICE_POLICY_PATH);
|
||||
if (policyMatch) {
|
||||
if (!dependencies.deviceInventory || req.method !== 'PUT') {
|
||||
throw new HarborError('ENDPOINT_NOT_FOUND');
|
||||
}
|
||||
const body = await dependencies.readBody(req);
|
||||
sendJson(
|
||||
res,
|
||||
200,
|
||||
await dependencies.deviceInventory.setPolicy(
|
||||
policyMatch[1],
|
||||
body.mode,
|
||||
body.expectedRevision,
|
||||
),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { GatewayAutoService } from '../../features/routing/index.js';
|
||||
import { HarborError } from '../../../shared/errors.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface GatewayAutoRouteDependencies {
|
||||
appMode: string;
|
||||
gatewayAuto: Pick<GatewayAutoService, 'setEnabled'>;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
withOperation<T>(kind: string, operation: () => Promise<T>): Promise<T>;
|
||||
readStatePayload(): Promise<Record<string, unknown> & { gatewayAuto?: unknown }>;
|
||||
}
|
||||
|
||||
export function createGatewayAutoRoute(dependencies: GatewayAutoRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'POST' || req.url !== '/api/gateway-auto') return false;
|
||||
if (dependencies.appMode !== 'client') throw new HarborError('REQUEST_INVALID');
|
||||
const { enabled } = await dependencies.readBody(req);
|
||||
if (typeof enabled !== 'boolean') throw new HarborError('REQUEST_INVALID');
|
||||
|
||||
await dependencies.withOperation(
|
||||
'gateway-auto',
|
||||
() => dependencies.gatewayAuto.setEnabled(enabled),
|
||||
);
|
||||
const state = await dependencies.readStatePayload();
|
||||
sendJson(res, 200, { success: true, gatewayAuto: state.gatewayAuto, state });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { buildGatewayPresence } from '../../gatewayPresence.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface GatewayPresenceState {
|
||||
subscriptionUrl?: unknown;
|
||||
}
|
||||
|
||||
interface GatewayPresenceRouteDependencies {
|
||||
appMode: string;
|
||||
readState(): GatewayPresenceState;
|
||||
getHwid(): unknown;
|
||||
}
|
||||
|
||||
export function createGatewayPresenceRoute(dependencies: GatewayPresenceRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
const requestUrl = new URL(req.url || '/', 'http://localhost');
|
||||
if (req.method !== 'GET' || requestUrl.pathname !== '/api/gateway-presence') return false;
|
||||
|
||||
const state = dependencies.readState();
|
||||
const gatewayId = dependencies.getHwid();
|
||||
sendJson(res, 200, buildGatewayPresence({
|
||||
appMode: dependencies.appMode,
|
||||
subscriptionUrl: state.subscriptionUrl,
|
||||
gatewayId,
|
||||
nonce: requestUrl.searchParams.get('nonce'),
|
||||
}));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { HarborError } from '../../../shared/errors.js';
|
||||
import { sendPrometheusMetrics } from '../../prometheusMetrics.js';
|
||||
|
||||
interface MetricsSnapshotPort {
|
||||
metricsSnapshot(): unknown;
|
||||
}
|
||||
|
||||
interface PrometheusMetricsRouteDependencies {
|
||||
deviceInventory: MetricsSnapshotPort | null;
|
||||
}
|
||||
|
||||
export function createPrometheusMetricsRoute(dependencies: PrometheusMetricsRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
const pathname = new URL(req.url || '/', 'http://localhost').pathname;
|
||||
if (pathname !== '/metrics') return false;
|
||||
if (!dependencies.deviceInventory || req.method !== 'GET') {
|
||||
throw new HarborError('ENDPOINT_NOT_FOUND');
|
||||
}
|
||||
sendPrometheusMetrics(res, dependencies.deviceInventory.metricsSnapshot());
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { RouteRulesService } from '../../features/routing/index.js';
|
||||
|
||||
interface RouteRulesRouteDependencies {
|
||||
routeRules: RouteRulesService;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
sendState(res: ServerResponse): Promise<void>;
|
||||
}
|
||||
|
||||
export function createRouteRulesRoute(dependencies: RouteRulesRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'PUT' || req.url !== '/api/route-rules') return false;
|
||||
const { rules, expectedRulesRevision, expectedRevision } = await dependencies.readBody(req);
|
||||
await dependencies.routeRules.update(rules, expectedRulesRevision, expectedRevision);
|
||||
await dependencies.sendState(res);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { ConnectionService } from '../../features/connection/index.js';
|
||||
|
||||
interface ServerApplyRouteDependencies {
|
||||
connection: Pick<ConnectionService, 'apply'>;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
withOperation<T>(kind: string, operation: () => Promise<T>): Promise<T>;
|
||||
sendState(res: ServerResponse, extra: { serverId: string; selectedTag: string }): Promise<void>;
|
||||
}
|
||||
|
||||
export function createServerApplyRoute(dependencies: ServerApplyRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'POST' || req.url !== '/api/apply') return false;
|
||||
const { serverId = '', selectedTag = '' } = await dependencies.readBody(req);
|
||||
const result = await dependencies.withOperation(
|
||||
'apply-server',
|
||||
() => dependencies.connection.apply(serverId, selectedTag),
|
||||
);
|
||||
await dependencies.sendState(res, result);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { ServerHealthService } from '../../features/servers/index.js';
|
||||
|
||||
interface ServerHealthRouteDependencies {
|
||||
serverHealth: ServerHealthService;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
sendState(res: ServerResponse, extra: { results: Array<Record<string, unknown>> }): Promise<void>;
|
||||
}
|
||||
|
||||
export function createServerHealthRoute(dependencies: ServerHealthRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'POST' || req.url !== '/api/servers/ping-all') return false;
|
||||
const { serverIds = [] } = await dependencies.readBody(req);
|
||||
const results = await dependencies.serverHealth.check(serverIds);
|
||||
await dependencies.sendState(res, { results });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { buildSharedProxyInfo } from '../../sharedProxy.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface SharedProxyRouteDependencies {
|
||||
appMode: string;
|
||||
proxyPort: unknown;
|
||||
sharedProxyHost: unknown;
|
||||
refreshRuntime(): Promise<{ running?: unknown }>;
|
||||
}
|
||||
|
||||
export function createSharedProxyRoute(dependencies: SharedProxyRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'GET' || req.url !== '/api/shared-proxy') return false;
|
||||
|
||||
const runtime = await dependencies.refreshRuntime();
|
||||
sendJson(res, 200, buildSharedProxyInfo({
|
||||
appMode: dependencies.appMode,
|
||||
proxyPort: dependencies.proxyPort,
|
||||
running: runtime.running,
|
||||
hostHeader: req.headers.host,
|
||||
sharedProxyHost: dependencies.sharedProxyHost,
|
||||
}));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { normalizeStoredState, type GatewayAutoState, type StateSnapshot } from '../../../shared/contracts/state.js';
|
||||
import type { StateReadResult, StateService } from '../../features/state/stateService.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
export interface LegacyStatePayload extends StateSnapshot, Record<string, unknown> {
|
||||
port: number;
|
||||
proxyPort: number;
|
||||
configExists: boolean;
|
||||
singboxRunning: boolean;
|
||||
singboxStartedAt: string | null;
|
||||
subscriptionHost: string;
|
||||
hasSubscription: boolean;
|
||||
selectedTag: string;
|
||||
userInfo: Record<string, unknown>;
|
||||
fetchedAt: string | null;
|
||||
gatewayAuto: {
|
||||
mode: string;
|
||||
enabled: boolean;
|
||||
available: boolean;
|
||||
address: string;
|
||||
uiOrigin: string;
|
||||
interface: string;
|
||||
failures: number;
|
||||
lastError: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface StateRouteDependencies {
|
||||
stateService: StateService;
|
||||
port: number;
|
||||
proxyPort: number;
|
||||
}
|
||||
|
||||
function withStateV0Compatibility(
|
||||
{ snapshot, storedState, gatewayAuto, configExists }: StateReadResult,
|
||||
{ port, proxyPort }: Pick<StateRouteDependencies, 'port' | 'proxyPort'>,
|
||||
): LegacyStatePayload {
|
||||
const stored = normalizeStoredState(storedState);
|
||||
return {
|
||||
...snapshot,
|
||||
port,
|
||||
proxyPort,
|
||||
configExists,
|
||||
singboxRunning: snapshot.connection.process === 'running',
|
||||
singboxStartedAt: snapshot.connection.startedAt,
|
||||
subscriptionHost: snapshot.subscription.host,
|
||||
hasSubscription: snapshot.subscription.status === 'ready',
|
||||
selectedTag: stored.selectedTag,
|
||||
userInfo: snapshot.subscription.userInfo,
|
||||
fetchedAt: snapshot.subscription.fetchedAt,
|
||||
gatewayAuto: snapshot.mode === 'client'
|
||||
? legacyGatewayAuto(gatewayAuto, stored.gatewayAutoEnabled !== false)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
function legacyGatewayAuto(gatewayAuto: GatewayAutoState, enabled: boolean) {
|
||||
return {
|
||||
mode: gatewayAuto?.mode || 'local-vpn',
|
||||
enabled,
|
||||
available: Boolean(gatewayAuto?.gatewayId),
|
||||
address: gatewayAuto?.gateway?.gateway || '',
|
||||
uiOrigin: gatewayAuto?.uiOrigin || '',
|
||||
interface: gatewayAuto?.gateway?.interface || '',
|
||||
failures: Number(gatewayAuto?.failures) || 0,
|
||||
lastError: gatewayAuto?.lastError || '',
|
||||
};
|
||||
}
|
||||
|
||||
export function createStateRoute(dependencies: StateRouteDependencies) {
|
||||
const readPayload = async () => withStateV0Compatibility(
|
||||
await dependencies.stateService.read(),
|
||||
dependencies,
|
||||
);
|
||||
|
||||
return {
|
||||
readPayload,
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'GET' || req.url !== '/api/state') return false;
|
||||
sendJson(res, 200, await readPayload());
|
||||
return true;
|
||||
},
|
||||
async send(res: ServerResponse, extra: Record<string, unknown> = {}) {
|
||||
sendJson(res, 200, { success: true, ...extra, state: await readPayload() });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type StateRoute = ReturnType<typeof createStateRoute>;
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { SubscriptionService } from '../../features/subscription/index.js';
|
||||
|
||||
interface SubscriptionMutationRouteDependencies {
|
||||
subscriptionService: Pick<
|
||||
SubscriptionService,
|
||||
'importSubscription' | 'refreshSavedSubscription' | 'resetSavedSubscription'
|
||||
>;
|
||||
readBody(req: IncomingMessage): Promise<Record<string, unknown>>;
|
||||
withOperation<T>(kind: string, operation: () => Promise<T>): Promise<T>;
|
||||
sendState(res: ServerResponse, extra?: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
|
||||
export function createSubscriptionMutationRoute(dependencies: SubscriptionMutationRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method === 'POST' && req.url === '/api/subscription/fetch') {
|
||||
const { url = '' } = await dependencies.readBody(req);
|
||||
const result = await dependencies.withOperation(
|
||||
'subscription-import',
|
||||
() => dependencies.subscriptionService.importSubscription(String(url).trim()),
|
||||
);
|
||||
await dependencies.sendState(res, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.url === '/api/subscription/refresh') {
|
||||
const { success: _success, ...result } = await dependencies.withOperation(
|
||||
'subscription-refresh',
|
||||
() => dependencies.subscriptionService.refreshSavedSubscription(),
|
||||
);
|
||||
await dependencies.sendState(res, result);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method === 'DELETE' && req.url === '/api/subscription') {
|
||||
await dependencies.withOperation(
|
||||
'subscription-forget',
|
||||
() => dependencies.subscriptionService.resetSavedSubscription(),
|
||||
);
|
||||
await dependencies.sendState(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import type { ValidateSubscription } from '../../features/subscription/index.js';
|
||||
|
||||
interface SubscriptionValidationRouteDependencies {
|
||||
validateSubscription: ValidateSubscription;
|
||||
readBody: (req: IncomingMessage) => Promise<Record<string, unknown>>;
|
||||
sendState: (res: ServerResponse, extra: Record<string, unknown>) => Promise<void>;
|
||||
}
|
||||
|
||||
export function createSubscriptionValidationRoute(dependencies: SubscriptionValidationRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'POST' || req.url !== '/api/subscription/validate') return false;
|
||||
const { url = '' } = await dependencies.readBody(req);
|
||||
await dependencies.sendState(res, await dependencies.validateSubscription(url));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
|
||||
import { buildGatewayVersionInfo } from '../../version.js';
|
||||
import { sendJson } from '../response.js';
|
||||
|
||||
interface DataplaneVersionState {
|
||||
gatewayBackendVersion?: unknown;
|
||||
singBoxVersion?: unknown;
|
||||
}
|
||||
|
||||
interface VersionRouteDependencies {
|
||||
versionInfo: Record<string, unknown>;
|
||||
refreshDataplaneRuntime: (() => Promise<DataplaneVersionState>) | null;
|
||||
}
|
||||
|
||||
export function createVersionRoute(dependencies: VersionRouteDependencies) {
|
||||
return {
|
||||
async handle(req: IncomingMessage, res: ServerResponse) {
|
||||
if (req.method !== 'GET' || req.url !== '/api/version') return false;
|
||||
|
||||
const payload = dependencies.refreshDataplaneRuntime
|
||||
? buildGatewayVersionInfo(
|
||||
dependencies.versionInfo,
|
||||
await dependencies.refreshDataplaneRuntime(),
|
||||
)
|
||||
: dependencies.versionInfo;
|
||||
sendJson(res, 200, payload);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user