From b9e9b8af4880435e66c4528f165b8f758d21bd8a Mon Sep 17 00:00:00 2001 From: gbuomprisco Date: Tue, 23 Jul 2024 10:49:04 +0200 Subject: [PATCH] Refactor analytics services to support multiple providers The analytics manager has been significantly refactored to allow the use of multiple providers simultaneously. The API has been adjusted, replacing the single active service with a new Map called activeServices, to hold the active analytics services. Additionally, several methods have been modified to return promises rather than void. The 'defaultProvider' property has been removed, as it no longer fits into the system architecture. --- packages/analytics/src/analytics-manager.ts | 82 +++++++++---------- packages/analytics/src/index.ts | 1 - .../analytics/src/null-analytics-service.ts | 3 +- packages/analytics/src/types.ts | 21 +++-- 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/packages/analytics/src/analytics-manager.ts b/packages/analytics/src/analytics-manager.ts index c33ea2093..92d8d912c 100644 --- a/packages/analytics/src/analytics-manager.ts +++ b/packages/analytics/src/analytics-manager.ts @@ -5,69 +5,69 @@ import type { CreateAnalyticsManagerOptions, } from './types'; -/** - * Creates an analytics manager that can be used to track page views and events. The manager is initialized with a - * default provider and can be switched to a different provider at any time. The manager will use a NullAnalyticsService - * if the provider is not registered. - * @param options - */ export function createAnalyticsManager( options: CreateAnalyticsManagerOptions, ): AnalyticsManager { - let activeService: AnalyticsService = NullAnalyticsService; + const activeServices = new Map(); - const getActiveService = (): AnalyticsService => { - if (activeService === NullAnalyticsService) { + const getActiveServices = (): AnalyticsService[] => { + if (activeServices.size === 0) { console.debug( - 'Analytics service not initialized. Using NullAnalyticsService.', - ); - } - - return activeService; - }; - - const initialize = (provider: T, config: Config) => { - const factory = options.providers[provider]; - - if (!factory) { - console.warn( - `Analytics provider '${provider}' not registered. Using NullAnalyticsService.`, + 'No active analytics services. Using NullAnalyticsService.', ); - activeService = NullAnalyticsService; - return; + return [NullAnalyticsService]; } - activeService = factory(config); - activeService.initialize(); + return Array.from(activeServices.values()); }; - // Initialize with the default provider - initialize(options.defaultProvider, {} as Config); - return { + addProvider: ( + provider: T, + config: Config, + ) => { + const factory = options.providers[provider]; + + if (!factory) { + console.warn( + `Analytics provider '${provider}' not registered. Skipping initialization.`, + ); + + return Promise.resolve(); + } + + const service = factory(config); + activeServices.set(provider, service); + + return service.initialize(); + }, + + removeProvider: (provider: T) => { + activeServices.delete(provider); + }, + identify: (userId: string, traits?: Record) => { - return getActiveService().identify(userId, traits); + return Promise.all( + getActiveServices().map((service) => service.identify(userId, traits)), + ); }, - /** - * Track a page view with the given URL. - * @param url - */ trackPageView: (url: string) => { - return getActiveService().trackPageView(url); + return Promise.all( + getActiveServices().map((service) => service.trackPageView(url)), + ); }, - /** - * Track an event with the given name and properties. - * @param eventName - * @param eventProperties - */ trackEvent: ( eventName: string, eventProperties?: Record, ) => { - return getActiveService().trackEvent(eventName, eventProperties); + return Promise.all( + getActiveServices().map((service) => + service.trackEvent(eventName, eventProperties), + ), + ); }, }; } diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts index 74a47eda6..ed4f93d9d 100644 --- a/packages/analytics/src/index.ts +++ b/packages/analytics/src/index.ts @@ -3,7 +3,6 @@ import { NullAnalyticsService } from './null-analytics-service'; import type { AnalyticsManager } from './types'; export const analytics: AnalyticsManager = createAnalyticsManager({ - defaultProvider: 'null', providers: { null: () => NullAnalyticsService, }, diff --git a/packages/analytics/src/null-analytics-service.ts b/packages/analytics/src/null-analytics-service.ts index 7ee7b0eed..a52466105 100644 --- a/packages/analytics/src/null-analytics-service.ts +++ b/packages/analytics/src/null-analytics-service.ts @@ -3,7 +3,8 @@ import { AnalyticsService } from './types'; const noop = (event: string) => { // do nothing - this is to prevent errors when the analytics service is not initialized - return (...args: unknown[]) => { + // eslint-disable-next-line @typescript-eslint/require-await + return async (...args: unknown[]) => { console.debug( `Noop analytics service called with event: ${event}`, ...args.filter(Boolean), diff --git a/packages/analytics/src/types.ts b/packages/analytics/src/types.ts index 194b9fb4f..b2aa1a496 100644 --- a/packages/analytics/src/types.ts +++ b/packages/analytics/src/types.ts @@ -2,19 +2,25 @@ interface TrackEvent { trackEvent( eventName: string, eventProperties?: Record, - ): void; + ): Promise; } interface TrackPageView { - trackPageView(url: string): void; + trackPageView(url: string): Promise; } interface Identify { - identify(userId: string, traits?: Record): void; + identify(userId: string, traits?: Record): Promise; +} + +interface ProviderManager { + addProvider(provider: string, config: object): Promise; + + removeProvider(provider: string): void; } export interface AnalyticsService extends TrackPageView, TrackEvent, Identify { - initialize(): void; + initialize(): Promise; } export type AnalyticsProviderFactory = ( @@ -25,8 +31,11 @@ export interface CreateAnalyticsManagerOptions< T extends string, Config extends object, > { - defaultProvider: T; providers: Record>; } -export interface AnalyticsManager extends TrackPageView, TrackEvent, Identify {} +export interface AnalyticsManager + extends TrackPageView, + TrackEvent, + Identify, + ProviderManager {}