@sneat/logging 0.21.1 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/analytics/gtag-analytics.service.js +111 -0
- package/esm2022/lib/analytics/gtag-analytics.service.js.map +1 -0
- package/esm2022/lib/analytics/provide-sneat-analytics.js +6 -0
- package/esm2022/lib/analytics/provide-sneat-analytics.js.map +1 -1
- package/lib/analytics/gtag-analytics.service.d.ts +26 -0
- package/lib/analytics/provide-sneat-analytics.d.ts +1 -0
- package/package.json +1 -1
- package/tsconfig.lib.prod.tsbuildinfo +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { inject } from '@angular/core';
|
|
2
|
+
import { NavigationEnd, Router } from '@angular/router';
|
|
3
|
+
import { filter } from 'rxjs';
|
|
4
|
+
import { ErrorLogger, } from '@sneat/core';
|
|
5
|
+
const logErrOptions = { show: false, feedback: false };
|
|
6
|
+
// EU/EEA + UK — analytics storage is denied by default here (until the visitor
|
|
7
|
+
// consents), matching the geo-gated Consent Mode v2 posture of the marketing
|
|
8
|
+
// landings (landings/src/components/GoogleAnalytics.astro). Elsewhere it is
|
|
9
|
+
// granted by default. Because an app and its landing share one measurement ID
|
|
10
|
+
// on the same domain, a consent choice made on the landing carries over here.
|
|
11
|
+
const CONSENT_GATED_REGIONS = [
|
|
12
|
+
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR',
|
|
13
|
+
'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK',
|
|
14
|
+
'SI', 'ES', 'SE', 'IS', 'LI', 'NO', 'GB',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Google Analytics (GA4) backend for the Sneat AnalyticsService fan-out.
|
|
18
|
+
*
|
|
19
|
+
* Uses gtag.js directly (not @angular/fire/analytics) so it can report to the
|
|
20
|
+
* app's own per-domain GA4 property — the same property/stream its marketing
|
|
21
|
+
* landing already uses — instead of the shared Firebase-linked property. Every
|
|
22
|
+
* hit carries `surface: 'app'` so in-app usage is separable from landing
|
|
23
|
+
* traffic within the one shared stream while keeping a single client_id (so the
|
|
24
|
+
* landing -> app funnel stays intact).
|
|
25
|
+
*
|
|
26
|
+
* Instantiated by provideSneatAnalytics() with the app's measurement ID.
|
|
27
|
+
*/
|
|
28
|
+
export class GtagAnalyticsService {
|
|
29
|
+
constructor(measurementId) {
|
|
30
|
+
this.measurementId = measurementId;
|
|
31
|
+
this.errorLogger = inject(ErrorLogger);
|
|
32
|
+
this.router = inject(Router);
|
|
33
|
+
this.gtag = () => undefined;
|
|
34
|
+
this.logError = (e, m) => this.errorLogger.logError(e, m, logErrOptions);
|
|
35
|
+
this.init();
|
|
36
|
+
}
|
|
37
|
+
init() {
|
|
38
|
+
try {
|
|
39
|
+
const w = window;
|
|
40
|
+
const dataLayer = (w.dataLayer = w.dataLayer ?? []);
|
|
41
|
+
this.gtag = (...args) => {
|
|
42
|
+
dataLayer.push(args);
|
|
43
|
+
};
|
|
44
|
+
this.gtag('js', new Date());
|
|
45
|
+
// Consent Mode v2: granted by default, denied in gated regions until the
|
|
46
|
+
// visitor consents (region-scoped default wins for those countries).
|
|
47
|
+
this.gtag('consent', 'default', { analytics_storage: 'granted' });
|
|
48
|
+
this.gtag('consent', 'default', {
|
|
49
|
+
analytics_storage: 'denied',
|
|
50
|
+
region: CONSENT_GATED_REGIONS,
|
|
51
|
+
});
|
|
52
|
+
const script = document.createElement('script');
|
|
53
|
+
script.async = true;
|
|
54
|
+
script.src =
|
|
55
|
+
'https://www.googletagmanager.com/gtag/js?id=' + this.measurementId;
|
|
56
|
+
document.head.appendChild(script);
|
|
57
|
+
this.gtag('config', this.measurementId, {
|
|
58
|
+
// SPA: we emit page_view ourselves on each NavigationEnd below.
|
|
59
|
+
send_page_view: false,
|
|
60
|
+
// Never use the data for ads (mirrors the landings' config).
|
|
61
|
+
allow_google_signals: false,
|
|
62
|
+
allow_ad_personalization_signals: false,
|
|
63
|
+
});
|
|
64
|
+
// Default parameter that tags every hit as in-app.
|
|
65
|
+
this.gtag('set', { surface: 'app' });
|
|
66
|
+
this.router.events
|
|
67
|
+
.pipe(filter((e) => e instanceof NavigationEnd))
|
|
68
|
+
.subscribe((e) => this.setCurrentScreen(e.urlAfterRedirects));
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
this.logError(e, 'Failed to init Google Analytics (gtag)');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
logEvent(eventName, eventParams) {
|
|
75
|
+
try {
|
|
76
|
+
this.gtag('event', eventName, eventParams);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
this.logError(e, 'Failed to log event to Google Analytics');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
setCurrentScreen(screenName) {
|
|
83
|
+
try {
|
|
84
|
+
this.gtag('event', 'page_view', { page_path: screenName });
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
this.logError(e, 'Failed to log page_view to Google Analytics');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
identify(userID, userPropertiesToSet, userPropertiesToSetOnce) {
|
|
91
|
+
try {
|
|
92
|
+
this.gtag('config', this.measurementId, { user_id: userID });
|
|
93
|
+
const props = { ...userPropertiesToSet, ...userPropertiesToSetOnce };
|
|
94
|
+
if (Object.keys(props).length) {
|
|
95
|
+
this.gtag('set', 'user_properties', props);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
this.logError(e, 'Failed to set user id in Google Analytics');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
loggedOut() {
|
|
103
|
+
try {
|
|
104
|
+
this.gtag('config', this.measurementId, { user_id: undefined });
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
this.logError(e, 'Failed to clear user id in Google Analytics');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=gtag-analytics.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gtag-analytics.service.js","sourceRoot":"","sources":["../../../../../../libs/logging/src/lib/analytics/gtag-analytics.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject } from '@angular/core';\nimport { NavigationEnd, Router } from '@angular/router';\nimport { filter } from 'rxjs';\nimport {\n ErrorLogger,\n IAnalyticsService,\n IErrorLogger,\n ILogErrorOptions,\n UserProperties,\n} from '@sneat/core';\n\nconst logErrOptions: ILogErrorOptions = { show: false, feedback: false };\n\n// EU/EEA + UK — analytics storage is denied by default here (until the visitor\n// consents), matching the geo-gated Consent Mode v2 posture of the marketing\n// landings (landings/src/components/GoogleAnalytics.astro). Elsewhere it is\n// granted by default. Because an app and its landing share one measurement ID\n// on the same domain, a consent choice made on the landing carries over here.\nconst CONSENT_GATED_REGIONS = [\n 'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR',\n 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'RO', 'SK',\n 'SI', 'ES', 'SE', 'IS', 'LI', 'NO', 'GB',\n];\n\ntype GtagFn = (...args: unknown[]) => void;\ntype DataLayerWindow = Window & { dataLayer?: unknown[] };\n\n/**\n * Google Analytics (GA4) backend for the Sneat AnalyticsService fan-out.\n *\n * Uses gtag.js directly (not @angular/fire/analytics) so it can report to the\n * app's own per-domain GA4 property — the same property/stream its marketing\n * landing already uses — instead of the shared Firebase-linked property. Every\n * hit carries `surface: 'app'` so in-app usage is separable from landing\n * traffic within the one shared stream while keeping a single client_id (so the\n * landing -> app funnel stays intact).\n *\n * Instantiated by provideSneatAnalytics() with the app's measurement ID.\n */\nexport class GtagAnalyticsService implements IAnalyticsService {\n private readonly errorLogger = inject<IErrorLogger>(ErrorLogger);\n private readonly router = inject(Router);\n private gtag: GtagFn = () => undefined;\n\n constructor(private readonly measurementId: string) {\n this.init();\n }\n\n private readonly logError = (e: unknown, m: string) =>\n this.errorLogger.logError(e, m, logErrOptions);\n\n private init(): void {\n try {\n const w = window as DataLayerWindow;\n const dataLayer = (w.dataLayer = w.dataLayer ?? []);\n this.gtag = (...args: unknown[]) => {\n dataLayer.push(args);\n };\n\n this.gtag('js', new Date());\n\n // Consent Mode v2: granted by default, denied in gated regions until the\n // visitor consents (region-scoped default wins for those countries).\n this.gtag('consent', 'default', { analytics_storage: 'granted' });\n this.gtag('consent', 'default', {\n analytics_storage: 'denied',\n region: CONSENT_GATED_REGIONS,\n });\n\n const script = document.createElement('script');\n script.async = true;\n script.src =\n 'https://www.googletagmanager.com/gtag/js?id=' + this.measurementId;\n document.head.appendChild(script);\n\n this.gtag('config', this.measurementId, {\n // SPA: we emit page_view ourselves on each NavigationEnd below.\n send_page_view: false,\n // Never use the data for ads (mirrors the landings' config).\n allow_google_signals: false,\n allow_ad_personalization_signals: false,\n });\n // Default parameter that tags every hit as in-app.\n this.gtag('set', { surface: 'app' });\n\n this.router.events\n .pipe(filter((e): e is NavigationEnd => e instanceof NavigationEnd))\n .subscribe((e) => this.setCurrentScreen(e.urlAfterRedirects));\n } catch (e) {\n this.logError(e, 'Failed to init Google Analytics (gtag)');\n }\n }\n\n public logEvent(\n eventName: string,\n eventParams?: Readonly<Record<string, unknown>>,\n ): void {\n try {\n this.gtag('event', eventName, eventParams);\n } catch (e) {\n this.logError(e, 'Failed to log event to Google Analytics');\n }\n }\n\n public setCurrentScreen(screenName: string): void {\n try {\n this.gtag('event', 'page_view', { page_path: screenName });\n } catch (e) {\n this.logError(e, 'Failed to log page_view to Google Analytics');\n }\n }\n\n public identify(\n userID: string,\n userPropertiesToSet?: UserProperties,\n userPropertiesToSetOnce?: UserProperties,\n ): void {\n try {\n this.gtag('config', this.measurementId, { user_id: userID });\n const props = { ...userPropertiesToSet, ...userPropertiesToSetOnce };\n if (Object.keys(props).length) {\n this.gtag('set', 'user_properties', props);\n }\n } catch (e) {\n this.logError(e, 'Failed to set user id in Google Analytics');\n }\n }\n\n public loggedOut(): void {\n try {\n this.gtag('config', this.measurementId, { user_id: undefined });\n } catch (e) {\n this.logError(e, 'Failed to clear user id in Google Analytics');\n }\n }\n}\n"]}
|
|
@@ -3,16 +3,19 @@ import { FirebaseApp } from '@angular/fire/app';
|
|
|
3
3
|
import { AnalyticsService, } from '@sneat/core';
|
|
4
4
|
import { ErrorLogger } from '@sneat/core';
|
|
5
5
|
import { FireAnalyticsService } from './fire-analytics.service';
|
|
6
|
+
import { GtagAnalyticsService } from './gtag-analytics.service';
|
|
6
7
|
import { MultiAnalyticsService } from './multi-analytics.service';
|
|
7
8
|
import { PosthogAnalyticsService } from './posthog-analytics.service';
|
|
8
9
|
function getAnalyticsConfig(environmentConfig) {
|
|
9
10
|
const useAnalytics = location.host === 'sneat.app' || location.protocol === 'https:';
|
|
10
11
|
const firebaseMeasurementId = environmentConfig.firebaseConfig?.measurementId;
|
|
12
|
+
const gaMeasurementId = environmentConfig.googleAnalytics?.measurementId;
|
|
11
13
|
return {
|
|
12
14
|
addPosthog: useAnalytics && !!environmentConfig.posthog?.token,
|
|
13
15
|
addFirebaseAnalytics: useAnalytics &&
|
|
14
16
|
!!firebaseMeasurementId &&
|
|
15
17
|
firebaseMeasurementId !== 'G-PROVIDE_IF_NEEDED',
|
|
18
|
+
googleAnalyticsMeasurementId: useAnalytics ? gaMeasurementId : undefined,
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
export function provideSneatAnalytics(environmentConfig) {
|
|
@@ -29,6 +32,9 @@ export function provideSneatAnalytics(environmentConfig) {
|
|
|
29
32
|
if (config?.addPosthog) {
|
|
30
33
|
as.push(new PosthogAnalyticsService());
|
|
31
34
|
}
|
|
35
|
+
if (config?.googleAnalyticsMeasurementId) {
|
|
36
|
+
as.push(new GtagAnalyticsService(config.googleAnalyticsMeasurementId));
|
|
37
|
+
}
|
|
32
38
|
if (config?.addFirebaseAnalytics) {
|
|
33
39
|
const analytics = fbApp && getAnalytics(fbApp); // Ideally we would want to get it from the DI
|
|
34
40
|
if (analytics) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provide-sneat-analytics.js","sourceRoot":"","sources":["../../../../../../libs/logging/src/lib/analytics/provide-sneat-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,gBAAgB,GAGjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"provide-sneat-analytics.js","sourceRoot":"","sources":["../../../../../../libs/logging/src/lib/analytics/provide-sneat-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,gBAAgB,GAGjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAgB,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAWtE,SAAS,kBAAkB,CACzB,iBAAqC;IAErC,MAAM,YAAY,GAChB,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAElE,MAAM,qBAAqB,GAAG,iBAAiB,CAAC,cAAc,EAAE,aAAa,CAAC;IAC9E,MAAM,eAAe,GAAG,iBAAiB,CAAC,eAAe,EAAE,aAAa,CAAC;IAEzE,OAAO;QACL,UAAU,EAAE,YAAY,IAAI,CAAC,CAAC,iBAAiB,CAAC,OAAO,EAAE,KAAK;QAC9D,oBAAoB,EAClB,YAAY;YACZ,CAAC,CAAC,qBAAqB;YACvB,qBAAqB,KAAK,qBAAqB;QACjD,4BAA4B,EAAE,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;KACzE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,iBAAqC;IAErC,OAAO;QACL,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE;YACJ,WAAW;YACX,WAAW;YACX,sGAAsG;SACvG;QACD,UAAU,EAAE,CAAC,WAAyB,EAAE,KAAkB,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;YACrD,MAAM,EAAE,GAAwB,EAAE,CAAC;YACnC,IAAI,MAAM,EAAE,UAAU,EAAE,CAAC;gBACvB,EAAE,CAAC,IAAI,CAAC,IAAI,uBAAuB,EAAE,CAAC,CAAC;YACzC,CAAC;YACD,IAAI,MAAM,EAAE,4BAA4B,EAAE,CAAC;gBACzC,EAAE,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAAC,CAAC;YACzE,CAAC;YACD,IAAI,MAAM,EAAE,oBAAoB,EAAE,CAAC;gBACjC,MAAM,SAAS,GAAc,KAAK,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,8CAA8C;gBACzG,IAAI,SAAS,EAAE,CAAC;oBACd,EAAE,CAAC,IAAI,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;oBACpC,6DAA6D;gBAC/D,CAAC;qBAAM,CAAC;oBACN,WAAW,CAAC,QAAQ,CAClB,oEAAoE,EACpE,SAAS,EACT,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CACjC,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,IAAI,qBAAqB,CAAC,EAAE,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import { Analytics, getAnalytics } from '@angular/fire/analytics';\nimport { FirebaseApp } from '@angular/fire/app';\nimport {\n AnalyticsService,\n IAnalyticsService,\n IEnvironmentConfig,\n} from '@sneat/core';\nimport { ErrorLogger, IErrorLogger } from '@sneat/core';\nimport { FireAnalyticsService } from './fire-analytics.service';\nimport { GtagAnalyticsService } from './gtag-analytics.service';\nimport { MultiAnalyticsService } from './multi-analytics.service';\nimport { PosthogAnalyticsService } from './posthog-analytics.service';\nimport { Provider } from '@angular/core';\n\nexport interface IAnalyticsConfig {\n addPosthog?: boolean;\n addFirebaseAnalytics?: boolean;\n // GA4 measurement ID for the app's own per-domain property (the same stream\n // its marketing landing uses). When set, a gtag-based GA4 backend is added.\n googleAnalyticsMeasurementId?: string;\n}\n\nfunction getAnalyticsConfig(\n environmentConfig: IEnvironmentConfig,\n): IAnalyticsConfig {\n const useAnalytics =\n location.host === 'sneat.app' || location.protocol === 'https:';\n\n const firebaseMeasurementId = environmentConfig.firebaseConfig?.measurementId;\n const gaMeasurementId = environmentConfig.googleAnalytics?.measurementId;\n\n return {\n addPosthog: useAnalytics && !!environmentConfig.posthog?.token,\n addFirebaseAnalytics:\n useAnalytics &&\n !!firebaseMeasurementId &&\n firebaseMeasurementId !== 'G-PROVIDE_IF_NEEDED',\n googleAnalyticsMeasurementId: useAnalytics ? gaMeasurementId : undefined,\n };\n}\n\nexport function provideSneatAnalytics(\n environmentConfig: IEnvironmentConfig,\n): Provider {\n return {\n provide: AnalyticsService,\n deps: [\n ErrorLogger,\n FirebaseApp,\n // [new Optional(), Analytics], // TODO: The received Analytics instance does not have app property :(\n ],\n useFactory: (errorLogger: IErrorLogger, fbApp: FirebaseApp) => {\n const config = getAnalyticsConfig(environmentConfig);\n const as: IAnalyticsService[] = [];\n if (config?.addPosthog) {\n as.push(new PosthogAnalyticsService());\n }\n if (config?.googleAnalyticsMeasurementId) {\n as.push(new GtagAnalyticsService(config.googleAnalyticsMeasurementId));\n }\n if (config?.addFirebaseAnalytics) {\n const analytics: Analytics = fbApp && getAnalytics(fbApp); // Ideally we would want to get it from the DI\n if (analytics) {\n as.push(new FireAnalyticsService());\n // as.push(new FireAnalyticsService(errorLogger, analytics));\n } else {\n errorLogger.logError(\n 'addFirebaseAnalytics==true, but Firebase Analytics is not provided',\n undefined,\n { show: false, feedback: false },\n );\n }\n }\n return new MultiAnalyticsService(as);\n },\n };\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IAnalyticsService, UserProperties } from '@sneat/core';
|
|
2
|
+
/**
|
|
3
|
+
* Google Analytics (GA4) backend for the Sneat AnalyticsService fan-out.
|
|
4
|
+
*
|
|
5
|
+
* Uses gtag.js directly (not @angular/fire/analytics) so it can report to the
|
|
6
|
+
* app's own per-domain GA4 property — the same property/stream its marketing
|
|
7
|
+
* landing already uses — instead of the shared Firebase-linked property. Every
|
|
8
|
+
* hit carries `surface: 'app'` so in-app usage is separable from landing
|
|
9
|
+
* traffic within the one shared stream while keeping a single client_id (so the
|
|
10
|
+
* landing -> app funnel stays intact).
|
|
11
|
+
*
|
|
12
|
+
* Instantiated by provideSneatAnalytics() with the app's measurement ID.
|
|
13
|
+
*/
|
|
14
|
+
export declare class GtagAnalyticsService implements IAnalyticsService {
|
|
15
|
+
private readonly measurementId;
|
|
16
|
+
private readonly errorLogger;
|
|
17
|
+
private readonly router;
|
|
18
|
+
private gtag;
|
|
19
|
+
constructor(measurementId: string);
|
|
20
|
+
private readonly logError;
|
|
21
|
+
private init;
|
|
22
|
+
logEvent(eventName: string, eventParams?: Readonly<Record<string, unknown>>): void;
|
|
23
|
+
setCurrentScreen(screenName: string): void;
|
|
24
|
+
identify(userID: string, userPropertiesToSet?: UserProperties, userPropertiesToSetOnce?: UserProperties): void;
|
|
25
|
+
loggedOut(): void;
|
|
26
|
+
}
|
|
@@ -3,5 +3,6 @@ import { Provider } from '@angular/core';
|
|
|
3
3
|
export interface IAnalyticsConfig {
|
|
4
4
|
addPosthog?: boolean;
|
|
5
5
|
addFirebaseAnalytics?: boolean;
|
|
6
|
+
googleAnalyticsMeasurementId?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare function provideSneatAnalytics(environmentConfig: IEnvironmentConfig): Provider;
|