B2B-88: add starter kit structure and elements
This commit is contained in:
94
packages/analytics/src/analytics-manager.ts
Normal file
94
packages/analytics/src/analytics-manager.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { NullAnalyticsService } from './null-analytics-service';
|
||||
import type {
|
||||
AnalyticsManager,
|
||||
AnalyticsService,
|
||||
CreateAnalyticsManagerOptions,
|
||||
} from './types';
|
||||
|
||||
export function createAnalyticsManager<T extends string, Config extends object>(
|
||||
options: CreateAnalyticsManagerOptions<T, Config>,
|
||||
): AnalyticsManager {
|
||||
const activeServices = new Map<T, AnalyticsService>();
|
||||
|
||||
const getActiveServices = (): AnalyticsService[] => {
|
||||
if (activeServices.size === 0) {
|
||||
console.debug(
|
||||
'No active analytics services. Using NullAnalyticsService.',
|
||||
);
|
||||
|
||||
return [NullAnalyticsService];
|
||||
}
|
||||
|
||||
return Array.from(activeServices.values());
|
||||
};
|
||||
|
||||
const registerActiveServices = (
|
||||
options: CreateAnalyticsManagerOptions<T, Config>,
|
||||
) => {
|
||||
Object.keys(options.providers).forEach((provider) => {
|
||||
const providerKey = provider as keyof typeof options.providers;
|
||||
const factory = options.providers[providerKey];
|
||||
|
||||
if (!factory) {
|
||||
console.warn(
|
||||
`Analytics provider '${provider}' not registered. Skipping initialization.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const service = factory();
|
||||
activeServices.set(provider as T, service);
|
||||
|
||||
void service.initialize();
|
||||
});
|
||||
};
|
||||
|
||||
registerActiveServices(options);
|
||||
|
||||
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<string, string>) => {
|
||||
return Promise.all(
|
||||
getActiveServices().map((service) => service.identify(userId, traits)),
|
||||
);
|
||||
},
|
||||
|
||||
trackPageView: (path: string) => {
|
||||
return Promise.all(
|
||||
getActiveServices().map((service) => service.trackPageView(path)),
|
||||
);
|
||||
},
|
||||
|
||||
trackEvent: (
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>,
|
||||
) => {
|
||||
return Promise.all(
|
||||
getActiveServices().map((service) =>
|
||||
service.trackEvent(eventName, eventProperties),
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
9
packages/analytics/src/index.ts
Normal file
9
packages/analytics/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createAnalyticsManager } from './analytics-manager';
|
||||
import { NullAnalyticsService } from './null-analytics-service';
|
||||
import type { AnalyticsManager } from './types';
|
||||
|
||||
export const analytics: AnalyticsManager = createAnalyticsManager({
|
||||
providers: {
|
||||
null: () => NullAnalyticsService,
|
||||
},
|
||||
});
|
||||
23
packages/analytics/src/null-analytics-service.ts
Normal file
23
packages/analytics/src/null-analytics-service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AnalyticsService } from './types';
|
||||
|
||||
const noop = (event: string) => {
|
||||
// do nothing - this is to prevent errors when the analytics service is not initialized
|
||||
|
||||
return async (...args: unknown[]) => {
|
||||
console.debug(
|
||||
`Noop analytics service called with event: ${event}`,
|
||||
...args.filter(Boolean),
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Null analytics service that does nothing. It is initialized with a noop function. This is useful for testing or when
|
||||
* the user is calling analytics methods before the analytics service is initialized.
|
||||
*/
|
||||
export const NullAnalyticsService: AnalyticsService = {
|
||||
initialize: noop('initialize'),
|
||||
trackPageView: noop('trackPageView'),
|
||||
trackEvent: noop('trackEvent'),
|
||||
identify: noop('identify'),
|
||||
};
|
||||
41
packages/analytics/src/types.ts
Normal file
41
packages/analytics/src/types.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
interface TrackEvent {
|
||||
trackEvent(
|
||||
eventName: string,
|
||||
eventProperties?: Record<string, string | string[]>,
|
||||
): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface TrackPageView {
|
||||
trackPageView(path: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface Identify {
|
||||
identify(userId: string, traits?: Record<string, string>): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface ProviderManager {
|
||||
addProvider(provider: string, config: object): Promise<unknown>;
|
||||
|
||||
removeProvider(provider: string): void;
|
||||
}
|
||||
|
||||
export interface AnalyticsService extends TrackPageView, TrackEvent, Identify {
|
||||
initialize(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export type AnalyticsProviderFactory<Config extends object> = (
|
||||
config?: Config,
|
||||
) => AnalyticsService;
|
||||
|
||||
export interface CreateAnalyticsManagerOptions<
|
||||
T extends string,
|
||||
Config extends object,
|
||||
> {
|
||||
providers: Record<T, AnalyticsProviderFactory<Config>>;
|
||||
}
|
||||
|
||||
export interface AnalyticsManager
|
||||
extends TrackPageView,
|
||||
TrackEvent,
|
||||
Identify,
|
||||
ProviderManager {}
|
||||
Reference in New Issue
Block a user