Skip to content

Commit 1a43ad1

Browse files
committed
Merge branch 'firebase-v7' of github.com:angular/angularfire2 into firebase-v7
2 parents 62d90b9 + 916e069 commit 1a43ad1

File tree

1 file changed

+87
-44
lines changed

1 file changed

+87
-44
lines changed

src/analytics/analytics.service.ts

Lines changed: 87 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
import { Injectable, Inject, Optional, NgZone, OnDestroy, InjectionToken } from '@angular/core';
2-
import { Subscription, from, Observable, empty } from 'rxjs';
3-
import { filter, withLatestFrom, switchMap, map, tap } from 'rxjs/operators';
2+
import { Subscription, from, Observable, empty, of } from 'rxjs';
3+
import { filter, withLatestFrom, switchMap, map, tap, pairwise, startWith, groupBy, mergeMap } from 'rxjs/operators';
44
import { Router, NavigationEnd, ActivationEnd } from '@angular/router';
55
import { runOutsideAngular, _lazySDKProxy, _firebaseAppFactory } from '@angular/fire';
66
import { AngularFireAnalytics } from './analytics';
77
import { User } from 'firebase/app';
88

9-
export const AUTOMATICALLY_SET_CURRENT_SCREEN = new InjectionToken<boolean>('angularfire2.analytics.setCurrentScreen');
10-
export const AUTOMATICALLY_LOG_SCREEN_VIEWS = new InjectionToken<boolean>('angularfire2.analytics.logScreenViews');
119
export const APP_VERSION = new InjectionToken<string>('angularfire2.analytics.appVersion');
1210
export const APP_NAME = new InjectionToken<string>('angularfire2.analytics.appName');
1311

1412
const DEFAULT_APP_VERSION = '?';
1513
const DEFAULT_APP_NAME = 'Angular App';
1614

