Skip to content

Commit e5be927

Browse files
committed
Firebase v7, analytics and remote-config
1 parent ca43c8b commit e5be927

27 files changed

+890
-158
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"@angular/platform-browser": ">=6.0.0 <9 || ^9.0.0-0",
4343
"@angular/platform-browser-dynamic": ">=6.0.0 <9 || ^9.0.0-0",
4444
"@angular/router": ">=6.0.0 <9 || ^9.0.0-0",
45-
"firebase": ">= 5.5.7 <7",
45+
"firebase": ">= 5.5.7 <8",
4646
"firebase-tools": "^6.10.0",
4747
"fuzzy": "^0.1.3",
4848
"inquirer": "^6.2.2",

src/analytics/analytics.module.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NgModule } from '@angular/core';
2+
import { AngularFireAnalytics } from './analytics';
3+
4+
@NgModule({
5+
providers: [ AngularFireAnalytics ]
6+
})
7+
export class AngularFireAnalyticsModule {
8+
constructor(_: AngularFireAnalytics) {
9+
// DI inject Analytics here for the automatic data collection
10+
}
11+
}

src/analytics/analytics.spec.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { User } from 'firebase/app';
2+
import { Observable, Subject } from 'rxjs'
3+
import { TestBed, inject } from '@angular/core/testing';
4+
import { FirebaseApp, FirebaseOptionsToken, AngularFireModule, FirebaseNameOrConfigToken } from '@angular/fire';
5+
import { AngularFireAuth, AngularFireAuthModule } from '@angular/fire/auth';
6+
import { COMMON_CONFIG } from './test-config';
7+
import { take, skip } from 'rxjs/operators';
8+
9+
function authTake(auth: Observable<any>, count: number): Observable<any> {
10+
return take.call(auth, 1);
11+
}
12+
13+
function authSkip(auth: Observable<any>, count: number): Observable<any> {
14+
return skip.call(auth, 1);
15+
}
16+
17+
const firebaseUser = <User> {
18+
uid: '12345',
19+
providerData: [{ displayName: 'jeffbcrossyface' }]
20+
};
21+
22+
describe('AngularFireAuth', () => {
23+
let app: FirebaseApp;
24+
let afAuth: AngularFireAuth;
25+
let authSpy: jasmine.Spy;
26+
let mockAuthState: Subject<User>;
27+
28+
beforeEach(() => {
29+
TestBed.configureTestingModule({
30+
imports: [
31+
AngularFireModule.initializeApp(COMMON_CONFIG),
32+
AngularFireAuthModule
33+
]
34+
});
35+
inject([FirebaseApp, AngularFireAuth], (app_: FirebaseApp, _auth: AngularFireAuth) => {
36+
app = app_;
37+
afAuth = _auth;
38+
})();
39+
40+
mockAuthState = new Subject<User>();
41+
spyOn(afAuth, 'authState').and.returnValue(mockAuthState);
42+
spyOn(afAuth, 'idToken').and.returnValue(mockAuthState);
43+
afAuth.authState = mockAuthState as Observable<User>;
44+
afAuth.idToken = mockAuthState as Observable<User>;
45+
});
46+
47+
afterEach(done => {
48+
afAuth.auth.app.delete().then(done, done.fail);
49+
});
50+
51+
describe('Zones', () => {
52+
it('should call operators and subscriber in the same zone as when service was initialized', (done) => {
53+
// Initialize the app outside of the zone, to mimick real life behavior.
54+
let ngZone = Zone.current.fork({
55+
name: 'ngZone'
56+
});
57+
ngZone.run(() => {
58+
const subs = [
59+
afAuth.authState.subscribe(user => {
60+
expect(Zone.current.name).toBe('ngZone');
61+
done();
62+
}, done.fail),
63+
afAuth.authState.subscribe(user => {
64+
expect(Zone.current.name).toBe('ngZone');
65+
done();
66+
}, done.fail)
67+
];
68+
mockAuthState.next(firebaseUser);
69+
subs.forEach(s => s.unsubscribe());
70+
});
71+
});
72+
});
73+
74+
it('should be exist', () => {
75+
expect(afAuth instanceof AngularFireAuth).toBe(true);
76+
});
77+
78+
it('should have the Firebase Auth instance', () => {
79+
expect(afAuth.auth).toBeDefined();
80+
});
81+
82+
it('should have an initialized Firebase app', () => {
83+
expect(afAuth.auth.app).toBeDefined();
84+
expect(afAuth.auth.app).toEqual(app);
85+
});
86+
87+
it('should emit auth updates through authState', (done: any) => {
88+
let count = 0;
89+
90+
// Check that the first value is null and second is the auth user
91+
const subs = afAuth.authState.subscribe(user => {
92+
if (count === 0) {
93+
expect(user).toBe(null!);
94+
count = count + 1;
95+
mockAuthState.next(firebaseUser);
96+
} else {
97+
expect(user).toEqual(firebaseUser);
98+
subs.unsubscribe();
99+
done();
100+
}
101+
}, done, done.fail);
102+
mockAuthState.next(null!);
103+
});
104+
105+
it('should emit auth updates through idToken', (done: any) => {
106+
let count = 0;
107+
108+
// Check that the first value is null and second is the auth user
109+
const subs = afAuth.idToken.subscribe(user => {
110+
if (count === 0) {
111+
expect(user).toBe(null!);
112+
count = count + 1;
113+
mockAuthState.next(firebaseUser);
114+
} else {
115+
expect(user).toEqual(firebaseUser);
116+
subs.unsubscribe();
117+
done();
118+
}
119+
}, done, done.fail);
120+
mockAuthState.next(null!);
121+
});
122+
123+
});
124+
125+
const FIREBASE_APP_NAME_TOO = (Math.random() + 1).toString(36).substring(7);
126+
127+
describe('AngularFireAuth with different app', () => {
128+
let app: FirebaseApp;
129+
let afAuth: AngularFireAuth;
130+
131+
beforeEach(() => {
132+
TestBed.configureTestingModule({
133+
imports: [
134+
AngularFireModule.initializeApp(COMMON_CONFIG),
135+
AngularFireAuthModule
136+
],
137+
providers: [
138+
{ provide: FirebaseNameOrConfigToken, useValue: FIREBASE_APP_NAME_TOO },
139+
{ provide: FirebaseOptionsToken, useValue: COMMON_CONFIG }
140+
]
141+
});
142+
inject([FirebaseApp, AngularFireAuth], (app_: FirebaseApp, _afAuth: AngularFireAuth) => {
143+
app = app_;
144+
afAuth = _afAuth;
145+
})();
146+
});
147+
148+
afterEach(done => {
149+
app.delete().then(done, done.fail);
150+
});
151+
152+
describe('<constructor>', () => {
153+
154+
it('should be an AngularFireAuth type', () => {
155+
expect(afAuth instanceof AngularFireAuth).toEqual(true);
156+
});
157+
158+
it('should have an initialized Firebase app', () => {
159+
expect(afAuth.auth.app).toBeDefined();
160+
expect(afAuth.auth.app).toEqual(app);
161+
});
162+
163+
it('should have an initialized Firebase app instance member', () => {
164+
expect(afAuth.auth.app.name).toEqual(FIREBASE_APP_NAME_TOO);
165+
});
166+
});
167+
168+
});

