Files
hurler-webdesign-saas/src/app/core/services/openpanel.service.ts

150 lines
4.6 KiB
TypeScript

import { Injectable, OnDestroy, inject } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';
import { filter, Subscription, skip } from 'rxjs';
import { OpenPanel } from '@openpanel/web';
import type { IdentifyPayload } from '@openpanel/web';
import { OpenPanelConfig, OPENPANEL_CONFIG } from '@core/models/openpanel.model';
export type TrackProperties = Record<string, string | number | boolean | null | undefined>;
@Injectable({
providedIn: 'root',
})
export class OpenPanelService implements OnDestroy {
private readonly config = inject(OPENPANEL_CONFIG);
private readonly platformId = inject(PLATFORM_ID)
private readonly router = inject(Router);
private op?: OpenPanel;
private routerSubscription?: Subscription;
constructor() {
if(isPlatformBrowser(this.platformId)) {
this.initialize();
}
}
// ─── Initialization ────────────────────────────────────────────────────────
private initialize(): void {
this.op = new OpenPanel({
clientId: this.config.clientId,
apiUrl: this.config.apiUrl,
trackScreenViews: false, // We handle this manually via Router
trackOutgoingLinks: this.config.trackOutgoingLinks ?? false,
trackAttributes: this.config.trackAttributes ?? false,
disabled: this.config.disabled ?? false,
});
if (this.config.globalProperties) {
this.op.setGlobalProperties(this.config.globalProperties);
}
if (this.config.trackScreenViews !== false) {
this.setupRouteTracking();
}
}
private setupRouteTracking(): void {
this.routerSubscription?.unsubscribe();
this.routerSubscription = this.router.events.pipe(
filter((event) => event instanceof NavigationEnd),
).subscribe(() => {
const route = this.getActiveRoute();
const trackName = route.snapshot.data['trackName'] ?? this.router.url;
this.trackScreenView(trackName);
});
}
private getActiveRoute() {
let route = this.router.routerState.root;
while (route.firstChild) route = route.firstChild;
return route;
}
// ─── Public API ────────────────────────────────────────────────────────────
/**
* Tracks a custom event with optional properties.
* @example opService.track('button_clicked', { button_name: 'signup' });
*/
track(eventName: string, properties?: TrackProperties): void {
if (!this.op) return;
if (this.config.debug) {
console.debug('[OpenPanel] track:', eventName, properties);
}
this.op.track(eventName, properties);
}
/**
* Identifies the current user. Call this after login.
* @example opService.identify({ profileId: 'user-123', email: 'user@example.com' });
*/
identify(payload: IdentifyPayload): void {
if (!this.op) return;
if (this.config.debug) {
console.debug('[OpenPanel] identify:', payload.profileId);
}
this.op.identify(payload);
}
/**
* Clears the current user identity. Call this on logout.
*/
clearUser(): void {
if (!this.op) return;
if (this.config.debug) {
console.debug('[OpenPanel] clearUser');
}
this.op.clear();
}
/**
* Sets properties that will be sent with every subsequent event.
*/
setGlobalProperties(properties: TrackProperties): void {
if (!this.op) return;
this.op.setGlobalProperties(properties);
}
/**
* Increments a numeric property on the user profile.
* @example opService.increment('login_count');
*/
increment(property: string): void {
if (!this.op) return;
this.op.increment(property);
}
/**
* Decrements a numeric property on the user profile.
* @example opService.decrement('credits');
*/
decrement(property: string): void {
if (!this.op) return;
this.op.decrement(property);
}
/**
* Manually tracks a screen/page view.
*/
trackScreenView(path?: string): void {
if (!this.op) return;
const currentPath = path ?? this.router.url;
if (this.config.debug) {
console.debug('[OpenPanel] screenView:', currentPath);
}
this.op.track('screen_view', { path: currentPath });
}
// ─── Cleanup ───────────────────────────────────────────────────────────────
ngOnDestroy(): void {
this.routerSubscription?.unsubscribe();
}
}