15+
type AngularFireAnalyticsEventParams = {
16+
app_name: string;
17+
firebase_screen_class: string | undefined;
18+
firebase_screen: string;
19+
app_version: string;
20+
screen_name: string;
21+
outlet: string;
22+
url: string;
23+
};
24+
1725
@Injectable({
1826
providedIn: 'root'
1927
})
@@ -24,49 +32,66 @@ export class ScreenTrackingService implements OnDestroy {
2432
constructor(
2533
analytics: AngularFireAnalytics,
2634
@Optional() router:Router,
27-
@Optional() @Inject(AUTOMATICALLY_SET_CURRENT_SCREEN) automaticallySetCurrentScreen:boolean|null,
28-
@Optional() @Inject(AUTOMATICALLY_LOG_SCREEN_VIEWS) automaticallyLogScreenViews:boolean|null,
2935
@Optional() @Inject(APP_VERSION) providedAppVersion:string|null,
3036
@Optional() @Inject(APP_NAME) providedAppName:string|null,
3137
zone: NgZone
3238
) {
33-
if (!router) {
34-
// TODO warning about Router
35-
} else if (automaticallySetCurrentScreen !== false || automaticallyLogScreenViews !== false) {
36-
const app_name = providedAppName || DEFAULT_APP_NAME;
37-
const app_version = providedAppVersion || DEFAULT_APP_VERSION;
38-
const activationEndEvents = router.events.pipe(filter<ActivationEnd>(e => e instanceof ActivationEnd));
39-
const navigationEndEvents = router.events.pipe(filter<NavigationEnd>(e => e instanceof NavigationEnd));
40-
this.disposable = navigationEndEvents.pipe(
41-
withLatestFrom(activationEndEvents),
42-
switchMap(([navigationEnd, activationEnd]) => {
43-
const url = navigationEnd.url;
44-
const screen_name = activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.path || url;
45-
const outlet = activationEnd.snapshot.outlet;
46-
const component = activationEnd.snapshot.component;
47-
const ret = new Array<Promise<void>>();
48-
if (automaticallyLogScreenViews !== false) {
49-
if (component) {
50-
const screen_class = component.hasOwnProperty('name') && (component as any).name || component.toString();
51-
ret.push(analytics.logEvent("screen_view", { app_name, screen_class, app_version, screen_name, outlet, url }));
52-
} else if (activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.loadChildren) {
53-
ret.push((activationEnd.snapshot.routeConfig.loadChildren as any)().then((child:any) => {
54-
const screen_class = child.name;
55-
console.log("logEvent", "screen_view", { app_name, screen_class, app_version, screen_name, outlet, url });
56-
return analytics.logEvent("screen_view", { app_name, screen_class, app_version, screen_name, outlet, url });
57-
}));
58-
} else {
59-
ret.push(analytics.logEvent("screen_view", { app_name, app_version, screen_name, outlet, url }));
60-
}
61-
}
62-
if (automaticallySetCurrentScreen !== false) {
63-
ret.push(analytics.setCurrentScreen(screen_name || url, { global: outlet == "primary" }));
64-
}
65-
return Promise.all(ret);
66-
}),
67-
runOutsideAngular(zone)
68-
).subscribe();
69-
}
39+
if (!router) { return this }
40+
const app_name = providedAppName || DEFAULT_APP_NAME;
41+
const app_version = providedAppVersion || DEFAULT_APP_VERSION;
42+
const activationEndEvents = router.events.pipe(filter<ActivationEnd>(e => e instanceof ActivationEnd));
43+
const navigationEndEvents = router.events.pipe(filter<NavigationEnd>(e => e instanceof NavigationEnd));
44+
this.disposable = navigationEndEvents.pipe(
45+
withLatestFrom(activationEndEvents),
46+
switchMap(([navigationEnd, activationEnd]) => {
47+
const url = navigationEnd.url;
48+
const screen_name = activationEnd.snapshot.routeConfig && activationEnd.snapshot.routeConfig.path || url;
49+
const params: AngularFireAnalyticsEventParams = {
50+
app_name, app_version, screen_name, url,
51+
firebase_screen_class: undefined,
52+
firebase_screen: screen_name,
53+
outlet: activationEnd.snapshot.outlet
54+
};
55+
const component = activationEnd.snapshot.component;
56+
const routeConfig = activationEnd.snapshot.routeConfig;
57+
const loadedConfig = routeConfig && (routeConfig as any)._loadedConfig;
58+
const loadChildren = routeConfig && routeConfig.loadChildren;
59+
if (component) {
60+
return of({...params, firebase_screen_class: nameOrToString(component) });
61+
} else if (loadedConfig && loadedConfig.module && loadedConfig.module._moduleType) {
62+
return of({...params, firebase_screen_class: nameOrToString(loadedConfig.module._moduleType)});
63+
} else if (typeof loadChildren === "string") {
64+
// TODO is this an older lazy loading style parse
65+
return of({...params, firebase_screen_class: loadChildren });
66+
} else if (loadChildren) {
67+
// TODO look into the other return types here
68+
return from(loadChildren() as Promise<any>).pipe(map(child => ({...params, firebase_screen_class: nameOrToString(child) })));
69+
} else {
70+
// TODO figure out what forms of router events I might be missing
71+
return of(params);
72+
}
73+
}),
74+
tap(params => {
75+
// TODO perhaps I can be smarter about this, bubble events up to the nearest outlet?
76+
if (params.outlet == "primary") {
77+
// TODO do I need to add gtag config for firebase_screen, firebase_screen_class, firebase_screen_id?
78+
// also shouldn't these be computed in the setCurrentScreen function? prior too?
79+
// do we want to be logging screen name or class?
80+
analytics.setCurrentScreen(params.screen_name, { global: true })
81+
}
82+
}),
83+
map(params => ({ firebase_screen_id: nextScreenId(params), ...params})),
84+
groupBy(params => params.outlet),
85+
mergeMap(group => group.pipe(startWith(undefined), pairwise())),
86+
map(([prior, current]) => prior ? {
87+
firebase_previous_class: prior.firebase_screen_class,
88+
firebase_previous_screen: prior.firebase_screen,
89+
firebase_previous_id: prior.firebase_screen_id,
90+
...current!
91+
} : current!),
92+
tap(params => analytics.logEvent('screen_view', params)),
93+
runOutsideAngular(zone)
94+
).subscribe();
7095
}
7196

7297
ngOnDestroy() {
@@ -99,4 +124,22 @@ export class UserTrackingService implements OnDestroy {
99124
ngOnDestroy() {
100125
if (this.disposable) { this.disposable.unsubscribe(); }
101126
}
102-
}
127+
}
128+
129+
// firebase_screen_id is an INT64 but use INT32 cause javascript
130+
const randomInt32 = () => Math.floor(Math.random() * (2**32 - 1)) - 2**31;
131+
132+
const currentScreenIds: {[key:string]: number} = {};
133+
134+
const nextScreenId = (params:AngularFireAnalyticsEventParams) => {
135+
const scope = params.outlet;
136+
if (currentScreenIds.hasOwnProperty(scope)) {
137+
return ++currentScreenIds[scope];
138+
} else {
139+
const ret = randomInt32();
140+
currentScreenIds[scope] = ret;
141+
return ret;
142+
}
143+
}
144+
145+
const nameOrToString = (it:any): string => it.name || it.toString();

0 commit comments

Comments
 (0)