src/analytics/analytics.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Injectable, Inject, Optional, NgZone, InjectionToken } from '@angular/core';
2+
import { Observable, from, of } from 'rxjs';
3+
import { map, switchMap, tap, filter } from 'rxjs/operators';
4+
import { FirebaseAppConfig, FirebaseOptions, runOutsideAngular } from '@angular/fire';
5+
import { Router, NavigationEnd } from '@angular/router';
6+
import { FirebaseAnalytics, FirebaseOptionsToken, FirebaseNameOrConfigToken, _firebaseAppFactory } from '@angular/fire';
7+
8+
export const AUTOMATICALLY_SET_CURRENT_SCREEN = new InjectionToken<boolean>('angularfire2.analytics.setCurrentScreen');
9+
export const ANALYTICS_COLLECTION_ENABLED = new InjectionToken<boolean>('angularfire2.analytics.analyticsCollectionEnabled');
10+
export const AUTOMATICALLY_TRACK_USER_IDENTIFIER = new InjectionToken<boolean>('angularfire2.analytics.trackUserIdentifier');
11+
12+
@Injectable()
13+
export class AngularFireAnalytics {
14+
15+
/**
16+
* Firebase Analytics instance
17+
*/
18+
public readonly analytics: Observable<FirebaseAnalytics>;
19+
20+
constructor(
21+
@Inject(FirebaseOptionsToken) options:FirebaseOptions,
22+
@Optional() @Inject(FirebaseNameOrConfigToken) nameOrConfig:string|FirebaseAppConfig|null|undefined,
23+
@Optional() router:Router,
24+
@Optional() @Inject(AUTOMATICALLY_SET_CURRENT_SCREEN) automaticallySetCurrentScreen:boolean|null,
25+
@Optional() @Inject(ANALYTICS_COLLECTION_ENABLED) analyticsCollectionEnabled:boolean|null,
26+
@Optional() @Inject(AUTOMATICALLY_TRACK_USER_IDENTIFIER) automaticallyTrackUserIdentifier:boolean|null,
27+
private zone: NgZone
28+
) {
29+
// @ts-ignore zapping in the UMD in the build script
30+
const requireAnalytics = from(import('firebase/analytics'));
31+
32+
this.analytics = requireAnalytics.pipe(
33+
map(() => _firebaseAppFactory(options, nameOrConfig)),
34+
map(app => app.analytics()),
35+
tap(analytics => {
36+
if (analyticsCollectionEnabled == false) { analytics.setAnalyticsCollectionEnabled(false) }
37+
}),
38+
runOutsideAngular(zone)
39+
);
40+
41+
if (router && automaticallySetCurrentScreen !== false) {
42+
this.analytics.pipe(
43+
switchMap(analytics =>
44+
router.events.pipe(
45+
filter<NavigationEnd>(e => e instanceof NavigationEnd),
46+
tap(e => console.log(e)),
47+
tap(e => analytics.setCurrentScreen(e.url))
48+
)
49+
)
50+
).subscribe();
51+
}
52+
53+
if (automaticallyTrackUserIdentifier !== false) {
54+
this.analytics.pipe(
55+
switchMap(analytics => {
56+
if (analytics.app.auth) {
57+
const auth = analytics.app.auth();
58+
return new Observable<firebase.User|null>(auth.onAuthStateChanged.bind(auth)).pipe(
59+
tap(user => console.log("uid", user && user.uid)),
60+
tap(user => user ? analytics.setUserId(user.uid) : analytics.setUserId(null!))
61+
)
62+
} else {
63+
return of()
64+
}
65+
}),
66+
runOutsideAngular(zone)
67+
).subscribe()
68+
}
69+
70+
}
71+
72+
}

