1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-09 00:18:26 +01:00
unleash.unleash/src/lib/features/frontend-api/proxy-repository.ts
Christopher Kolstad 53354224fc
chore: Bump biome and configure husky (#6589)
Upgrades biome to 1.6.1, and updates husky pre-commit hook.

Most changes here are making type imports explicit.
2024-03-18 13:58:05 +01:00

197 lines
5.8 KiB
TypeScript

import EventEmitter from 'events';
import type { RepositoryInterface } from 'unleash-client/lib/repository';
import type { Segment } from 'unleash-client/lib/strategy/strategy';
import type {
EnhancedFeatureInterface,
FeatureInterface,
} from 'unleash-client/lib/feature';
import type { IApiUser } from '../../types/api-user';
import type {
IUnleashConfig,
IUnleashServices,
IUnleashStores,
} from '../../types';
import {
mapFeaturesForClient,
mapSegmentsForClient,
} from '../playground/offline-unleash-client';
import { ALL_ENVS } from '../../util/constants';
import { UnleashEvents } from 'unleash-client';
import type { Logger } from '../../logger';
import type ConfigurationRevisionService from '../feature-toggle/configuration-revision-service';
import { UPDATE_REVISION } from '../feature-toggle/configuration-revision-service';
import {
FUNCTION_TIME,
PROXY_FEATURES_FOR_TOKEN_TIME,
} from '../../metric-events';
import metricsHelper from '../../util/metrics-helper';
type Config = Pick<IUnleashConfig, 'getLogger' | 'frontendApi' | 'eventBus'>;
type Stores = Pick<IUnleashStores, 'segmentReadModel'>;
type Services = Pick<
IUnleashServices,
'featureToggleServiceV2' | 'configurationRevisionService'
>;
// TODO: remove after finished migration to global frontend api cache
export class ProxyRepository
extends EventEmitter
implements RepositoryInterface
{
private readonly config: Config;
private readonly logger: Logger;
private readonly stores: Stores;
private readonly services: Services;
private readonly configurationRevisionService: ConfigurationRevisionService;
private readonly token: IApiUser;
private features: FeatureInterface[];
private segments: Segment[];
private interval: number;
private timer: NodeJS.Timeout | null;
private running: boolean;
private methodTimer: Function;
constructor(
config: Config,
stores: Stores,
services: Services,
token: IApiUser,
) {
super();
this.config = config;
this.logger = config.getLogger('proxy-repository.ts');
this.stores = stores;
this.services = services;
this.configurationRevisionService =
services.configurationRevisionService;
this.token = token;
this.onUpdateRevisionEvent = this.onUpdateRevisionEvent.bind(this);
this.interval = config.frontendApi.refreshIntervalInMs;
this.methodTimer = (functionName) =>
metricsHelper.wrapTimer(config.eventBus, FUNCTION_TIME, {
className: 'ProxyRepository',
functionName,
});
}
getTogglesWithSegmentData(): EnhancedFeatureInterface[] {
// TODO: add real implementation
return [];
}
getSegment(id: number): Segment | undefined {
return this.segments.find((segment) => segment.id === id);
}
getToggle(name: string): FeatureInterface {
//@ts-ignore (we must update the node SDK to allow undefined)
return this.features.find((feature) => feature.name === name);
}
getToggles(): FeatureInterface[] {
return this.features;
}
async start(): Promise<void> {
this.running = true;
await this.dataPolling();
// Reload cached token data whenever something relevant has changed.
// For now, simply reload all the data on any EventStore event.
this.configurationRevisionService.on(
UPDATE_REVISION,
this.onUpdateRevisionEvent,
);
this.emit(UnleashEvents.Ready);
this.emit(UnleashEvents.Changed);
}
stop(): void {
this.configurationRevisionService.off(
UPDATE_REVISION,
this.onUpdateRevisionEvent,
);
this.running = false;
}
private async dataPolling() {
this.timer = setTimeout(
async () => {
if (!this.running) {
clearTimeout(this.timer!);
this.timer = null;
this.logger.debug(
'Shutting down data polling for proxy repository',
);
return;
}
await this.dataPolling();
},
this.randomizeDelay(this.interval, this.interval * 2),
).unref();
await this.loadDataForToken();
}
private async loadDataForToken() {
try {
const stopTimer = this.methodTimer('loadDataForToken');
this.features = await this.featuresForToken();
this.segments = await this.segmentsForToken();
stopTimer();
} catch (e) {
this.logger.error('Cannot load data for token', e);
}
}
private randomizeDelay(floor: number, ceiling: number): number {
return Math.floor(Math.random() * (ceiling - floor) + floor);
}
private async onUpdateRevisionEvent() {
await this.loadDataForToken();
}
private async featuresForToken(): Promise<FeatureInterface[]> {
const start = Date.now();
const mappedFeatures = await mapFeaturesForClient(
await this.services.featureToggleServiceV2.getClientFeatures({
project: this.token.projects,
environment: this.environmentNameForToken(),
}),
);
const duration = (Date.now() - start) / 1000;
this.config.eventBus.emit(PROXY_FEATURES_FOR_TOKEN_TIME, { duration });
return mappedFeatures;
}
private async segmentsForToken(): Promise<Segment[]> {
return mapSegmentsForClient(
await this.stores.segmentReadModel.getAll(),
);
}
private environmentNameForToken(): string {
if (this.token.environment === ALL_ENVS) {
return 'default';
}
return this.token.environment;
}
}