mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-31 13:47:02 +02:00
feat: Spike frontend applications registration (#9846)
This commit is contained in:
parent
3ac087e0f6
commit
1ccc6cae19
@ -35,6 +35,7 @@ const mapToDb = (client) => ({
|
|||||||
app_name: client.appName,
|
app_name: client.appName,
|
||||||
instance_id: client.instanceId,
|
instance_id: client.instanceId,
|
||||||
sdk_version: client.sdkVersion || '',
|
sdk_version: client.sdkVersion || '',
|
||||||
|
sdk_type: client.sdkType,
|
||||||
client_ip: client.clientIp,
|
client_ip: client.clientIp,
|
||||||
last_seen: client.lastSeen || 'now()',
|
last_seen: client.lastSeen || 'now()',
|
||||||
environment: client.environment || 'default',
|
environment: client.environment || 'default',
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import Controller from '../../routes/controller';
|
import Controller from '../../routes/controller';
|
||||||
import { type IUnleashConfig, type IUnleashServices, NONE } from '../../types';
|
import {
|
||||||
|
type IFlagResolver,
|
||||||
|
type IUnleashConfig,
|
||||||
|
type IUnleashServices,
|
||||||
|
type IUser,
|
||||||
|
NONE,
|
||||||
|
} from '../../types';
|
||||||
import type { Logger } from '../../logger';
|
import type { Logger } from '../../logger';
|
||||||
import type { IApiUser } from '../../types/api-user';
|
import ApiUser, { type IApiUser } from '../../types/api-user';
|
||||||
import {
|
import {
|
||||||
type ClientMetricsSchema,
|
type ClientMetricsSchema,
|
||||||
createRequestSchema,
|
createRequestSchema,
|
||||||
createResponseSchema,
|
createResponseSchema,
|
||||||
emptyResponse,
|
emptyResponse,
|
||||||
getStandardResponses,
|
|
||||||
type FrontendApiClientSchema,
|
type FrontendApiClientSchema,
|
||||||
frontendApiFeaturesSchema,
|
frontendApiFeaturesSchema,
|
||||||
type FrontendApiFeaturesSchema,
|
type FrontendApiFeaturesSchema,
|
||||||
|
getStandardResponses,
|
||||||
} from '../../openapi';
|
} from '../../openapi';
|
||||||
import type { Context } from 'unleash-client';
|
import type { Context } from 'unleash-client';
|
||||||
import { enrichContextWithIp } from './index';
|
import { enrichContextWithIp } from './index';
|
||||||
@ -34,7 +40,10 @@ interface ApiUserRequest<
|
|||||||
|
|
||||||
type Services = Pick<
|
type Services = Pick<
|
||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
'settingService' | 'frontendApiService' | 'openApiService'
|
| 'settingService'
|
||||||
|
| 'frontendApiService'
|
||||||
|
| 'openApiService'
|
||||||
|
| 'clientInstanceService'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export default class FrontendAPIController extends Controller {
|
export default class FrontendAPIController extends Controller {
|
||||||
@ -44,10 +53,13 @@ export default class FrontendAPIController extends Controller {
|
|||||||
|
|
||||||
private timer: Function;
|
private timer: Function;
|
||||||
|
|
||||||
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
constructor(config: IUnleashConfig, services: Services) {
|
constructor(config: IUnleashConfig, services: Services) {
|
||||||
super(config);
|
super(config);
|
||||||
this.logger = config.getLogger('frontend-api-controller.ts');
|
this.logger = config.getLogger('frontend-api-controller.ts');
|
||||||
this.services = services;
|
this.services = services;
|
||||||
|
this.flagResolver = config.flagResolver;
|
||||||
|
|
||||||
this.timer = (functionName: string) =>
|
this.timer = (functionName: string) =>
|
||||||
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
|
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
|
||||||
@ -216,6 +228,13 @@ export default class FrontendAPIController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveProject(user: IUser | IApiUser) {
|
||||||
|
if (user instanceof ApiUser) {
|
||||||
|
return user.projects;
|
||||||
|
}
|
||||||
|
return ['default'];
|
||||||
|
}
|
||||||
|
|
||||||
private async registerFrontendApiMetrics(
|
private async registerFrontendApiMetrics(
|
||||||
req: ApiUserRequest<unknown, unknown, ClientMetricsSchema>,
|
req: ApiUserRequest<unknown, unknown, ClientMetricsSchema>,
|
||||||
res: Response,
|
res: Response,
|
||||||
@ -229,11 +248,29 @@ export default class FrontendAPIController extends Controller {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const environment =
|
||||||
await this.services.frontendApiService.registerFrontendApiMetrics(
|
await this.services.frontendApiService.registerFrontendApiMetrics(
|
||||||
req.user,
|
req.user,
|
||||||
req.body,
|
req.body,
|
||||||
req.ip,
|
req.ip,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.body.instanceId &&
|
||||||
|
req.headers['unleash-sdk'] &&
|
||||||
|
this.flagResolver.isEnabled('registerFrontendClient')
|
||||||
|
) {
|
||||||
|
const client = {
|
||||||
|
appName: req.body.appName,
|
||||||
|
instanceId: req.body.instanceId,
|
||||||
|
sdkVersion: req.headers['unleash-sdk'] as string,
|
||||||
|
sdkType: 'frontend' as const,
|
||||||
|
environment: environment,
|
||||||
|
projects: this.resolveProject(req.user),
|
||||||
|
};
|
||||||
|
this.services.clientInstanceService.registerFrontendClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ export class FrontendApiService {
|
|||||||
token: IApiUser,
|
token: IApiUser,
|
||||||
metrics: ClientMetricsSchema,
|
metrics: ClientMetricsSchema,
|
||||||
ip: string,
|
ip: string,
|
||||||
): Promise<void> {
|
): Promise<string> {
|
||||||
FrontendApiService.assertExpectedTokenType(token);
|
FrontendApiService.assertExpectedTokenType(token);
|
||||||
|
|
||||||
const environment =
|
const environment =
|
||||||
@ -127,6 +127,8 @@ export class FrontendApiService {
|
|||||||
},
|
},
|
||||||
ip,
|
ip,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async clientForFrontendApiToken(token: IApiUser): Promise<Unleash> {
|
private async clientForFrontendApiToken(token: IApiUser): Promise<Unleash> {
|
||||||
|
@ -13,7 +13,11 @@ import type {
|
|||||||
import type { IFeatureToggleStore } from '../../feature-toggle/types/feature-toggle-store-type';
|
import type { IFeatureToggleStore } from '../../feature-toggle/types/feature-toggle-store-type';
|
||||||
import type { IStrategyStore } from '../../../types/stores/strategy-store';
|
import type { IStrategyStore } from '../../../types/stores/strategy-store';
|
||||||
import type { IClientInstanceStore } from '../../../types/stores/client-instance-store';
|
import type { IClientInstanceStore } from '../../../types/stores/client-instance-store';
|
||||||
import type { IClientApp, ISdkHeartbeat } from '../../../types/model';
|
import type {
|
||||||
|
IClientApp,
|
||||||
|
IFrontendClientApp,
|
||||||
|
ISdkHeartbeat,
|
||||||
|
} from '../../../types/model';
|
||||||
import { clientRegisterSchema } from '../shared/schema';
|
import { clientRegisterSchema } from '../shared/schema';
|
||||||
|
|
||||||
import type { IClientMetricsStoreV2 } from '../client-metrics/client-metrics-store-v2-type';
|
import type { IClientMetricsStoreV2 } from '../client-metrics/client-metrics-store-v2-type';
|
||||||
@ -104,6 +108,12 @@ export default class ClientInstanceService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerFrontendClient(data: IFrontendClientApp): void {
|
||||||
|
data.createdBy = SYSTEM_USER.username!;
|
||||||
|
|
||||||
|
this.seenClients[this.clientKey(data)] = data;
|
||||||
|
}
|
||||||
|
|
||||||
public async registerClient(
|
public async registerClient(
|
||||||
data: PartialSome<IClientApp, 'instanceId'>,
|
data: PartialSome<IClientApp, 'instanceId'>,
|
||||||
clientIp: string,
|
clientIp: string,
|
||||||
@ -111,6 +121,7 @@ export default class ClientInstanceService {
|
|||||||
const value = await clientRegisterSchema.validateAsync(data);
|
const value = await clientRegisterSchema.validateAsync(data);
|
||||||
value.clientIp = clientIp;
|
value.clientIp = clientIp;
|
||||||
value.createdBy = SYSTEM_USER.username!;
|
value.createdBy = SYSTEM_USER.username!;
|
||||||
|
value.sdkType = 'backend';
|
||||||
this.seenClients[this.clientKey(value)] = value;
|
this.seenClients[this.clientKey(value)] = value;
|
||||||
this.eventBus.emit(CLIENT_REGISTERED, value);
|
this.eventBus.emit(CLIENT_REGISTERED, value);
|
||||||
|
|
||||||
|
@ -69,7 +69,8 @@ export type IFlagKey =
|
|||||||
| 'flagsOverviewSearch'
|
| 'flagsOverviewSearch'
|
||||||
| 'flagsReleaseManagementUI'
|
| 'flagsReleaseManagementUI'
|
||||||
| 'cleanupReminder'
|
| 'cleanupReminder'
|
||||||
| 'removeInactiveApplications';
|
| 'removeInactiveApplications'
|
||||||
|
| 'registerFrontendClient';
|
||||||
|
|
||||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||||
|
|
||||||
@ -330,6 +331,10 @@ const flags: IFlags = {
|
|||||||
process.env.UNLEASH_EXPERIMENTAL_REMOVE_INACTIVE_APPLICATIONS,
|
process.env.UNLEASH_EXPERIMENTAL_REMOVE_INACTIVE_APPLICATIONS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
registerFrontendClient: parseEnvVarBoolean(
|
||||||
|
process.env.UNLEASH_EXPERIMENTAL_REGISTER_FRONTEND_CLIENT,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
@ -460,6 +460,16 @@ export interface IRoleIdentifier {
|
|||||||
roleName?: RoleName;
|
roleName?: RoleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFrontendClientApp {
|
||||||
|
appName: string;
|
||||||
|
instanceId: string;
|
||||||
|
sdkVersion: string;
|
||||||
|
sdkType: 'frontend';
|
||||||
|
environment: string;
|
||||||
|
projects: string[];
|
||||||
|
createdBy?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IClientApp {
|
export interface IClientApp {
|
||||||
appName: string;
|
appName: string;
|
||||||
instanceId: string;
|
instanceId: string;
|
||||||
@ -478,6 +488,7 @@ export interface IClientApp {
|
|||||||
platformVersion?: string;
|
platformVersion?: string;
|
||||||
yggdrasilVersion?: string;
|
yggdrasilVersion?: string;
|
||||||
specVersion?: string;
|
specVersion?: string;
|
||||||
|
sdkType?: 'frontend' | 'backend';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAppFeature {
|
export interface IAppFeature {
|
||||||
|
@ -63,6 +63,7 @@ process.nextTick(async () => {
|
|||||||
flagsOverviewSearch: true,
|
flagsOverviewSearch: true,
|
||||||
cleanupReminder: true,
|
cleanupReminder: true,
|
||||||
strictSchemaValidation: true,
|
strictSchemaValidation: true,
|
||||||
|
registerFrontendClient: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user