src/analytics/index.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './analytics.spec';

src/analytics/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './public_api';

src/analytics/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "@angular/fire/analytics",
3+
"main": "../bundles/analytics.umd.js",
4+
"module": "index.js",
5+
"es2015": "./es2015/index.js",
6+
"typings": "index.d.ts",
7+
"sideEffects": false
8+
}

src/analytics/public_api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './analytics';
2+
export * from './analytics.module';

src/analytics/test-config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
export const COMMON_CONFIG = {
3+
apiKey: "AIzaSyBVSy3YpkVGiKXbbxeK0qBnu3-MNZ9UIjA",
4+
authDomain: "angularfire2-test.firebaseapp.com",
5+
databaseURL: "https://angularfire2-test.firebaseio.com",
6+
storageBucket: "angularfire2-test.appspot.com",
7+
};

src/analytics/tsconfig-build.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"experimentalDecorators": true,
5+
"emitDecoratorMetadata": true,
6+
"module": "es2015",
7+
"target": "es2015",
8+
"noImplicitAny": false,
9+
"outDir": "../../dist/packages-dist/analytics/es2015",
10+
"rootDir": ".",
11+
"sourceMap": true,
12+
"inlineSources": true,
13+
"declaration": false,
14+
"removeComments": true,
15+
"strictNullChecks": true,
16+
"lib": ["es2015", "dom", "es2015.promise", "es2015.collection", "es2015.iterable"],
17+
"skipLibCheck": true,
18+
"moduleResolution": "node",
19+
"paths": {
20+
"@angular/fire": ["../../dist/packages-dist"]
21+
}
22+
},
23+
"files": [
24+
"index.ts",
25+
"../../node_modules/zone.js/dist/zone.js.d.ts"
26+
],
27+
"angularCompilerOptions": {
28+
"skipTemplateCodegen": true,
29+
"strictMetadataEmit": true,
30+
"enableSummariesForJit": false
31+
}
32+
}
33+

0 commit comments

Comments
 (0)