@@ -10,7 +10,7 @@ import { isPlatformBrowser, isPlatformServer } from '@angular/common';
10
10
11
11
const FIREBASE_EVENT_ORIGIN_KEY = 'firebase_event_origin' ;
12
12
const FIREBASE_PREVIOUS_SCREEN_CLASS_KEY = 'firebase_previous_class' ;
13
- const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id' ;
13
+ const FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY = 'firebase_previous_id' ;
14
14
const FIREBASE_PREVIOUS_SCREEN_NAME_KEY = 'firebase_previous_screen' ;
15
15
const FIREBASE_SCREEN_CLASS_KEY = 'firebase_screen_class' ;
16
16
const FIREBASE_SCREEN_INSTANCE_ID_KEY = 'firebase_screen_id' ;
@@ -30,177 +30,184 @@ const SCREEN_INSTANCE_DELIMITER = '#';
30
30
const ANNOTATIONS = '__annotations__' ;
31
31
32
32
@Injectable ( {
33
- providedIn : 'any'
33
+ providedIn : 'any'
34
34
} )
35
35
export class ScreenTrackingService implements OnDestroy {
36
36
37
- private disposable : Subscription | undefined ;
38
-
39
- constructor (
40
- analytics : AngularFireAnalytics ,
41
- @Optional ( ) router : Router ,
42
- @Optional ( ) title : Title ,
43
- componentFactoryResolver : ComponentFactoryResolver ,
44
- @Inject ( PLATFORM_ID ) platformId : Object ,
45
- @Optional ( ) @Inject ( DEBUG_MODE ) debugModeEnabled : boolean | null ,
46
- zone : NgZone ,
47
- injector : Injector
48
- ) {
49
- if ( ! router || ! isPlatformBrowser ( platformId ) ) { return this ; }
50
- zone . runOutsideAngular ( ( ) => {
51
- const activationEndEvents = router . events . pipe ( filter < ActivationEnd > ( e => e instanceof ActivationEnd ) ) ;
52
- const navigationEndEvents = router . events . pipe ( filter < NavigationEnd > ( e => e instanceof NavigationEnd ) ) ;
53
- this . disposable = navigationEndEvents . pipe (
54
- withLatestFrom ( activationEndEvents ) ,
55
- switchMap ( ( [ navigationEnd , activationEnd ] ) => {
56
- // SEMVER: start using optional chains and nullish coalescing once we support newer typescript
57
- const page_path = navigationEnd . url ;
58
- const screen_name = activationEnd . snapshot . routeConfig && activationEnd . snapshot . routeConfig . path || page_path ;
59
- const params = {
60
- [ SCREEN_NAME_KEY ] : screen_name ,
61
- [ PAGE_PATH_KEY ] : page_path ,
62
- [ FIREBASE_EVENT_ORIGIN_KEY ] : EVENT_ORIGIN_AUTO ,
63
- [ FIREBASE_SCREEN_NAME_KEY ] : screen_name ,
64
- [ OUTLET_KEY ] : activationEnd . snapshot . outlet
65
- } ;
66
- if ( title ) {
67
- params [ PAGE_TITLE_KEY ] = title . getTitle ( ) ;
68
- }
69
- const component = activationEnd . snapshot . component ;
70
- const routeConfig = activationEnd . snapshot . routeConfig ;
71
- const loadChildren = routeConfig && routeConfig . loadChildren ;
72
- // TODO figure out how to handle minification
73
- if ( typeof loadChildren === 'string' ) {
74
- // SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng
75
- // TODO is it worth seeing if I can look up the component factory selector from the module name?
76
- // it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
77
- return of ( { ...params , [ SCREEN_CLASS_KEY ] : loadChildren . split ( '#' ) [ 1 ] } ) ;
78
- } else if ( typeof component === 'string' ) {
79
- return of ( { ...params , [ SCREEN_CLASS_KEY ] : component } ) ;
80
- } else if ( component ) {
81
- const componentFactory = componentFactoryResolver . resolveComponentFactory ( component ) ;
82
- return of ( { ...params , [ SCREEN_CLASS_KEY ] : componentFactory . selector } ) ;
83
- } else if ( loadChildren ) {
84
- const loadedChildren = loadChildren ( ) ;
85
- const loadedChildren$ : Observable < any > = ( loadedChildren instanceof Observable ) ? loadedChildren : from ( Promise . resolve ( loadedChildren ) ) ;
86
- return loadedChildren$ . pipe (
87
- map ( lazyModule => {
88
- if ( lazyModule instanceof NgModuleFactory ) {
89
- // AOT create an injector
90
- const moduleRef = lazyModule . create ( injector ) ;
91
- // INVESTIGATE is this the right way to get at the matching route?
92
- const routes = moduleRef . injector . get ( ROUTES ) ;
93
- const component = routes [ 0 ] [ 0 ] . component ; // should i just be grabbing 0-0 here?
94
- try {
95
- const componentFactory = moduleRef . componentFactoryResolver . resolveComponentFactory ( component ! ) ;
96
- return { ...params , [ SCREEN_CLASS_KEY ] : componentFactory . selector } ;
97
- } catch ( _ ) {
98
- return { ...params , [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ;
99
- }
100
- } else {
101
- // JIT look at the annotations
102
- // INVESTIGATE are there public APIs for this stuff?
103
- const declarations = [ ] . concat . apply ( [ ] , ( lazyModule [ ANNOTATIONS ] || [ ] ) . map ( ( f : any ) => f . declarations ) ) ;
104
- const selectors = [ ] . concat . apply ( [ ] , declarations . map ( ( c : any ) => ( c [ ANNOTATIONS ] || [ ] ) . map ( ( f : any ) => f . selector ) ) ) ;
105
- // should I just be grabbing the selector like this or should i match against the route component?
106
- // const routerModule = lazyModule.ngInjectorDef.imports.find(i => i.ngModule && ....);
107
- // const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0];
108
- return { ...params , [ SCREEN_CLASS_KEY ] : selectors [ 0 ] || DEFAULT_SCREEN_CLASS } ;
109
- }
110
- } )
111
- ) ;
112
- } else {
113
- return of ( { ...params , [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ) ;
114
- }
115
- } ) ,
116
- map ( params => ( {
117
- [ FIREBASE_SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ] ,
118
- [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] : getScreenInstanceID ( params ) ,
119
- ...params
120
- } ) ) ,
121
- tap ( params => {
122
- // TODO perhaps I can be smarter about this, bubble events up to the nearest outlet?
123
- if ( params [ OUTLET_KEY ] == NG_PRIMARY_OUTLET ) {
124
- analytics . setCurrentScreen ( params [ SCREEN_NAME_KEY ] ) ;
125
- analytics . updateConfig ( {
126
- [ PAGE_PATH_KEY ] : params [ PAGE_PATH_KEY ] ,
127
- [ SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ]
128
- } ) ;
129
- if ( title ) {
130
- analytics . updateConfig ( { [ PAGE_TITLE_KEY ] : params [ PAGE_TITLE_KEY ] } ) ;
131
- }
132
- }
133
- } ) ,
134
- groupBy ( params => params [ OUTLET_KEY ] ) ,
135
- mergeMap ( group => group . pipe ( startWith ( undefined ) , pairwise ( ) ) ) ,
136
- map ( ( [ prior , current ] ) => prior ? {
137
- [ FIREBASE_PREVIOUS_SCREEN_CLASS_KEY ] : prior [ SCREEN_CLASS_KEY ] ,
138
- [ FIREBASE_PREVIOUS_SCREEN_NAME_KEY ] : prior [ SCREEN_NAME_KEY ] ,
139
- [ FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY ] : prior [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] ,
140
- ...current !
141
- } : current ! ) ,
142
- tap ( params => debugModeEnabled && console . info ( SCREEN_VIEW_EVENT , params ) ) ,
143
- tap ( params => zone . runOutsideAngular ( ( ) => analytics . logEvent ( SCREEN_VIEW_EVENT , params ) ) )
144
- ) . subscribe ( ) ;
145
- } ) ;
37
+ private disposable : Subscription | undefined ;
38
+
39
+ constructor (
40
+ analytics : AngularFireAnalytics ,
41
+ @Optional ( ) router : Router ,
42
+ @Optional ( ) title : Title ,
43
+ componentFactoryResolver : ComponentFactoryResolver ,
44
+ // tslint:disable-next-line:ban-types
45
+ @Inject ( PLATFORM_ID ) platformId : Object ,
46
+ @Optional ( ) @Inject ( DEBUG_MODE ) debugModeEnabled : boolean | null ,
47
+ zone : NgZone ,
48
+ injector : Injector
49
+ ) {
50
+ if ( ! router || ! isPlatformBrowser ( platformId ) ) {
51
+ return this ;
146
52
}
147
-
148
- ngOnDestroy ( ) {
149
- if ( this . disposable ) { this . disposable . unsubscribe ( ) ; }
53
+ zone . runOutsideAngular ( ( ) => {
54
+ const activationEndEvents = router . events . pipe ( filter < ActivationEnd > ( e => e instanceof ActivationEnd ) ) ;
55
+ const navigationEndEvents = router . events . pipe ( filter < NavigationEnd > ( e => e instanceof NavigationEnd ) ) ;
56
+ this . disposable = navigationEndEvents . pipe (
57
+ withLatestFrom ( activationEndEvents ) ,
58
+ switchMap ( ( [ navigationEnd , activationEnd ] ) => {
59
+ // SEMVER: start using optional chains and nullish coalescing once we support newer typescript
60
+ const pagePath = navigationEnd . url ;
61
+ const screenName = activationEnd . snapshot . routeConfig && activationEnd . snapshot . routeConfig . path || pagePath ;
62
+ const params = {
63
+ [ SCREEN_NAME_KEY ] : screenName ,
64
+ [ PAGE_PATH_KEY ] : pagePath ,
65
+ [ FIREBASE_EVENT_ORIGIN_KEY ] : EVENT_ORIGIN_AUTO ,
66
+ [ FIREBASE_SCREEN_NAME_KEY ] : screenName ,
67
+ [ OUTLET_KEY ] : activationEnd . snapshot . outlet
68
+ } ;
69
+ if ( title ) {
70
+ params [ PAGE_TITLE_KEY ] = title . getTitle ( ) ;
71
+ }
72
+ const component = activationEnd . snapshot . component ;
73
+ const routeConfig = activationEnd . snapshot . routeConfig ;
74
+ const loadChildren = routeConfig && routeConfig . loadChildren ;
75
+ // TODO figure out how to handle minification
76
+ if ( typeof loadChildren === 'string' ) {
77
+ // SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng
78
+ // TODO is it worth seeing if I can look up the component factory selector from the module name?
79
+ // it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
80
+ return of ( { ...params , [ SCREEN_CLASS_KEY ] : loadChildren . split ( '#' ) [ 1 ] } ) ;
81
+ } else if ( typeof component === 'string' ) {
82
+ return of ( { ...params , [ SCREEN_CLASS_KEY ] : component } ) ;
83
+ } else if ( component ) {
84
+ const componentFactory = componentFactoryResolver . resolveComponentFactory ( component ) ;
85
+ return of ( { ...params , [ SCREEN_CLASS_KEY ] : componentFactory . selector } ) ;
86
+ } else if ( loadChildren ) {
87
+ const loadedChildren = loadChildren ( ) ;
88
+ const loadedChildren$ : Observable < any > = ( loadedChildren instanceof Observable ) ? loadedChildren : from ( Promise . resolve ( loadedChildren ) ) ;
89
+ return loadedChildren$ . pipe (
90
+ map ( lazyModule => {
91
+ if ( lazyModule instanceof NgModuleFactory ) {
92
+ // AOT create an injector
93
+ const moduleRef = lazyModule . create ( injector ) ;
94
+ // INVESTIGATE is this the right way to get at the matching route?
95
+ const routes = moduleRef . injector . get ( ROUTES ) ;
96
+ const component = routes [ 0 ] [ 0 ] . component ; // should i just be grabbing 0-0 here?
97
+ try {
98
+ const componentFactory = moduleRef . componentFactoryResolver . resolveComponentFactory ( component ! ) ;
99
+ return { ...params , [ SCREEN_CLASS_KEY ] : componentFactory . selector } ;
100
+ } catch ( _ ) {
101
+ return { ...params , [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ;
102
+ }
103
+ } else {
104
+ // JIT look at the annotations
105
+ // INVESTIGATE are there public APIs for this stuff?
106
+ const declarations = [ ] . concat . apply ( [ ] , ( lazyModule [ ANNOTATIONS ] || [ ] ) . map ( ( f : any ) => f . declarations ) ) ;
107
+ const selectors = [ ] . concat . apply ( [ ] , declarations . map ( ( c : any ) => ( c [ ANNOTATIONS ] || [ ] ) . map ( ( f : any ) => f . selector ) ) ) ;
108
+ // should I just be grabbing the selector like this or should i match against the route component?
109
+ // const routerModule = lazyModule.ngInjectorDef.imports.find(i => i.ngModule && ....);
110
+ // const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0];
111
+ return { ...params , [ SCREEN_CLASS_KEY ] : selectors [ 0 ] || DEFAULT_SCREEN_CLASS } ;
112
+ }
113
+ } )
114
+ ) ;
115
+ } else {
116
+ return of ( { ...params , [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ) ;
117
+ }
118
+ } ) ,
119
+ map ( params => ( {
120
+ [ FIREBASE_SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ] ,
121
+ [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] : getScreenInstanceID ( params ) ,
122
+ ...params
123
+ } ) ) ,
124
+ tap ( params => {
125
+ // TODO perhaps I can be smarter about this, bubble events up to the nearest outlet?
126
+ if ( params [ OUTLET_KEY ] === NG_PRIMARY_OUTLET ) {
127
+ analytics . setCurrentScreen ( params [ SCREEN_NAME_KEY ] ) ;
128
+ analytics . updateConfig ( {
129
+ [ PAGE_PATH_KEY ] : params [ PAGE_PATH_KEY ] ,
130
+ [ SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ]
131
+ } ) ;
132
+ if ( title ) {
133
+ analytics . updateConfig ( { [ PAGE_TITLE_KEY ] : params [ PAGE_TITLE_KEY ] } ) ;
134
+ }
135
+ }
136
+ } ) ,
137
+ groupBy ( params => params [ OUTLET_KEY ] ) ,
138
+ mergeMap ( group => group . pipe ( startWith ( undefined ) , pairwise ( ) ) ) ,
139
+ map ( ( [ prior , current ] ) => prior ? {
140
+ [ FIREBASE_PREVIOUS_SCREEN_CLASS_KEY ] : prior [ SCREEN_CLASS_KEY ] ,
141
+ [ FIREBASE_PREVIOUS_SCREEN_NAME_KEY ] : prior [ SCREEN_NAME_KEY ] ,
142
+ [ FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY ] : prior [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] ,
143
+ ...current !
144
+ } : current ! ) ,
145
+ tap ( params => debugModeEnabled && console . info ( SCREEN_VIEW_EVENT , params ) ) ,
146
+ tap ( params => zone . runOutsideAngular ( ( ) => analytics . logEvent ( SCREEN_VIEW_EVENT , params ) ) )
147
+ ) . subscribe ( ) ;
148
+ } ) ;
149
+ }
150
+
151
+ ngOnDestroy ( ) {
152
+ if ( this . disposable ) {
153
+ this . disposable . unsubscribe ( ) ;
150
154
}
155
+ }
151
156
152
157
}
153
158
154
159
@Injectable ( {
155
- providedIn : 'any'
160
+ providedIn : 'any'
156
161
} )
157
162
export class UserTrackingService implements OnDestroy {
158
163
159
- private disposable : Subscription | undefined ;
160
-
161
- // TODO a user properties injector
162
- constructor (
163
- analytics : AngularFireAnalytics ,
164
- zone : NgZone ,
165
- @Inject ( PLATFORM_ID ) platformId : Object
166
- ) {
167
- const schedulers = new ɵAngularFireSchedulers ( zone ) ;
168
-
169
- if ( ! isPlatformServer ( platformId ) ) {
170
- zone . runOutsideAngular ( ( ) => {
171
- // @ts -ignore zap the import in the UMD
172
- this . disposable = from ( import ( 'firebase/auth' ) ) . pipe (
173
- observeOn ( schedulers . outsideAngular ) ,
174
- switchMap ( ( ) => analytics . app ) ,
175
- map ( app => app . auth ( ) ) ,
176
- switchMap ( auth => new Observable < User | null > ( auth . onAuthStateChanged . bind ( auth ) ) ) ,
177
- switchMap ( user => analytics . setUserId ( user ? user . uid : null ! ) )
178
- ) . subscribe ( ) ;
179
- } ) ;
180
- }
164
+ private disposable : Subscription | undefined ;
165
+
166
+ // TODO a user properties injector
167
+ constructor (
168
+ analytics : AngularFireAnalytics ,
169
+ zone : NgZone ,
170
+ @Inject ( PLATFORM_ID ) platformId : Object
171
+ ) {
172
+ const schedulers = new ɵAngularFireSchedulers ( zone ) ;
173
+
174
+ if ( ! isPlatformServer ( platformId ) ) {
175
+ zone . runOutsideAngular ( ( ) => {
176
+ // @ts -ignore zap the import in the UMD
177
+ this . disposable = from ( import ( 'firebase/auth' ) ) . pipe (
178
+ observeOn ( schedulers . outsideAngular ) ,
179
+ switchMap ( ( ) => analytics . app ) ,
180
+ map ( app => app . auth ( ) ) ,
181
+ switchMap ( auth => new Observable < User | null > ( auth . onAuthStateChanged . bind ( auth ) ) ) ,
182
+ switchMap ( user => analytics . setUserId ( user ? user . uid : null ! ) )
183
+ ) . subscribe ( ) ;
184
+ } ) ;
181
185
}
186
+ }
182
187
183
- ngOnDestroy ( ) {
184
- if ( this . disposable ) { this . disposable . unsubscribe ( ) ; }
188
+ ngOnDestroy ( ) {
189
+ if ( this . disposable ) {
190
+ this . disposable . unsubscribe ( ) ;
185
191
}
192
+ }
186
193
}
187
194
188
195
// this is an INT64 in iOS/Android but use INT32 cause javascript
189
196
let nextScreenInstanceID = Math . floor ( Math . random ( ) * ( 2 ** 32 - 1 ) ) - 2 ** 31 ;
190
197
191
- const knownScreenInstanceIDs : { [ key : string ] : number } = { } ;
192
-
193
- const getScreenInstanceID = ( params : { [ key : string ] : any } ) => {
194
- // unique the screen class against the outlet name
195
- const screenInstanceKey = [
196
- params [ SCREEN_CLASS_KEY ] ,
197
- params [ OUTLET_KEY ]
198
- ] . join ( SCREEN_INSTANCE_DELIMITER ) ;
199
- if ( knownScreenInstanceIDs . hasOwnProperty ( screenInstanceKey ) ) {
200
- return knownScreenInstanceIDs [ screenInstanceKey ] ;
201
- } else {
202
- const ret = nextScreenInstanceID ++ ;
203
- knownScreenInstanceIDs [ screenInstanceKey ] = ret ;
204
- return ret ;
205
- }
198
+ const knownScreenInstanceIDs : { [ key : string ] : number } = { } ;
199
+
200
+ const getScreenInstanceID = ( params : { [ key : string ] : any } ) => {
201
+ // unique the screen class against the outlet name
202
+ const screenInstanceKey = [
203
+ params [ SCREEN_CLASS_KEY ] ,
204
+ params [ OUTLET_KEY ]
205
+ ] . join ( SCREEN_INSTANCE_DELIMITER ) ;
206
+ if ( knownScreenInstanceIDs . hasOwnProperty ( screenInstanceKey ) ) {
207
+ return knownScreenInstanceIDs [ screenInstanceKey ] ;
208
+ } else {
209
+ const ret = nextScreenInstanceID ++ ;
210
+ knownScreenInstanceIDs [ screenInstanceKey ] = ret ;
211
+ return ret ;
212
+ }
206
213
} ;
0 commit comments