Skip to content

Commit 1ccc6ca

Browse files
authored
feat: Spike frontend applications registration (#9846)
1 parent 3ac087e commit 1ccc6ca

File tree

7 files changed

+80
-12
lines changed

7 files changed

+80
-12
lines changed

src/lib/db/client-instance-store.ts

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const mapToDb = (client) => ({
3535
app_name: client.appName,
3636
instance_id: client.instanceId,
3737
sdk_version: client.sdkVersion || '',
38+
sdk_type: client.sdkType,
3839
client_ip: client.clientIp,
3940
last_seen: client.lastSeen || 'now()',
4041
environment: client.environment || 'default',

src/lib/features/frontend-api/frontend-api-controller.ts

+46-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import type { Request, Response } from 'express';
22
import Controller from '../../routes/controller';
3-
import { type IUnleashConfig, type IUnleashServices, NONE } from '../../types';
3+
import {
4+
type IFlagResolver,
5+
type IUnleashConfig,
6+
type IUnleashServices,
7+
type IUser,
8+
NONE,
9+
} from '../../types';
410
import type { Logger } from '../../logger';
5-
import type { IApiUser } from '../../types/api-user';
11+
import ApiUser, { type IApiUser } from '../../types/api-user';
612
import {
713
type ClientMetricsSchema,
814
createRequestSchema,
915
createResponseSchema,
1016
emptyResponse,
11-
getStandardResponses,
1217
type FrontendApiClientSchema,
1318
frontendApiFeaturesSchema,
1419
type FrontendApiFeaturesSchema,
20+
getStandardResponses,
1521
} from '../../openapi';
1622
import type { Context } from 'unleash-client';
1723
import { enrichContextWithIp } from './index';
@@ -34,7 +40,10 @@ interface ApiUserRequest<
3440

3541
type Services = Pick<
3642
IUnleashServices,
37-
'settingService' | 'frontendApiService' | 'openApiService'
43+
| 'settingService'
44+
| 'frontendApiService'
45+
| 'openApiService'
46+
| 'clientInstanceService'
3847
>;
3948

4049
export default class FrontendAPIController extends Controller {
@@ -44,10 +53,13 @@ export default class FrontendAPIController extends Controller {
4453

4554
private timer: Function;
4655

56+
private flagResolver: IFlagResolver;
57+
4758
constructor(config: IUnleashConfig, services: Services) {
4859
super(config);
4960
this.logger = config.getLogger('frontend-api-controller.ts');
5061
this.services = services;
62+
this.flagResolver = config.flagResolver;
5163

5264
this.timer = (functionName: string) =>
5365
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
@@ -216,6 +228,13 @@ export default class FrontendAPIController extends Controller {
216228
);
217229
}
218230

231+
private resolveProject(user: IUser | IApiUser) {
232+
if (user instanceof ApiUser) {
233+
return user.projects;
234+
}
235+
return ['default'];
236+
}
237+
219238
private async registerFrontendApiMetrics(
220239
req: ApiUserRequest<unknown, unknown, ClientMetricsSchema>,
221240
res: Response,
@@ -229,11 +248,29 @@ export default class FrontendAPIController extends Controller {
229248
return;
230249
}
231250

232-
await this.services.frontendApiService.registerFrontendApiMetrics(
233-
req.user,
234-
req.body,
235-
req.ip,
236-
);
251+
const environment =
252+
await this.services.frontendApiService.registerFrontendApiMetrics(
253+
req.user,
254+
req.body,
255+
req.ip,
256+
);
257+
258+
if (
259+
req.body.instanceId &&
260+
req.headers['unleash-sdk'] &&
261+
this.flagResolver.isEnabled('registerFrontendClient')
262+
) {
263+
const client = {
264+
appName: req.body.appName,
265+
instanceId: req.body.instanceId,
266+
sdkVersion: req.headers['unleash-sdk'] as string,
267+
sdkType: 'frontend' as const,
268+
environment: environment,
269+
projects: this.resolveProject(req.user),
270+
};
271+
this.services.clientInstanceService.registerFrontendClient(client);
272+
}
273+
237274
res.sendStatus(200);
238275
}
239276

src/lib/features/frontend-api/frontend-api-service.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class FrontendApiService {
111111
token: IApiUser,
112112
metrics: ClientMetricsSchema,
113113
ip: string,
114-
): Promise<void> {
114+
): Promise<string> {
115115
FrontendApiService.assertExpectedTokenType(token);
116116

117117
const environment =
@@ -127,6 +127,8 @@ export class FrontendApiService {
127127
},
128128
ip,
129129
);
130+
131+
return environment;
130132
}
131133

132134
private async clientForFrontendApiToken(token: IApiUser): Promise<Unleash> {

src/lib/features/metrics/instance/instance-service.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import type {
1313
import type { IFeatureToggleStore } from '../../feature-toggle/types/feature-toggle-store-type';
1414
import type { IStrategyStore } from '../../../types/stores/strategy-store';
1515
import type { IClientInstanceStore } from '../../../types/stores/client-instance-store';
16-
import type { IClientApp, ISdkHeartbeat } from '../../../types/model';
16+
import type {
17+
IClientApp,
18+
IFrontendClientApp,
19+
ISdkHeartbeat,
20+
} from '../../../types/model';
1721
import { clientRegisterSchema } from '../shared/schema';
1822

1923
import type { IClientMetricsStoreV2 } from '../client-metrics/client-metrics-store-v2-type';
@@ -104,13 +108,20 @@ export default class ClientInstanceService {
104108
});
105109
}
106110

111+
public registerFrontendClient(data: IFrontendClientApp): void {
112+
data.createdBy = SYSTEM_USER.username!;
113+
114+
this.seenClients[this.clientKey(data)] = data;
115+
}
116+
107117
public async registerClient(
108118
data: PartialSome<IClientApp, 'instanceId'>,
109119
clientIp: string,
110120
): Promise<void> {
111121
const value = await clientRegisterSchema.validateAsync(data);
112122
value.clientIp = clientIp;
113123
value.createdBy = SYSTEM_USER.username!;
124+
value.sdkType = 'backend';
114125
this.seenClients[this.clientKey(value)] = value;
115126
this.eventBus.emit(CLIENT_REGISTERED, value);
116127

src/lib/types/experimental.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ export type IFlagKey =
6969
| 'flagsOverviewSearch'
7070
| 'flagsReleaseManagementUI'
7171
| 'cleanupReminder'
72-
| 'removeInactiveApplications';
72+
| 'removeInactiveApplications'
73+
| 'registerFrontendClient';
7374

7475
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
7576

@@ -330,6 +331,10 @@ const flags: IFlags = {
330331
process.env.UNLEASH_EXPERIMENTAL_REMOVE_INACTIVE_APPLICATIONS,
331332
false,
332333
),
334+
registerFrontendClient: parseEnvVarBoolean(
335+
process.env.UNLEASH_EXPERIMENTAL_REGISTER_FRONTEND_CLIENT,
336+
false,
337+
),
333338
};
334339

335340
export const defaultExperimentalOptions: IExperimentalOptions = {

src/lib/types/model.ts

+11
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,16 @@ export interface IRoleIdentifier {
460460
roleName?: RoleName;
461461
}
462462

463+
export interface IFrontendClientApp {
464+
appName: string;
465+
instanceId: string;
466+
sdkVersion: string;
467+
sdkType: 'frontend';
468+
environment: string;
469+
projects: string[];
470+
createdBy?: string;
471+
}
472+
463473
export interface IClientApp {
464474
appName: string;
465475
instanceId: string;
@@ -478,6 +488,7 @@ export interface IClientApp {
478488
platformVersion?: string;
479489
yggdrasilVersion?: string;
480490
specVersion?: string;
491+
sdkType?: 'frontend' | 'backend';
481492
}
482493

483494
export interface IAppFeature {

src/server-dev.ts

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ process.nextTick(async () => {
6363
flagsOverviewSearch: true,
6464
cleanupReminder: true,
6565
strictSchemaValidation: true,
66+
registerFrontendClient: true,
6667
},
6768
},
6869
authentication: {

0 commit comments

Comments
 (0)