import { Response, Request } from 'express';
import Controller from '../controller';
import { IUnleashConfig, IUnleashServices } from '../../types';
import { Logger } from '../../logger';
import { OpenApiService } from '../../services/openapi-service';
import { NONE } from '../../types/permissions';
import { ProxyService } from '../../services/proxy-service';
import ApiUser from '../../types/api-user';
import {
proxyFeaturesSchema,
ProxyFeaturesSchema,
} from '../../openapi/spec/proxy-features-schema';
import { Context } from 'unleash-client';
import { createContext } from '../../proxy/create-context';
import { ProxyMetricsSchema } from '../../openapi/spec/proxy-metrics-schema';
import { ProxyClientSchema } from '../../openapi/spec/proxy-client-schema';
import { createResponseSchema } from '../../openapi/util/create-response-schema';
import { createRequestSchema } from '../../openapi/util/create-request-schema';
import { emptyResponse } from '../../openapi/util/standard-responses';
import { corsOriginMiddleware } from '../../middleware/cors-origin-middleware';
interface ApiUserRequest<
PARAM = any,
ResBody = any,
ReqBody = any,
ReqQuery = any,
> extends Request {
user: ApiUser;
}
export default class ProxyController extends Controller {
private readonly logger: Logger;
private proxyService: ProxyService;
private openApiService: OpenApiService;
constructor(
config: IUnleashConfig,
{
proxyService,
openApiService,
}: Pick,
) {
super(config);
this.logger = config.getLogger('client-api/feature.js');
this.proxyService = proxyService;
this.openApiService = openApiService;
if (config.frontendApiOrigins.length > 0) {
// Support CORS requests for the frontend endpoints.
// Preflight requests are handled in `app.ts`.
this.app.use(corsOriginMiddleware(config.frontendApiOrigins));
}
this.route({
method: 'get',
path: '',
handler: this.getProxyFeatures,
permission: NONE,
middleware: [
this.openApiService.validPath({
tags: ['Unstable'],
operationId: 'getFrontendFeatures',
responses: {
200: createResponseSchema('proxyFeaturesSchema'),
},
}),
],
});
this.route({
method: 'post',
path: '',
handler: ProxyController.endpointNotImplemented,
permission: NONE,
});
this.route({
method: 'get',
path: '/client/features',
handler: ProxyController.endpointNotImplemented,
permission: NONE,
});
this.route({
method: 'post',
path: '/client/metrics',
handler: this.registerProxyMetrics,
permission: NONE,
middleware: [
this.openApiService.validPath({
tags: ['Unstable'],
operationId: 'registerFrontendMetrics',
requestBody: createRequestSchema('proxyMetricsSchema'),
responses: { 200: emptyResponse },
}),
],
});
this.route({
method: 'post',
path: '/client/register',
handler: ProxyController.registerProxyClient,
permission: NONE,
middleware: [
this.openApiService.validPath({
tags: ['Unstable'],
operationId: 'registerFrontendClient',
requestBody: createRequestSchema('proxyClientSchema'),
responses: { 200: emptyResponse },
}),
],
});
this.route({
method: 'get',
path: '/health',
handler: ProxyController.endpointNotImplemented,
permission: NONE,
});
this.route({
method: 'get',
path: '/internal-backstage/prometheus',
handler: ProxyController.endpointNotImplemented,
permission: NONE,
});
}
private static async endpointNotImplemented(
req: ApiUserRequest,
res: Response,
) {
res.status(405).json({
message: 'The frontend API does not support this endpoint.',
});
}
private async getProxyFeatures(
req: ApiUserRequest,
res: Response,
) {
const toggles = await this.proxyService.getProxyFeatures(
req.user,
ProxyController.createContext(req),
);
this.openApiService.respondWithValidation(
200,
res,
proxyFeaturesSchema.$id,
{ toggles },
);
}
private async registerProxyMetrics(
req: ApiUserRequest,
res: Response,
) {
await this.proxyService.registerProxyMetrics(
req.user,
req.body,
req.ip,
);
res.sendStatus(200);
}
private static async registerProxyClient(
req: ApiUserRequest,
res: Response,
) {
// Client registration is not yet supported by @unleash/proxy,
// but proxy clients may still expect a 200 from this endpoint.
res.sendStatus(200);
}
private static createContext(req: ApiUserRequest): Context {
const { query } = req;
query.remoteAddress = query.remoteAddress || req.ip;
query.environment = req.user.environment;
return createContext(query);
}
}