) => snapshot.numChildren())
110 | }
111 |
112 | child(path: P): DataSnapshotObservable {
113 | return new DataSnapshotObservable(sub => {
114 | const subscription = map.call(this,
115 | (snapshot: DataSnapshot) => snapshot.child(path))
116 | .subscribe(sub)
117 |
118 | return () => subscription.unsubscribe()
119 | })
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/database.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { Observable } from 'rxjs'
3 | import 'rxjs'
4 | import { asyncEvents } from '../testing/jasmine'
5 | import { FirebaseApp } from './app'
6 | import { DataSnapshotObservable } from './data-snapshot-observable'
7 | import { FirebaseDatabase } from './database'
8 |
9 | interface DBSchema {
10 | parent: {
11 | child: string
12 | },
13 | addedChild?: string
14 | list: {
15 | [key: string]: {
16 | c: string
17 | }
18 | }
19 | }
20 |
21 | let dbFixture: DBSchema = {
22 | parent: {
23 | child: 'childValue'
24 | },
25 | list: {
26 | c: { c: 'c' },
27 | a: { c: 'a' },
28 | d: { c: 'd' },
29 | b: { c: 'b' },
30 | }
31 | }
32 |
33 | let firebaseApp: FirebaseApp
34 | let db: FirebaseDatabase
35 |
36 | describe('Service: FirebaseDatabase', () => {
37 |
38 | beforeAll(() => {
39 | firebaseApp = new FirebaseApp({ options: firebaseConfig })
40 | db = firebaseApp.database()
41 | });
42 |
43 | afterAll(asyncEvents(() => {
44 | firebaseApp.delete().subscribe()
45 | }))
46 |
47 | beforeEach(asyncEvents(() => {
48 | db.ref().setWithPriority(dbFixture, 42).subscribe()
49 | }))
50 |
51 | it('should set node', asyncEvents(() => {
52 | const ref = db.ref().child('addedChild')
53 |
54 | ref.set('bar')
55 | .mergeMapTo(ref.onceValue().val())
56 | .subscribe(val => {
57 | expect(val).toBe('bar')
58 | })
59 | }))
60 |
61 | it('should wrap callbacks to stay in current zone', asyncEvents(() => {
62 | const zone = Zone.current
63 | db.ref().child('parent')
64 | .onceValue()
65 | .subscribe(() => {
66 | expect(Zone.current).toBe(zone)
67 | })
68 | }))
69 |
70 | it('should update node', asyncEvents(() => {
71 | const ref = db.ref().child('parent')
72 | ref.update({ child: 'newValue' })
73 | .mergeMapTo(ref.onceValue().val())
74 | .subscribe(node => expect(node).toEqual({ child: 'newValue' }))
75 | }))
76 |
77 | it('push node', asyncEvents(() => {
78 | const parent = db.ref().child('list')
79 | const newNode = { c: 'c' }
80 |
81 | parent.push(newNode)
82 | .mergeMap(child => child.onceValue().val())
83 | .subscribe(node => expect(node).toEqual(newNode))
84 | }))
85 |
86 | it('listen to updates', asyncEvents(() => {
87 | const node = db.ref().child('parent').child('child')
88 |
89 | node.onValue().val().bufferCount(2).take(1).subscribe(values => {
90 | expect(values).toEqual(['a', 'b'])
91 | })
92 |
93 | Observable.concat(node.set('a'), node.set('b')).subscribe()
94 | }))
95 |
96 | it('orderByChild', asyncEvents(() => {
97 | const node = db.ref().child('list')
98 |
99 | node.orderByChild('c')
100 | .onceValue()
101 | .toValArray()
102 | .subscribe(children => expect(children).toEqual([
103 | { c: 'a' },
104 | { c: 'b' },
105 | { c: 'c' },
106 | { c: 'd' }
107 | ]))
108 | }))
109 |
110 | describe('DataSnapshotObservable', () => {
111 | let snapshotObsFixt: DataSnapshotObservable
112 |
113 | beforeEach(() => snapshotObsFixt = db.ref().onceValue())
114 |
115 | it('.values()', asyncEvents(() => {
116 | db.ref().child('parent').onceValue().values().subscribe(values => {
117 | expect(values).toEqual(['childValue'])
118 | })
119 | }))
120 |
121 | it('.keys()', asyncEvents(() => {
122 | snapshotObsFixt.keys().subscribe(keys => {
123 | expect(keys).toEqual(['list', 'parent'])
124 | })
125 | }))
126 |
127 | it('.list()', asyncEvents(() => {
128 | db.ref().child('parent').onceValue().list().subscribe(entries => {
129 | expect(entries).toEqual([{ key: 'child', val: 'childValue' }])
130 | })
131 | }))
132 |
133 | it('.entry()', asyncEvents(() => {
134 | snapshotObsFixt.entry().subscribe(entry => {
135 | expect(entry).toEqual({ key: null, val: dbFixture })
136 | })
137 | snapshotObsFixt.child('parent').entry().subscribe(entry => {
138 | expect(entry).toEqual({ key: 'parent', val: dbFixture.parent })
139 | })
140 | }))
141 |
142 | it('.key()', asyncEvents(() => {
143 | snapshotObsFixt.child('parent').key().subscribe(entry => {
144 | expect(entry).toEqual('parent')
145 | })
146 | }))
147 |
148 | it('.prevKey()', asyncEvents(() => {
149 | const initialKeys = Object.keys(dbFixture.list)
150 | db.ref().child('list').onChildAdded().prevKey().bufferCount(initialKeys.length).take(1)
151 | .subscribe(prevKey => expect(prevKey).toEqual([null, 'a', 'b', 'c']));
152 | }))
153 |
154 | it('.exportVal()', asyncEvents(() => {
155 | snapshotObsFixt.exportVal()
156 | .subscribe(val => expect(val).toEqual(Object.assign({ '.priority': 42 }, dbFixture)));
157 | }))
158 |
159 | it('.exists()', asyncEvents(() => {
160 | snapshotObsFixt.exists()
161 | .subscribe(exists => expect(exists).toBeTruthy());
162 | }))
163 |
164 | it('.getPriority()', asyncEvents(() => {
165 | snapshotObsFixt.getPriority()
166 | .subscribe(priority => expect(priority).toBe(42));
167 | }))
168 |
169 | it('.hasChildren()', asyncEvents(() => {
170 | snapshotObsFixt.hasChildren()
171 | .subscribe(hasChildren => expect(hasChildren).toBeTruthy());
172 | }))
173 |
174 | it('.hasChild()', asyncEvents(() => {
175 | snapshotObsFixt.hasChild('parent')
176 | .subscribe(hasChild => expect(hasChild).toBeTruthy());
177 | }))
178 |
179 | it('.numChildren()', asyncEvents(() => {
180 | snapshotObsFixt.numChildren()
181 | .subscribe(numChildren => expect(numChildren).toBe(Object.keys(dbFixture).length));
182 | }))
183 | })
184 |
185 | it('transaction', asyncEvents(() => {
186 | db.ref().child('parent').transaction(parent => {
187 | // TODO investigate why parent is null
188 | // expect(parent).toBe(dbFixture.parent)
189 | return { child: 'newValue' }
190 | }).switchMapTo(db.ref().child('parent').onceValue().val())
191 | .subscribe(parent => expect(parent.child).toBe('newValue'))
192 | }))
193 | })
194 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/database.ts:
--------------------------------------------------------------------------------
1 | import { database } from 'firebase'
2 | import { Observable } from 'rxjs/Observable'
3 | import { of } from 'rxjs/observable/of'
4 | import { map } from 'rxjs/operator/map'
5 | import { mapTo } from 'rxjs/operator/mapTo'
6 | import { Subscriber } from 'rxjs/Subscriber'
7 | import { FirebaseApp } from './app'
8 | import { DataSnapshotObservable, makeDataSnapshotObservable } from './data-snapshot-observable'
9 | import {
10 | DataSnapshot,
11 | EventType,
12 | NativeDatabaseRef,
13 | Priority,
14 | Query,
15 | TransactionResult
16 | } from './interfaces'
17 | import { NativeFirebaseDatabase } from './native-firebase'
18 |
19 | /**
20 | * Enum of event types.
21 | */
22 | export class Event {
23 | static Value: EventType = 'value'
24 | static ChildAdded: EventType = 'child_added'
25 | static ChildChanged: EventType = 'child_changed'
26 | static ChildRemoved: EventType = 'child_removed'
27 | static ChildMoved: EventType = 'child_moved'
28 | }
29 |
30 | export class FirebaseQuery {
31 | private query: Query
32 | protected wrappedRef: FirebaseDatabaseRef
33 |
34 | get ref(): FirebaseDatabaseRef {
35 | return this.wrappedRef
36 | }
37 |
38 | constructor(protected _ref: NativeDatabaseRef, protected app: FirebaseApp) {}
39 |
40 | orderByChild(child: keyof T[keyof T]): FirebaseQuery {
41 | this._call('orderByChild', child)
42 | return this
43 | }
44 |
45 | orderByKey(): FirebaseQuery {
46 | this._call('orderByKey')
47 | return this
48 | }
49 |
50 | orderByPriority(): FirebaseQuery {
51 | this._call('orderByPriority')
52 | return this
53 | }
54 |
55 | orderByValue(): FirebaseQuery {
56 | this._call('orderByValue')
57 | return this
58 | }
59 |
60 | startAt(value: number | string | boolean | null, key?: keyof T[keyof T]): FirebaseQuery {
61 | this._call('startAt', value, key)
62 | return this
63 | }
64 |
65 | endAt(value: number | string | boolean | null, key?: keyof T[keyof T]): FirebaseQuery {
66 | this._call('endAt', value, key)
67 | return this
68 | }
69 |
70 | equalTo(value: number | string | boolean | null, key?: keyof T[keyof T]): FirebaseQuery {
71 | this._call('equalTo', value, key)
72 | return this
73 | }
74 |
75 | limitToFirst(limit: number): FirebaseQuery {
76 | this._call('limitToFirst', limit)
77 | return this
78 | }
79 |
80 | limitToLast(limit: number): FirebaseQuery {
81 | this._call('limitToLast', limit)
82 | return this
83 | }
84 |
85 | once(event: 'value'): DataSnapshotObservable
86 | once(event: 'child_added'): DataSnapshotObservable
87 | once(event: 'child_changed'): DataSnapshotObservable
88 | once(event: 'child_moved'): DataSnapshotObservable
89 | once(event: 'child_removed'): DataSnapshotObservable
90 | once(event: EventType): DataSnapshotObservable {
91 | return makeDataSnapshotObservable(this._once(event))
92 | }
93 |
94 | onceValue(): DataSnapshotObservable {
95 | return this.once('value')
96 | }
97 |
98 | onceChildAdded(): DataSnapshotObservable {
99 | return this.once('child_added')
100 | }
101 |
102 | onceChildChanged(): DataSnapshotObservable {
103 | return this.once('child_changed')
104 | }
105 |
106 | onceChildMoved(): DataSnapshotObservable {
107 | return this.once('child_moved')
108 | }
109 |
110 | onceChildRemoved(): DataSnapshotObservable {
111 | return this.once('child_removed')
112 | }
113 |
114 | on(event: 'value'): DataSnapshotObservable
115 | on(event: 'child_added'): DataSnapshotObservable
116 | on(event: 'child_changed'): DataSnapshotObservable
117 | on(event: 'child_moved'): DataSnapshotObservable
118 | on(event: 'child_removed'): DataSnapshotObservable
119 | on(event: EventType): DataSnapshotObservable {
120 | return makeDataSnapshotObservable(this._on(event as any))
121 | }
122 |
123 | onValue(): DataSnapshotObservable {
124 | return this.on('value')
125 | }
126 |
127 | onChildAdded(): DataSnapshotObservable {
128 | return this.on('child_added')
129 | }
130 |
131 | onChildChanged(): DataSnapshotObservable {
132 | return this.on('child_changed')
133 | }
134 |
135 | onChildMoved(): DataSnapshotObservable {
136 | return this.on('child_moved')
137 | }
138 |
139 | onChildRemoved(): DataSnapshotObservable {
140 | return this.on('child_removed')
141 | }
142 |
143 | isEqual(query: FirebaseQuery): boolean {
144 | return this.getQueryOrRef().isEqual(query.getQueryOrRef())
145 | }
146 |
147 | private _once(event: string): Observable> {
148 | return map.call(
149 | this.app.zoneHelper.wrapPromise(() => this.getQueryOrRef().once(event)),
150 | (nativeSnapshot: any) => this.makeDataSnapshot(nativeSnapshot)
151 | )
152 | }
153 |
154 | private _on(event: string): Observable> {
155 | return this.app.zoneHelper.createObservable(sub => {
156 | const cb = this.getQueryOrRef().on(
157 | event, this.getEventHandler(sub),
158 | (err: any) => sub.error(err)
159 | )
160 |
161 | return () => this.getQueryOrRef().off(event, cb)
162 | })
163 | }
164 |
165 | protected makeDataSnapshot(snapshot: any, prevKey?: string) {
166 | if (typeof prevKey !== 'undefined') {
167 | snapshot.prevKey = prevKey
168 | }
169 | Object.defineProperty(snapshot, 'ref', {
170 | get: () => this
171 | })
172 | return snapshot
173 | }
174 |
175 | private getEventHandler(sub: Subscriber, complete?: boolean) {
176 | return (snapshot: any, prevKey: any) => {
177 | sub.next(this.makeDataSnapshot(snapshot, prevKey))
178 | if (complete) {
179 | sub.complete()
180 | }
181 | }
182 | }
183 |
184 | private getQueryOrRef() {
185 | if (this.query) {
186 | return this.query
187 | }
188 | return this._ref
189 | }
190 |
191 | private _call(fnName: string, ...args: any[]) {
192 | this.app.zoneHelper.runInFirebase(() => {
193 | if (this.query) {
194 | this.query = (this.query as any)[fnName](...args)
195 | }
196 | else {
197 | this.query = (this._ref as any)[fnName](...args)
198 | }
199 | })
200 | }
201 | }
202 |
203 | export class FirebaseDatabaseRef extends FirebaseQuery {
204 |
205 | get key(): string | null {
206 | return this._ref.key
207 | }
208 |
209 | constructor(public parent: FirebaseDatabaseRef | null,
210 | ref: NativeDatabaseRef,
211 | app: FirebaseApp) {
212 | super(ref, app)
213 |
214 | this.wrappedRef = this
215 | }
216 |
217 | child(path: P): FirebaseDatabaseRef {
218 | return new FirebaseDatabaseRef(this, this._ref.child(path), this.app)
219 | }
220 |
221 | set(value: T): Observable {
222 | return this.app.zoneHelper.wrapPromise(() => this._ref.set(value))
223 | }
224 |
225 | setPriority(priority: Priority): Observable {
226 | // There seems to be a bug with the typing for #setPriority(priority, onComplete): Promise
227 | // The firebase library, in every other case, declares the onComplete function optional since a
228 | // Promise is returned as well.
229 | return this.app.zoneHelper.wrapPromise(() => (this._ref as any).setPriority(priority))
230 | }
231 |
232 | setWithPriority(newVal: T, priority: Priority): Observable {
233 | return this.app.zoneHelper.wrapPromise(() => this._ref.setWithPriority(newVal, priority))
234 | }
235 |
236 | push(value?: P): Observable> {
237 | const pushRef = this._ref.push(value)
238 | const ref = new FirebaseDatabaseRef(this, pushRef, this.app)
239 |
240 | // Only if a value to push was given, use ref as promise, since otherwise
241 | // pushRef.then will be undefined
242 | if (value) {
243 | return mapTo.call(this.app.zoneHelper.wrapPromise>(() => pushRef), ref)
244 | }
245 | return of(ref)
246 | }
247 |
248 | update(value: T): Observable {
249 | return this.app.zoneHelper.wrapPromise(() => this._ref.update(value))
250 | }
251 |
252 | remove(): Observable {
253 | return this.app.zoneHelper.wrapPromise(() => this._ref.remove())
254 | }
255 |
256 | transaction(transactionHandler: (node: T | null) => T | null | never,
257 | applyLocally?: boolean): Observable> {
258 | if (Zone) {
259 | transactionHandler = this.app.zoneHelper.wrap(transactionHandler, 'firebaseRxJS.transaction')
260 | }
261 | return this.app.zoneHelper.wrapPromise>(
262 | () => new Promise((resolve, reject) => this._ref.transaction(
263 | transactionHandler,
264 | (err, committed, snapshot: any) => {
265 | return err ? reject(err) : resolve({
266 | committed,
267 | snapshot: this.makeDataSnapshot(snapshot)
268 | })
269 | },
270 | applyLocally
271 | ))
272 | )
273 | }
274 | }
275 |
276 | /**
277 | * A special object with information about the connection between client and server which can be
278 | * accessed by using `db.ref('.info')`.
279 | */
280 | export class InfoSchema {
281 | /**
282 | * Whether or not the client is connected to the server.
283 | */
284 | connected: boolean
285 | /**
286 | * The estimated offset of time in milliseconds between client and server.
287 | */
288 | serverTimeOffset: number
289 | }
290 |
291 | export class FirebaseDatabase {
292 |
293 | /**
294 | * A collection of special constants which can be used when writing data. Their values will be
295 | * substituted on the server with server generated values.
296 | * E.g {@link FirebaseDatabase.ServerValue.TIMESTAMP} will be substituted for the server time
297 | * when committing a write.
298 | */
299 | static ServerValue = database.ServerValue
300 |
301 | constructor(private db: NativeFirebaseDatabase, private app: FirebaseApp) { }
302 |
303 | /**
304 | * Get a {@link FirebaseDatabaseRef} to a location in the database.
305 | *
306 | * @howToUse
307 | * If you have defined a database schema you should use {@link FirebaseDatabase.ref} without
308 | * specifying a path in the database. At least not without giving a type parameter for the data
309 | * at that location. When using a schema you get the benefit of correct typing when using
310 | * {@link FirebaseDatabaseRef.child}. The TypeScript compiler can infer from the path segments
311 | * given to {@link FirebaseDatabaseRef.child} whether the path segment is valid at this
312 | * location in the database and what the type of the data is that will be returned when
313 | * fetching it.
314 | */
315 | ref(): FirebaseDatabaseRef
316 | ref(path: '.info'): FirebaseDatabaseRef
317 | ref(path: string): FirebaseDatabaseRef
318 | ref(path: string): FirebaseDatabaseRef
319 | ref(path?: string): FirebaseDatabaseRef {
320 | return new FirebaseDatabaseRef(null, this.db.ref(path), this.app)
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/interfaces.ts:
--------------------------------------------------------------------------------
1 | import * as firebase from 'firebase'
2 | import { database } from 'firebase'
3 | import { FirebaseDatabaseRef } from './database'
4 | import { FirebaseError } from './native-firebase'
5 | import AuthCredential = firebase.auth.AuthCredential
6 | import { auth } from 'firebase'
7 |
8 | /*
9 | * App Interfaces
10 | */
11 |
12 | export interface FirebaseAppConfig {
13 | /**
14 | * Name of the app internally used by firebase. If non is given one will be generated.
15 | */
16 | name?: string
17 | /**
18 | * Firebase App configuration.
19 | */
20 | options: {
21 | apiKey: string
22 | authDomain?: string
23 | databaseURL?: string
24 | storageBucket?: string
25 | messagingSenderId?: string
26 | }
27 | }
28 |
29 | export interface Extras {
30 | firebaseZone?: any
31 | }
32 |
33 | /*
34 | * Database Interfaces
35 | */
36 |
37 | export type Priority = number | string | null
38 |
39 | export interface PriorityField {
40 | '.priority'?: Priority // TODO define Priority type
41 | }
42 |
43 | export type ExportedSnapshot = {
44 | [P in keyof T]: ExportedSnapshot
45 | } & PriorityField
46 |
47 | // Implements firebase.database.DataSnapshot but with some changes which TypeScript can't express.
48 | export interface DataSnapshot {
49 | child(path: P): DataSnapshot;
50 | exists(): boolean;
51 | exportVal(): ExportedSnapshot;
52 | forEach(action: (a: DataSnapshot) => boolean): boolean;
53 | getPriority(): Priority;
54 | hasChild(path: keyof T): boolean;
55 | hasChildren(): boolean;
56 | key: string | null;
57 | numChildren(): number;
58 | ref: FirebaseDatabaseRef;
59 | toJSON(): T | null;
60 | val(): T | null;
61 |
62 | prevKey?: string;
63 | }
64 |
65 | export type NativeDatabaseRef = database.Reference
66 | export type Query = database.Query
67 |
68 | /**
69 | * Events which can be listened for.
70 | */
71 | export type EventType =
72 | 'value'
73 | | 'child_added'
74 | | 'child_changed'
75 | | 'child_removed'
76 | | 'child_moved'
77 |
78 | export interface TransactionResult {
79 | committed: boolean,
80 | snapshot: DataSnapshot | null
81 | }
82 |
83 | /*
84 | * Auth Interfaces
85 | */
86 |
87 | /**
88 | * General error codes which may occur with every operation.
89 | */
90 | export type AuthErrorCodeType =
91 | 'auth/app-deleted'
92 | | 'auth/app-not-authorized'
93 | | 'auth/argument-error'
94 | | 'auth/invalid-api-key'
95 | | 'auth/invalid-user-token'
96 | | 'auth/network-request-failed'
97 | | 'auth/operation-not-allowed'
98 | | 'auth/requires-recent-login'
99 | | 'auth/too-many-requests'
100 | | 'auth/unauthorized-domain'
101 | | 'auth/user-disabled'
102 | | 'auth/user-token-expired'
103 | | 'auth/web-storage-unsupported'
104 |
105 | export interface AuthError extends FirebaseError {
106 | code: AuthErrorCodeType | string
107 | email?: string
108 | credential?: AuthCredential
109 | }
110 |
111 | export interface ActionCodeError extends AuthError {
112 | code: AuthErrorCodeType
113 | | 'auth/expired-action-code'
114 | | 'auth/invalid-action-code'
115 | | 'auth/user-disabled'
116 | | 'auth/user-not-found'
117 | }
118 |
119 | /**
120 | * Error codes which can occur when calling {@link FirebaseAuth.confirmPasswordReset}
121 | */
122 | export interface ConfirmPasswordResetError extends AuthError {
123 | code: AuthErrorCodeType
124 | | 'auth/expired-action-code'
125 | | 'auth/invalid-action-code'
126 | | 'auth/user-disabled'
127 | | 'auth/user-not-found'
128 | | 'auth/weak-password'
129 | }
130 |
131 | /**
132 | * Error codes which can occur when calling {@link FirebaseAuth.createUserWithEmailAndPassword}
133 | */
134 | export interface CreateUserWithEmailAndPasswordError extends AuthError {
135 | code: AuthErrorCodeType
136 | | 'auth/email-already-in-use'
137 | | 'auth/invalid-email'
138 | | 'auth/operation-not-allowed'
139 | | 'auth/weak-password'
140 | }
141 |
142 | /**
143 | * Error codes which can occur when calling {@link FirebaseAuth.fetchProvidersForEmail}
144 | */
145 | export interface FetchProvidersForEmailError extends AuthError {
146 | code: AuthErrorCodeType
147 | | 'auth/invalid-email'
148 | }
149 |
150 | /**
151 | * Error codes which can occur when calling {@link FirebaseAuth.getRedirectResult}
152 | */
153 | export interface GetRedirectResultError extends AuthError {
154 | code: AuthErrorCodeType
155 | | 'auth/invalid-email'
156 | | 'auth/user-not-found'
157 | }
158 |
159 | /**
160 | * Error codes which can occur when calling {@link FirebaseAuth.sendPasswordResetEmail}
161 | */
162 | export interface SendPasswordResetEmailError extends AuthError {
163 | code: AuthErrorCodeType
164 | | 'auth/invalid-email'
165 | | 'auth/user-not-found'
166 | }
167 |
168 | /**
169 | * Error codes which can occur when calling {@link FirebaseAuth.signInAnonymously}
170 | */
171 | export interface SignInAnonymouslyError extends AuthError {
172 | code: AuthErrorCodeType
173 | | 'auth/operation-not-allowed'
174 | }
175 |
176 | /**
177 | * Error codes which can occur when calling {@link FirebaseAuth.signInWithCredential}
178 | */
179 | export interface SignInWithCredentialError extends AuthError {
180 | code: AuthErrorCodeType
181 | | 'auth/account-exists-with-different-credential'
182 | | 'auth/invalid-credential'
183 | | 'auth/operation-not-allowed'
184 | | 'auth/user-disabled'
185 | | 'auth/user-not-found'
186 | | 'auth/wrong-password'
187 | }
188 |
189 | /**
190 | * Error codes which can occur when calling {@link FirebaseAuth.signInWithCustomToken}
191 | */
192 | export interface SignInWithCustomTokenError extends AuthError {
193 | code: AuthErrorCodeType
194 | | 'auth/custom-token-mismatch'
195 | | 'auth/invalid-custom-token'
196 | }
197 |
198 | /**
199 | * Error codes which can occur when calling {@link FirebaseAuth.signInWithEmailAndPassword}
200 | */
201 | export interface SignInWithEmailAndPasswordError extends AuthError {
202 | code: AuthErrorCodeType
203 | | 'auth/invalid-email'
204 | | 'auth/user-disabled'
205 | | 'auth/user-not-found'
206 | | 'auth/wrong-password'
207 | }
208 |
209 | /**
210 | * Error codes which can occur when calling {@link FirebaseAuth.signInWithPopup}
211 | */
212 | export interface SignInWithPopupError extends AuthError {
213 | code: AuthErrorCodeType
214 | | 'auth/account-exists-with-different-credential'
215 | | 'auth/auth-domain-config-required'
216 | | 'auth/cancelled-popup-request'
217 | | 'auth/operation-not-allowed'
218 | | 'auth/operation-not-supported-in-this-environment'
219 | | 'auth/popup-blocked'
220 | | 'auth/popup-closed-by-user'
221 | | 'auth/unauthorized-domain'
222 | }
223 |
224 | /**
225 | * Error codes which can occur when calling {@link FirebaseAuth.signInWithRedirect}
226 | */
227 | export interface SignInWithRedirectError extends AuthError {
228 | code: AuthErrorCodeType
229 | | 'auth/auth-domain-config-required'
230 | | 'auth/operation-not-supported-in-this-environment'
231 | | 'auth/unauthorized-domain'
232 | }
233 |
234 | /**
235 | * Error codes which can occur when calling {@link FirebaseAuth.verifyPasswordResetCode}
236 | */
237 | export interface VerifyPasswordResetCodeError extends AuthError {
238 | code: AuthErrorCodeType
239 | | 'auth/expired-action-code'
240 | | 'auth/invalid-action-code'
241 | | 'auth/user-disabled'
242 | | 'auth/user-not-found'
243 | }
244 |
245 | export interface ActionCodeInfo {
246 | email: string
247 | }
248 |
249 | export type AuthProvider = auth.AuthProvider
250 | export type AuthCredential = auth.AuthCredential
251 |
252 | export class GoogleAuthProvider extends auth.GoogleAuthProvider {}
253 | export class FacebookAuthProvider extends auth.FacebookAuthProvider {}
254 | export class GithubAuthProvider extends auth.GithubAuthProvider {}
255 | export class EmailAuthProvider extends auth.EmailAuthProvider {}
256 | export class TwitterAuthProvider extends auth.TwitterAuthProvider {}
257 |
258 | /*
259 | * User Interfaces
260 | */
261 | export interface DeleteUserError extends FirebaseError {
262 | code: AuthErrorCodeType
263 | | 'auth/requires-recent-login'
264 | }
265 |
266 | export interface LinkUserError extends FirebaseError {
267 | code: AuthErrorCodeType
268 | | 'auth/provider-already-linked'
269 | | 'auth/invalid-credential'
270 | | 'auth/credential-already-in-use'
271 | | 'auth/email-already-in-use'
272 | | 'auth/operation-not-allowed'
273 | | 'auth/invalid-email'
274 | | 'auth/wrong-password'
275 | }
276 |
277 | export interface LinkUserWithPopupError extends FirebaseError {
278 | code: AuthErrorCodeType
279 | | 'auth/auth-domain-config-required'
280 | | 'auth/cancelled-popup-request'
281 | | 'auth/credential-already-in-use'
282 | | 'auth/email-already-in-use'
283 | | 'auth/operation-not-allowed'
284 | | 'auth/popup-blocked'
285 | | 'auth/operation-not-supported-in-this-environment'
286 | | 'auth/popup-closed-by-user'
287 | | 'auth/provider-already-linked'
288 | | 'auth/unauthorized-domain'
289 | }
290 |
291 | export interface LinkUserWithRedirectError extends FirebaseError {
292 | code: AuthErrorCodeType
293 | | 'auth/auth-domain-config-required'
294 | | 'auth/operation-not-supported-in-this-environment'
295 | | 'auth/provider-already-linked'
296 | | 'auth/unauthorized-domain'
297 | }
298 |
299 | export interface ReauthenticateError extends FirebaseError {
300 | code: AuthErrorCodeType
301 | | 'auth/user-mismatch'
302 | | 'auth/user-not-found'
303 | | 'auth/invalid-credential'
304 | | 'auth/invalid-email'
305 | | 'auth/wrong-password'
306 | }
307 |
308 | export interface UpdateEmailError extends FirebaseError {
309 | code: AuthErrorCodeType
310 | | 'auth/invalid-email'
311 | | 'auth/email-already-in-use'
312 | | 'auth/requires-recent-login'
313 | }
314 |
315 | export interface UpdatePasswordError extends FirebaseError {
316 | code: AuthErrorCodeType
317 | | 'auth/weak-password'
318 | | 'auth/requires-recent-login'
319 | }
320 |
321 | export type UserCredential = auth.UserCredential;
322 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/native-firebase.ts:
--------------------------------------------------------------------------------
1 | import { app, auth, database } from 'firebase'
2 |
3 | export abstract class NativeFirebaseApp implements app.App {
4 | name: string;
5 | options: Object;
6 |
7 | abstract auth(): firebase.auth.Auth;
8 |
9 | abstract database(): firebase.database.Database;
10 |
11 | abstract delete(): firebase.Promise;
12 |
13 | abstract storage(): firebase.storage.Storage;
14 |
15 | abstract messaging(): firebase.messaging.Messaging;
16 | }
17 |
18 | export abstract class NativeFirebaseAuth implements auth.Auth {
19 | app: firebase.app.App;
20 |
21 | abstract currentUser: firebase.User | null;
22 |
23 | abstract applyActionCode(code: string): firebase.Promise;
24 |
25 | abstract checkActionCode(code: string): firebase.Promise;
26 |
27 | abstract confirmPasswordReset(code: string, newPassword: string): firebase.Promise;
28 |
29 | abstract createCustomToken(uid: string, developerClaims?: Object | null): string;
30 |
31 | abstract createUserWithEmailAndPassword(email: string, password: string): firebase.Promise;
32 |
33 | abstract fetchProvidersForEmail(email: string): firebase.Promise;
34 |
35 | abstract getRedirectResult(): firebase.Promise;
36 |
37 | abstract onAuthStateChanged(nextOrObserver: Object, opt_error?: (a: firebase.auth.Error) => any,
38 | opt_completed?: () => any): () => any;
39 |
40 | abstract sendPasswordResetEmail(email: string): firebase.Promise;
41 |
42 | abstract signInAnonymously(): firebase.Promise;
43 |
44 | abstract signInWithCredential(credential: firebase.auth.AuthCredential): firebase.Promise;
45 |
46 | abstract signInWithCustomToken(token: string): firebase.Promise;
47 |
48 | abstract signInWithEmailAndPassword(email: string, password: string): firebase.Promise;
49 |
50 | abstract signInWithPopup(provider: firebase.auth.AuthProvider): firebase.Promise;
51 |
52 | abstract signInWithRedirect(provider: firebase.auth.AuthProvider): firebase.Promise;
53 |
54 | abstract signOut(): firebase.Promise;
55 |
56 | abstract verifyIdToken(idToken: string): firebase.Promise;
57 |
58 | abstract verifyPasswordResetCode(code: string): firebase.Promise;
59 | }
60 |
61 | export abstract class NativeFirebaseDatabase implements database.Database {
62 | app: firebase.app.App;
63 |
64 | abstract goOffline(): any;
65 |
66 | abstract goOnline(): any;
67 |
68 | abstract ref(path?: string): firebase.database.Reference;
69 |
70 | abstract refFromURL(url: string): firebase.database.Reference;
71 | }
72 |
73 | export interface FirebaseError extends Error {
74 | code: string
75 | message: string
76 | name: string
77 | stack: string
78 | }
79 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/user.spec.ts:
--------------------------------------------------------------------------------
1 | import { async } from '../testing/jasmine'
2 | import { FirebaseApp } from './app'
3 | import { FirebaseAuth } from './auth'
4 | import { FirebaseUser } from './user'
5 |
6 | describe('FirebaseUser', () => {
7 |
8 | describe('Password & Email', () => {
9 | const userCreds = { email: randomEmail(), password: 'password' };
10 | const profile = { displayName: 'Bob', photoURL: 'photo://url' }
11 | let app: FirebaseApp;
12 | let auth: FirebaseAuth;
13 | let user: FirebaseUser;
14 |
15 | it('should sign up', async(async () => {
16 | app = new FirebaseApp({ options: firebaseConfig });
17 | auth = app.auth();
18 |
19 | user = await auth.createUserWithEmailAndPassword(userCreds.email, userCreds.password)
20 | .toPromise()
21 | }));
22 |
23 | it('should update profile', async(async () => {
24 | await user.updateProfile(profile).toPromise();
25 | }));
26 |
27 | it('should forward User fields', () => {
28 | expect(user.isAnonymous).toBeFalsy();
29 | expect(user.email).toEqual(userCreds.email);
30 | expect(user.displayName).toEqual(profile.displayName);
31 | expect(user.photoURL).toEqual(profile.photoURL);
32 | expect(user.emailVerified).toBe(false);
33 | expect(user.providerId).toBe('firebase');
34 | expect(user.uid.length > 10).toBeTruthy();
35 | expect(user.refreshToken).toBeDefined();
36 | expect(user.providerData[0]!.uid).toEqual(user.email as string)
37 | })
38 |
39 | it('should get token', async(async () => {
40 | const token = await user.getToken(true).toPromise();
41 | expect(typeof token === 'string').toBeTruthy()
42 | }))
43 |
44 | // Only applicable for providers other than password.
45 | it('should reauthenticate', () => pending())
46 |
47 | it('should link', () => pending())
48 | it('should linkWithPopup', () => pending())
49 | it('should linkWithRedirect', () => pending())
50 | it('should unlink', () => pending())
51 |
52 | it('should reload user profile', async(async () => {
53 | await user.reload().toPromise()
54 | }))
55 |
56 | it('should send email verification', async(async () => {
57 | await user.sendEmailVerification().toPromise()
58 | }))
59 |
60 | it('should update email', async(async () => {
61 | userCreds.email = randomEmail();
62 | await user.updateEmail(userCreds.email).toPromise();
63 | expect(user.email).toBe(userCreds.email);
64 | }))
65 |
66 | it('should update password', async(async () => {
67 | userCreds.password = randomString();
68 | await user.updatePassword(userCreds.password).toPromise();
69 | user = await auth.signInWithEmailAndPassword(userCreds.email, userCreds.password).toPromise();
70 | expect(user).toBeDefined();
71 | }))
72 |
73 | it('should delete user', async(async () => {
74 | await user.delete().toPromise();
75 | }));
76 | });
77 | });
78 |
79 |
80 | function randomString() {
81 | return Math.random().toString(36).substring(7);
82 | }
83 |
84 | function randomEmail() {
85 | return `${randomString()}@yopmail.com`;
86 | }
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/user.ts:
--------------------------------------------------------------------------------
1 | import { auth, User, UserInfo } from 'firebase'
2 | import { Observable } from 'rxjs/Observable'
3 | import { map } from 'rxjs/operator/map'
4 | import { FirebaseApp } from './app'
5 | import { AuthCredential, UserCredential } from './interfaces'
6 |
7 |
8 | export class FirebaseUserCredential {
9 | credential?: AuthCredential;
10 | user?: FirebaseUser;
11 |
12 | constructor(cred: UserCredential, app: FirebaseApp) {
13 | this.credential = cred.credential || undefined;
14 | this.user = cred.user ? new FirebaseUser(cred.user, app) : undefined;
15 | }
16 | }
17 |
18 | export class FirebaseUser {
19 |
20 | get displayName(): string | null {
21 | return this.user.displayName
22 | }
23 |
24 | get email(): string | null {
25 | return this.user.email
26 | }
27 |
28 | get emailVerified(): boolean {
29 | return this.user.emailVerified;
30 | }
31 |
32 | get isAnonymous(): boolean {
33 | return this.user.isAnonymous;
34 | }
35 |
36 | get photoURL(): string | null {
37 | return this.user.photoURL;
38 | }
39 |
40 | get providerData(): (UserInfo | null)[] {
41 | return this.user.providerData;
42 | }
43 |
44 | get providerId(): string {
45 | return this.user.providerId;
46 | }
47 |
48 | get refreshToken(): string {
49 | return this.user.refreshToken;
50 | }
51 |
52 | get uid(): string {
53 | return this.user.uid
54 | }
55 |
56 | constructor(private user: User, private app: FirebaseApp) {}
57 |
58 | /**
59 | * @returns {Observable} - Returns {@link DeleteUserError} if operation fails.
60 | */
61 | delete(): Observable {
62 | return this.app.zoneHelper.wrapPromise(() => this.user.delete());
63 | }
64 |
65 | getToken(forceRefresh?: boolean): Observable {
66 | return this.app.zoneHelper.wrapPromise(() => this.user.getToken(forceRefresh));
67 | }
68 |
69 | /**
70 | * @returns {Observable} - Returns {@link LinkUserError} if operation fails.
71 | */
72 | link(credential: AuthCredential): Observable {
73 | return map.call(
74 | this.app.zoneHelper.wrapPromise(() => this.user.link(credential)),
75 | (user: User) => new FirebaseUser(user, this.app)
76 | );
77 | }
78 |
79 | /**
80 | * @returns {Observable} - Returns {@link LinkUserWithPopupError} if
81 | * operation fails.
82 | */
83 | linkWithPopup(provider: auth.AuthProvider): Observable {
84 | return map.call(
85 | this.app.zoneHelper.wrapPromise(() => this.user.linkWithPopup(provider)),
86 | (cred: UserCredential) => new FirebaseUserCredential(cred, this.app)
87 | );
88 | }
89 |
90 | /**
91 | * @returns {Observable} - Returns {@link LinkUserWithRedirectError} if
92 | * operation fails.
93 | */
94 | linkWithRedirect(provider: auth.AuthProvider): Observable {
95 | return map.call(
96 | this.app.zoneHelper.wrapPromise(() => this.user.linkWithRedirect(provider)),
97 | (cred: UserCredential) => new FirebaseUserCredential(cred, this.app)
98 | );
99 | }
100 |
101 | /**
102 | * @returns {Observable} - Returns {@link ReauthenticateError} if operation
103 | * fails.
104 | */
105 | reauthenticate(credential: AuthCredential): Observable {
106 | return this.app.zoneHelper.wrapPromise(() => this.user.reauthenticate(credential));
107 | }
108 |
109 | reload(): Observable {
110 | return this.app.zoneHelper.wrapPromise(() => this.user.reload());
111 | }
112 |
113 | sendEmailVerification(): Observable {
114 | return this.app.zoneHelper.wrapPromise(() => this.user.sendEmailVerification());
115 | }
116 |
117 | unlink(providerId: string): Observable {
118 | return map.call(
119 | this.app.zoneHelper.wrapPromise(() => this.user.unlink(providerId)),
120 | (user: User) => new FirebaseUser(user, this.app)
121 | );
122 | }
123 |
124 | /**
125 | * @returns {Observable} - Returns {@link UpdateEmailError} if operation
126 | * fails.
127 | */
128 | updateEmail(newEmail: string): Observable {
129 | return this.app.zoneHelper.wrapPromise(() => this.user.updateEmail(newEmail));
130 | }
131 |
132 | /**
133 | * @returns {Observable} - Returns {@link UpdatePasswordError} if operation
134 | * fails.
135 | */
136 | updatePassword(newPassword: string): Observable {
137 | return this.app.zoneHelper.wrapPromise(() => this.user.updatePassword(newPassword));
138 | }
139 |
140 | updateProfile(profile: { displayName?: string, photoURL?: string }): Observable {
141 | return this.app.zoneHelper.wrapPromise(() => this.user.updateProfile(profile as any));
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/zone-helper.spec.no-zone.ts:
--------------------------------------------------------------------------------
1 | import { ZoneHelper } from './zone-helper'
2 |
3 | describe('ZoneHelper: no zone.js', () => {
4 | let zoneHelper: ZoneHelper
5 |
6 | beforeEach(() => {
7 | zoneHelper = new ZoneHelper()
8 | })
9 |
10 | it('zone.js should not be loaded', () => {
11 | expect(typeof Zone).toBe('undefined')
12 | })
13 |
14 | it('should not setup firebaseZone', () => {
15 | expect((zoneHelper as any).firebaseZone).toBeUndefined()
16 | })
17 | })
--------------------------------------------------------------------------------
/packages/firebase-rxjs/src/zone-helper.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs/Observable'
2 | import { fromPromise } from 'rxjs/observable/fromPromise'
3 | import { Operator } from 'rxjs/Operator'
4 | import { Subscriber } from 'rxjs/Subscriber'
5 | import { TeardownLogic } from 'rxjs/Subscription'
6 |
7 | export class ZoneHelper {
8 |
9 | constructor(private firebaseZone?: Zone) {
10 | if (!firebaseZone) {
11 | if (typeof Zone !== 'undefined') {
12 | this.firebaseZone = Zone.root.fork({ name: 'firebase' })
13 | }
14 | }
15 | }
16 |
17 | wrap(callback: F, source: string): F {
18 | if (this.firebaseZone) {
19 | return this.firebaseZone.wrap(callback, source)
20 | }
21 | return callback
22 | }
23 |
24 | runInFirebase(fn: () => T): T {
25 | if (this.firebaseZone) {
26 | return this.firebaseZone.run(fn)
27 | }
28 | return fn()
29 | }
30 |
31 | wrapSubscribe(fn: T): T {
32 | if (this.firebaseZone) {
33 | return this.firebaseZone.wrap(fn, 'firebaseRxJS.Observable.subscribe')
34 | }
35 | return fn
36 | }
37 |
38 | wrapPromise(promiseFactory: () => firebase.Promise): Observable {
39 | if (typeof Zone === 'undefined') {
40 | return fromPromise(this.runInFirebase(promiseFactory) as Promise)
41 | }
42 |
43 | return new Observable(subscriber => {
44 | Zone.current.scheduleMacroTask('firebaseRxJS.Promise',
45 | (err: any, res: any) => {
46 | if (err) {
47 | subscriber.error(err)
48 | } else {
49 | subscriber.next(res)
50 | subscriber.complete()
51 | }
52 | }, {},
53 | (task: Task) => {
54 | const promise = this.runInFirebase(promiseFactory) as Promise
55 | promise.then(task.invoke.bind(task, null), task.invoke.bind(task))
56 | },
57 | (task: Task) => {}
58 | )
59 | })
60 | }
61 |
62 | createObservable(subscribe: (subscriber: Subscriber) => TeardownLogic): Observable {
63 | const obs = new Observable(this.wrapSubscribe(subscribe))
64 |
65 | if (typeof Zone === 'undefined') {
66 | return obs
67 | }
68 |
69 | return obs.lift(new EventTaskOperator())
70 | }
71 | }
72 |
73 | class EventTaskOperator implements Operator {
74 |
75 | call(subscriber: Subscriber, source: any): TeardownLogic {
76 | return source.subscribe(new EventTaskSubscriber(subscriber, Zone.current))
77 | }
78 | }
79 |
80 | class EventTaskSubscriber extends Subscriber {
81 | nextTask: EventTask
82 |
83 | constructor(destination: Subscriber, private zone: Zone) {
84 | super(destination)
85 |
86 | this.nextTask = this.zone.scheduleEventTask(
87 | 'firebaseRxJS.Observable.next',
88 | (val: T) => this.destination.next!(val),
89 | {},
90 | () => {},
91 | () => {},
92 | )
93 |
94 | this.add(() => this.zone.cancelTask(this.nextTask))
95 | }
96 |
97 | protected _next(value: T): void {
98 | const { nextTask } = this
99 | this.zone.run(nextTask.invoke, nextTask, [value]);
100 | }
101 |
102 | protected _error(err: any): void {
103 | const { destination } = this
104 | this.zone.scheduleMicroTask('firebaseRxJS.Observable.error',
105 | destination.error!.bind(destination, err))
106 | }
107 |
108 | protected _complete(): void {
109 | const { destination } = this
110 | this.zone.scheduleMicroTask('firebaseRxJS.Observable.complete',
111 | destination.complete!.bind(destination))
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/testing/jasmine.ts:
--------------------------------------------------------------------------------
1 | import { asyncTestWrapper } from './testing'
2 |
3 | export function async(test: () => void, waitForEventTasks = false): (done: () => void) => void {
4 | return function (done) {
5 | asyncTestWrapper('jasmine',
6 | test,
7 | {
8 | failure(err) {
9 | fail(err);
10 | done()
11 | },
12 | success: function () {
13 | done()
14 | }
15 | },
16 | waitForEventTasks)
17 | }
18 | }
19 |
20 | export function asyncEvents(test: () => void): (done: () => void) => void {
21 | return async(test, true)
22 | }
--------------------------------------------------------------------------------
/packages/firebase-rxjs/testing/log-spec.ts:
--------------------------------------------------------------------------------
1 |
2 | export class LogSpec implements ZoneSpec {
3 | indent = 0
4 | name = 'log-zone'
5 |
6 | log(msg: string) {
7 | console.log(`${' '.repeat(this.indent)}${msg}`)
8 | }
9 |
10 | onIntercept(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,
11 | source: string) {
12 | this.log(`[onIntercept] ${source}`)
13 | return parentZoneDelegate.intercept(targetZone, delegate, source)
14 | }
15 |
16 | onInvoke(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, delegate: Function,
17 | applyThis: any, applyArgs: any[], source: string) {
18 | this.log(`[onInvoke]:before ${targetZone.name} ${applyArgs}`)
19 | this.indent++
20 | const res = parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source)
21 | this.indent--
22 | this.log(`[onInvoke]:after ${targetZone.name}`)
23 | return res
24 | }
25 |
26 | onScheduleTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task) {
27 | this.log(`[onScheduleTask] ${task.source}`)
28 | return parentZoneDelegate.scheduleTask(targetZone, task)
29 | }
30 |
31 | onInvokeTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task,
32 | applyThis: any, applyArgs: any) {
33 | this.log(`[onInvokeTask]:before ${task.source} ${applyArgs}`)
34 | this.indent++
35 | const res = parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs)
36 | this.indent--
37 | this.log(`[onInvokeTask]:after ${task.source} ${applyArgs}`)
38 | return res
39 | }
40 |
41 | onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
42 | const { microTask, macroTask } = hasTaskState
43 | this.log(`[onHasTask] ${microTask ? 'microTask' : ''} ${macroTask ? 'macroTask' : ''}`)
44 | return delegate.hasTask(target, hasTaskState)
45 | }
46 | }
47 |
48 | export const logSpec = new LogSpec()
--------------------------------------------------------------------------------
/packages/firebase-rxjs/testing/testing.ts:
--------------------------------------------------------------------------------
1 | export interface TestFrameworkCallbacks {
2 | success(): void
3 | failure(err?: any): void
4 | }
5 |
6 | export function asyncTestWrapper(zoneNamePostFix: string,
7 | test: () => any,
8 | callbacks: TestFrameworkCallbacks,
9 | waitForEventTasks: boolean) {
10 | let startZone = Zone.current
11 |
12 | const longStackTraceSpec = (Zone as any)['longStackTraceZoneSpec']
13 | if (longStackTraceSpec) {
14 | startZone = startZone.fork(longStackTraceSpec)
15 | }
16 |
17 | startZone.fork(new AsyncTestZoneSpec(callbacks.success,
18 | callbacks.failure,
19 | zoneNamePostFix,
20 | waitForEventTasks))
21 | .run(() => {
22 | const res = test()
23 |
24 | if (typeof res === 'object' && typeof (res as any).then === 'function') {
25 | const promise = res as Promise
26 | promise.then(callbacks.success, callbacks.failure)
27 | }
28 | })
29 | }
30 |
31 | // Copied from zone.js/lib/zone-spec/async-test.ts and extended.
32 | class AsyncTestZoneSpec implements ZoneSpec {
33 | _finishCallback: Function;
34 | _failCallback: Function;
35 | _waitForEvents: boolean;
36 | _pendingMicroTasks: boolean = false;
37 | _pendingMacroTasks: boolean = false;
38 | _pendingEventTasks: boolean = false;
39 | _alreadyErrored: boolean = false;
40 | runZone = Zone.current;
41 |
42 | constructor(finishCallback: Function,
43 | failCallback: Function,
44 | namePrefix: string,
45 | waitForEvents: boolean) {
46 | this._finishCallback = finishCallback;
47 | this._failCallback = failCallback;
48 | this._waitForEvents = waitForEvents;
49 | this.name = 'asyncTestZone for ' + namePrefix;
50 | }
51 |
52 | _hasPendingTasks() {
53 | if (this._waitForEvents && this._pendingEventTasks) {
54 | return true
55 | }
56 | return this._pendingMicroTasks || this._pendingMacroTasks
57 | }
58 |
59 | _finishCallbackIfDone() {
60 | if (!this._hasPendingTasks()) {
61 | // We do this because we would like to catch unhandled rejected promises.
62 | this.runZone.run(() => {
63 | setTimeout(() => {
64 | if (!this._alreadyErrored && !this._hasPendingTasks()) {
65 | this._finishCallback();
66 | }
67 | }, 0);
68 | });
69 | }
70 | }
71 |
72 | // ZoneSpec implementation below.
73 |
74 | name: string;
75 |
76 | // Note - we need to use onInvoke at the moment to call finish when a test is
77 | // fully synchronous. TODO(juliemr): remove this when the logic for
78 | // onHasTask changes and it calls whenever the task queues are dirty.
79 | onInvoke(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
80 | delegate: Function,
81 | applyThis: any, applyArgs: any[], source: string): any {
82 | try {
83 | return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source);
84 | } finally {
85 | this._finishCallbackIfDone();
86 | }
87 | }
88 |
89 | onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
90 | error: any): boolean {
91 | // Let the parent try to handle the error.
92 | const result = parentZoneDelegate.handleError(targetZone, error);
93 | if (result) {
94 | this._failCallback(error);
95 | this._alreadyErrored = true;
96 | }
97 | return false;
98 | }
99 |
100 | onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) {
101 | delegate.hasTask(target, hasTaskState);
102 | if (hasTaskState.change == 'microTask') {
103 | this._pendingMicroTasks = hasTaskState.microTask;
104 | this._finishCallbackIfDone();
105 | } else if (hasTaskState.change == 'macroTask') {
106 | this._pendingMacroTasks = hasTaskState.macroTask;
107 | this._finishCallbackIfDone();
108 | } else if (hasTaskState.change == 'eventTask') {
109 | this._pendingEventTasks = hasTaskState.eventTask;
110 | this._finishCallbackIfDone();
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/packages/firebase-rxjs/tsconfig.dist.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "baseUrl": ".",
5 | "moduleResolution": "node",
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "outDir": "../../dist/packages/firebase-rxjs",
9 | "paths": {
10 | "rxjs/*": [
11 | "../../node_modules/rxjs/*"
12 | ],
13 | "firebase": [
14 | "../../node_modules/firebase"
15 | ]
16 | },
17 | "noImplicitAny": true,
18 | "strictNullChecks": true,
19 | "declaration": true,
20 | "stripInternal": true,
21 | "sourceMap": true,
22 | "inlineSources": true,
23 | "module": "es2015",
24 | "target": "es5",
25 | "lib": [
26 | "dom",
27 | "es2015",
28 | "es2015.collection",
29 | "es2015.promise"
30 | ],
31 | "types": []
32 | },
33 | "files": [
34 | "firebase-rxjs.ts",
35 | "../../node_modules/zone.js/dist/zone.js.d.ts"
36 | ],
37 | "angularCompilerOptions": {
38 | "strictMetadataEmit": true
39 | }
40 | }
--------------------------------------------------------------------------------
/packages/tests.no-zone.ts:
--------------------------------------------------------------------------------
1 | import 'core-js'
2 |
3 | declare var __karma__: any;
4 | declare var require: any;
5 |
6 | __karma__.loaded = function () {}
7 |
8 | const context = require.context('./', true, /\/.+\/src\/.+\.spec\.no-zone\.ts$/)
9 |
10 | context.keys().map(context)
11 |
12 | __karma__.start()
13 |
--------------------------------------------------------------------------------
/packages/tests.ts:
--------------------------------------------------------------------------------
1 | import 'core-js'
2 |
3 | import 'zone.js/dist/zone'
4 | import 'zone.js/dist/long-stack-trace-zone'
5 | import 'zone.js/dist/proxy'
6 | import 'zone.js/dist/fake-async-test'
7 | import 'zone.js/dist/sync-test'
8 | import 'zone.js/dist/async-test'
9 | import 'zone.js/dist/jasmine-patch'
10 |
11 | import { TestBed } from '@angular/core/testing'
12 | import {
13 | BrowserDynamicTestingModule,
14 | platformBrowserDynamicTesting
15 | } from '@angular/platform-browser-dynamic/testing'
16 |
17 | TestBed.initTestEnvironment(
18 | BrowserDynamicTestingModule,
19 | platformBrowserDynamicTesting()
20 | )
21 |
22 | declare var __karma__: any;
23 | declare var require: any;
24 |
25 | __karma__.loaded = function () {}
26 |
27 | const context = require.context('./', true, /\/.+\/src\/.+\.spec\.ts$/)
28 |
29 | context.keys().map(context)
30 |
31 | __karma__.start()
32 |
--------------------------------------------------------------------------------
/packages/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "traceResolution": true,
4 | "baseUrl": ".",
5 | "declaration": true,
6 | "experimentalDecorators": true,
7 | "emitDecoratorMetadata": true,
8 | "module": "commonjs",
9 | "moduleResolution": "node",
10 | "outDir": "../dist/all",
11 | "noImplicitAny": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "paths": {
14 | "rxjs/*": [
15 | "../node_modules/rxjs/*"
16 | ],
17 | "*": [
18 | "./*"
19 | ]
20 | },
21 | "rootDir": ".",
22 | "lib": [
23 | "es6",
24 | "dom"
25 | ],
26 | "skipDefaultLibCheck": true,
27 | "skipLibCheck": true,
28 | "target": "es5",
29 | "types": [
30 | "zone.js",
31 | "jasmine"
32 | ]
33 | }
34 | }
--------------------------------------------------------------------------------
/packages/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare const firebaseConfig: any
2 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 | ROOT="$( cd "${DIR}/.." && pwd )"
7 | BIN=${ROOT}/node_modules/.bin
8 | PKGS=${ROOT}/packages
9 | PKGS_DIST=${ROOT}/dist/packages
10 |
11 | buildJS () {
12 | echo "Building js for $1"
13 | ${BIN}/ngc -p ${PKGS}/$1/tsconfig.dist.json
14 | ${BIN}/cpy README.md CHANGELOG.md ${PKGS}/$1/package.json dist/packages/$1
15 | }
16 |
17 | bundle() {
18 | echo "Bundling $1"
19 | ${BIN}/rollup -c ${PKGS}/$1/rollup.config.js
20 | }
21 |
22 | uglify () {
23 | echo "Uglifying $1"
24 | ${BIN}/uglifyjs -c \
25 | -o ${PKGS_DIST}/$1/bundles/$1.umd.min.js \
26 | --source-map ${PKGS_DIST}/$1/bundles/$1.umd.min.map.js \
27 | ${PKGS_DIST}/$1/bundles/$1.umd.js
28 | }
29 |
30 | cleanDist () {
31 | echo "Cleaning dist folder"
32 | ${BIN}/rimraf ${PKGS_DIST}
33 | }
34 |
35 | cleanSrc () {
36 | echo "Cleaning src folders"
37 | ${BIN}/rimraf -rf "${PKGS}/*/node_modules"
38 | ${BIN}/rimraf -rf "${PKGS}/*/dist"
39 | ${BIN}/rimraf -rf "${PKGS}/**/*.{ngsummary.json,ngfactory.ts}"
40 | }
41 |
42 | buildPkg () {
43 | buildJS $1
44 | bundle $1
45 | uglify $1
46 | }
47 |
48 | build () {
49 | cleanDist
50 | buildPkg firebase-rxjs
51 | buildPkg firebase-rxjs-angular
52 | ${DIR}/version.js
53 | cleanSrc
54 | }
55 |
56 | docsPkg () {
57 | echo "Building docs for $1"
58 | local DOCS_DIR=${ROOT}/docs/$1
59 | rm -rf DOCS_DIR
60 | ${BIN}/typedoc --options typedoc.json -out ${DOCS_DIR} ${PKGS}/$1/$1.ts
61 | }
62 |
63 | docs () {
64 | docsPkg firebase-rxjs
65 | docsPkg firebase-rxjs-angular
66 | }
67 |
68 | "$@"
69 |
--------------------------------------------------------------------------------
/scripts/e2e-test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
6 |
7 | cd ${DIR}/../testing/ng-project
8 | yarn
9 | npm run e2e
10 | # Make sure aot and uglification work
11 | npm run ng -- build --prod --env dev
--------------------------------------------------------------------------------
/scripts/version.js:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/node
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const rootPkg = require('../package.json');
6 |
7 | const placeholder = /<--VERION_PLACEHOLDER-->/g;
8 |
9 | function replaceVersion(packageName) {
10 | const pkgPath = path.resolve('./dist/packages', packageName, 'package.json');
11 | const pkgStr = fs.readFileSync(pkgPath).toString();
12 | const pkgStrReplaced = pkgStr.replace(placeholder, rootPkg.version);
13 | fs.writeFileSync(pkgPath, pkgStrReplaced);
14 | }
15 |
16 | replaceVersion('firebase-rxjs');
17 | replaceVersion('firebase-rxjs-angular');
--------------------------------------------------------------------------------
/testing/karma.conf.common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 | const fs = require('fs');
4 | const webpack = require('webpack');
5 | const {TsConfigPathsPlugin} = require('awesome-typescript-loader');
6 |
7 | let firebaseConfig = process.env.FIREBASE_CONFIG ||
8 | fs.readFileSync('./firebase.config.local.json', {encoding: 'utf8'});
9 |
10 | if (firebaseConfig === '') {
11 | throw new Error('Failed to load Firebase config.')
12 | }
13 |
14 | const coverage = !!process.env['COVERAGE'];
15 | if (coverage) {
16 | console.log('Running tests with coverage!');
17 | }
18 |
19 | const reporters = ['mocha', 'kjhtml'];
20 |
21 | if (coverage) {
22 | reporters.push('karma-remap-istanbul')
23 | }
24 |
25 | const rules = [{
26 | test: /\.ts$/,
27 | loader: 'awesome-typescript-loader',
28 | exclude: /node_modules/,
29 | query: {
30 | configFileName: './packages/tsconfig.json'
31 | }
32 | }];
33 |
34 | if (coverage) {
35 | rules.push({
36 | enforce: 'post',
37 | test: /\.(ts|js)$/,
38 | loader: 'sourcemap-istanbul-instrumenter-loader',
39 | exclude: [
40 | /\.(spec|e2e|bundle)\.ts$/,
41 | /node_modules/
42 | ],
43 | query: {'force-sourcemap': true}
44 | })
45 | }
46 |
47 | exports.getKarmaConfig = function ({karma, testBundle}) {
48 | return {
49 | basePath: path.resolve(__dirname, '..'),
50 |
51 | mime: {
52 | 'application/javascript': ['ts']
53 | },
54 |
55 | frameworks: ['jasmine'],
56 |
57 | files: [{pattern: testBundle, watched: false}],
58 |
59 | preprocessors: {
60 | [testBundle]: ['webpack']
61 | },
62 |
63 | reporters,
64 |
65 | remapIstanbulReporter: {
66 | reports: {
67 | html: 'coverage',
68 | lcovonly: 'coverage/lcov.info'
69 | }
70 | },
71 |
72 | browsers: ['Chrome'],
73 | browserConsoleLogOptions: {
74 | level: ""
75 | },
76 | colors: true,
77 | autoWatch: true,
78 | singleRun: false,
79 | logLevel: karma.LOG_INFO,
80 |
81 | webpack: {
82 | devtool: 'inline-source-map',
83 |
84 | resolve: {
85 | extensions: ['.ts', '.js'],
86 | modules: [
87 | path.resolve(__dirname, '../node_modules'),
88 | path.resolve(__dirname, '../packages')
89 | ]
90 | },
91 |
92 | module: {
93 | rules,
94 | },
95 |
96 | plugins: [
97 | new TsConfigPathsPlugin({tsconfig: path.resolve(__dirname, '../packages/tsconfig.json')}),
98 | new webpack.DefinePlugin({
99 | firebaseConfig
100 | }),
101 | new webpack.SourceMapDevToolPlugin({
102 | filename: null, // if no value is provided the sourcemap is inlined
103 | test: /\.(ts|js)($|\?)/i // process .js and .ts files only
104 | })
105 | ]
106 | }
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/testing/karma.conf.no-zone.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const {getKarmaConfig} = require("./karma.conf.common");
3 |
4 | module.exports = function (karma) {
5 | karma.set(getKarmaConfig({testBundle: './packages/tests.no-zone.ts', karma}))
6 | };
7 |
--------------------------------------------------------------------------------
/testing/ng-project/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "ng-project"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "styles.css"
23 | ],
24 | "scripts": [],
25 | "environmentSource": "environments/environment.ts",
26 | "environments": {
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "e2e": {
33 | "protractor": {
34 | "config": "./protractor.conf.js"
35 | }
36 | },
37 | "lint": [
38 | {
39 | "project": "src/tsconfig.app.json"
40 | },
41 | {
42 | "project": "src/tsconfig.spec.json"
43 | },
44 | {
45 | "project": "e2e/tsconfig.e2e.json"
46 | }
47 | ],
48 | "test": {
49 | "karma": {
50 | "config": "./karma.conf.js"
51 | }
52 | },
53 | "defaults": {
54 | "styleExt": "css",
55 | "component": {}
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/testing/ng-project/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/testing/ng-project/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 |
36 | # e2e
37 | /e2e/*.js
38 | /e2e/*.map
39 |
40 | # System Files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/testing/ng-project/README.md:
--------------------------------------------------------------------------------
1 | # NgProject
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.4.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 | Before running the tests make sure you are serving the app via `ng serve`.
25 |
26 | ## Further help
27 |
28 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
29 |
--------------------------------------------------------------------------------
/testing/ng-project/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { browser } from 'protractor'
2 | import { NgProjectPage } from './app.po'
3 |
4 | describe('ng-project App', () => {
5 | let page: NgProjectPage;
6 |
7 | beforeEach(() => {
8 | page = new NgProjectPage();
9 | });
10 |
11 | it('should display message saying app works', () => {
12 | page.navigateTo();
13 | expect(page.getParagraphText()).toEqual('app works!');
14 | browser.sleep(2000)
15 | expect(page.getParagraphText()).toEqual('with Firebase!')
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/testing/ng-project/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class NgProjectPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/testing/ng-project/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types":[
8 | "jasmine",
9 | "node"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/testing/ng-project/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular/cli'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | files: [
19 | { pattern: './src/test.ts', watched: false }
20 | ],
21 | preprocessors: {
22 | './src/test.ts': ['@angular/cli']
23 | },
24 | mime: {
25 | 'text/x-typescript': ['ts','tsx']
26 | },
27 | coverageIstanbulReporter: {
28 | reports: [ 'html', 'lcovonly' ],
29 | fixWebpackSourcePaths: true
30 | },
31 | angularCli: {
32 | environment: 'dev'
33 | },
34 | reporters: config.angularCli && config.angularCli.codeCoverage
35 | ? ['progress', 'coverage-istanbul']
36 | : ['progress', 'kjhtml'],
37 | port: 9876,
38 | colors: true,
39 | logLevel: config.LOG_INFO,
40 | autoWatch: true,
41 | browsers: ['Chrome'],
42 | singleRun: false
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/testing/ng-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-project",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/common": ">=4.0.0-beta <5.0.0",
16 | "@angular/compiler": ">=4.0.0-beta <5.0.0",
17 | "@angular/core": ">=4.0.0-beta <5.0.0",
18 | "@angular/forms": ">=4.0.0-beta <5.0.0",
19 | "@angular/http": ">=4.0.0-beta <5.0.0",
20 | "@angular/platform-browser": ">=4.0.0-beta <5.0.0",
21 | "@angular/platform-browser-dynamic": ">=4.0.0-beta <5.0.0",
22 | "@angular/router": ">=4.0.0-beta <5.0.0",
23 | "core-js": "^2.4.1",
24 | "firebase-rxjs": "file:../../dist/packages/firebase-rxjs",
25 | "firebase-rxjs-angular": "file:../../dist/packages/firebase-rxjs-angular",
26 | "rxjs": "^5.1.0",
27 | "zone.js": "^0.8.4"
28 | },
29 | "devDependencies": {
30 | "@angular/cli": "^1.0.0",
31 | "@angular/compiler-cli": ">=4.0.0-beta <5.0.0",
32 | "@types/jasmine": "2.5.45",
33 | "@types/node": "^7.0.12",
34 | "codelyzer": "^3.0.0-beta.4",
35 | "jasmine-core": "~2.5.2",
36 | "jasmine-spec-reporter": "~3.2.0",
37 | "karma": "^1.5.0",
38 | "karma-chrome-launcher": "~2.0.0",
39 | "karma-cli": "~1.0.1",
40 | "karma-coverage-istanbul-reporter": "^1.0.0",
41 | "karma-jasmine": "~1.1.0",
42 | "karma-jasmine-html-reporter": "^0.2.2",
43 | "protractor": "~5.1.0",
44 | "ts-node": "^3.0.2",
45 | "tslint": "~4.5.0",
46 | "typescript": "^2.2.2"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/testing/ng-project/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | beforeLaunch: function() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | },
27 | onPrepare() {
28 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/testing/ng-project/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blaugold/firebase-rxjs/cc0a0a0e92c43f8deb1030b222f0ee23b0494d54/testing/ng-project/src/app/app.component.css
--------------------------------------------------------------------------------
/testing/ng-project/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{title | async}}
3 |
4 |
--------------------------------------------------------------------------------
/testing/ng-project/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 |
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async(() => {
7 | TestBed.configureTestingModule({
8 | declarations: [
9 | AppComponent
10 | ],
11 | }).compileComponents();
12 | }));
13 |
14 | it('should create the app', async(() => {
15 | const fixture = TestBed.createComponent(AppComponent);
16 | const app = fixture.debugElement.componentInstance;
17 | expect(app).toBeTruthy();
18 | }));
19 |
20 | it(`should have as title 'app works!'`, async(() => {
21 | const fixture = TestBed.createComponent(AppComponent);
22 | const app = fixture.debugElement.componentInstance;
23 | expect(app.title).toEqual('app works!');
24 | }));
25 |
26 | it('should render title in a h1 tag', async(() => {
27 | const fixture = TestBed.createComponent(AppComponent);
28 | fixture.detectChanges();
29 | const compiled = fixture.debugElement.nativeElement;
30 | expect(compiled.querySelector('h1').textContent).toContain('app works!');
31 | }));
32 | });
33 |
--------------------------------------------------------------------------------
/testing/ng-project/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { FirebaseDatabase } from 'firebase-rxjs'
3 | import { Observable } from 'rxjs'
4 |
5 | interface DBSchema {
6 | title: string
7 | }
8 |
9 | @Component({
10 | selector: 'app-root',
11 | templateUrl: './app.component.html',
12 | styleUrls: ['./app.component.css']
13 | })
14 | export class AppComponent implements OnInit {
15 | title: Observable;
16 |
17 | constructor(private db: FirebaseDatabase) {}
18 |
19 | ngOnInit(): void {
20 | const titleRef = this.db.ref().child('title')
21 |
22 | this.title = Observable.of('app works!')
23 | .merge(titleRef.set('with Firebase!')
24 | .switchMapTo(titleRef.onValue().val()))
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/testing/ng-project/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core'
2 | import { FormsModule } from '@angular/forms'
3 | import { HttpModule } from '@angular/http'
4 | import { BrowserModule } from '@angular/platform-browser'
5 | import { FirebaseRxJSModule } from 'firebase-rxjs-angular'
6 |
7 | import { AppComponent } from './app.component'
8 | import { environment } from '../environments/environment'
9 |
10 | @NgModule({
11 | declarations: [
12 | AppComponent
13 | ],
14 | imports: [
15 | BrowserModule,
16 | FormsModule,
17 | HttpModule,
18 | FirebaseRxJSModule.primaryApp({
19 | options: environment.firebase
20 | })
21 | ],
22 | providers: [],
23 | bootstrap: [AppComponent]
24 | })
25 | export class AppModule {
26 | }
27 |
--------------------------------------------------------------------------------
/testing/ng-project/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blaugold/firebase-rxjs/cc0a0a0e92c43f8deb1030b222f0ee23b0494d54/testing/ng-project/src/assets/.gitkeep
--------------------------------------------------------------------------------
/testing/ng-project/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/testing/ng-project/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | firebase: {
9 | "apiKey": "AIzaSyBd0enZJAJQOdbOSBfoYQUh67Lgc3ta550",
10 | "authDomain": "angular-firebase-tests-local.firebaseapp.com",
11 | "databaseURL": "https://angular-firebase-tests-local.firebaseio.com",
12 | "storageBucket": "angular-firebase-tests-local.appspot.com",
13 | "messagingSenderId": "452174369556"
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/testing/ng-project/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blaugold/firebase-rxjs/cc0a0a0e92c43f8deb1030b222f0ee23b0494d54/testing/ng-project/src/favicon.ico
--------------------------------------------------------------------------------
/testing/ng-project/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NgProject
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
14 |
15 |
--------------------------------------------------------------------------------
/testing/ng-project/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule);
12 |
--------------------------------------------------------------------------------
/testing/ng-project/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/set';
35 |
36 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
37 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
38 |
39 | /** IE10 and IE11 requires the following to support `@angular/animation`. */
40 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
41 |
42 |
43 | /** Evergreen browsers require these. **/
44 | import 'core-js/es6/reflect';
45 | import 'core-js/es7/reflect';
46 |
47 |
48 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/
49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
50 |
51 |
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular CLI.
57 |
58 |
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
64 | /**
65 | * Date, currency, decimal and percent pipes.
66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
67 | */
68 | // import 'intl'; // Run `npm install --save intl`.
69 |
--------------------------------------------------------------------------------
/testing/ng-project/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/testing/ng-project/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any;
17 | declare var require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/testing/ng-project/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "es2015",
6 | "baseUrl": "",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/testing/ng-project/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "baseUrl": "",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/testing/ng-project/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/testing/ng-project/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "baseUrl": "src",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "moduleResolution": "node",
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "target": "es5",
12 | "typeRoots": [
13 | "node_modules/@types"
14 | ],
15 | "lib": [
16 | "es2016",
17 | "dom"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/testing/ng-project/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "import-blacklist": [true, "rxjs"],
16 | "import-spacing": true,
17 | "indent": [
18 | true,
19 | "spaces"
20 | ],
21 | "interface-over-type-literal": true,
22 | "label-position": true,
23 | "max-line-length": [
24 | true,
25 | 140
26 | ],
27 | "member-access": false,
28 | "member-ordering": [
29 | true,
30 | "static-before-instance",
31 | "variables-before-functions"
32 | ],
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-construct": true,
44 | "no-debugger": true,
45 | "no-duplicate-variable": true,
46 | "no-empty": false,
47 | "no-empty-interface": true,
48 | "no-eval": true,
49 | "no-inferrable-types": [true, "ignore-params"],
50 | "no-shadowed-variable": true,
51 | "no-string-literal": false,
52 | "no-string-throw": true,
53 | "no-switch-case-fall-through": true,
54 | "no-trailing-whitespace": true,
55 | "no-unused-expression": true,
56 | "no-use-before-declare": true,
57 | "no-var-keyword": true,
58 | "object-literal-sort-keys": false,
59 | "one-line": [
60 | true,
61 | "check-open-brace",
62 | "check-catch",
63 | "check-else",
64 | "check-whitespace"
65 | ],
66 | "prefer-const": true,
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "radix": true,
72 | "semicolon": [
73 | "always"
74 | ],
75 | "triple-equals": [
76 | true,
77 | "allow-null-check"
78 | ],
79 | "typedef-whitespace": [
80 | true,
81 | {
82 | "call-signature": "nospace",
83 | "index-signature": "nospace",
84 | "parameter": "nospace",
85 | "property-declaration": "nospace",
86 | "variable-declaration": "nospace"
87 | }
88 | ],
89 | "typeof-compare": true,
90 | "unified-signatures": true,
91 | "variable-name": false,
92 | "whitespace": [
93 | true,
94 | "check-branch",
95 | "check-decl",
96 | "check-operator",
97 | "check-separator",
98 | "check-type"
99 | ],
100 |
101 | "directive-selector": [true, "attribute", "app", "camelCase"],
102 | "component-selector": [true, "element", "app", "kebab-case"],
103 | "use-input-property-decorator": true,
104 | "use-output-property-decorator": true,
105 | "use-host-property-decorator": true,
106 | "no-input-rename": true,
107 | "no-output-rename": true,
108 | "use-life-cycle-interface": true,
109 | "use-pipe-transform-interface": true,
110 | "component-class-suffix": true,
111 | "directive-class-suffix": true,
112 | "no-access-missing-member": true,
113 | "templates-use-public": true,
114 | "invoke-injectable": true
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/typedoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "mode": "file",
3 | "theme": "minimal",
4 | "ignoreCompilerErrors": true,
5 | "experimentalDecorators": true,
6 | "emitDecoratorMetadata": true,
7 | "target": "ES6",
8 | "moduleResolution": "node",
9 | "preserveConstEnums": true,
10 | "stripInternal": true,
11 | "suppressExcessPropertyErrors": true,
12 | "suppressImplicitAnyIndexErrors": true,
13 | "module": "ES6",
14 | "exclude": "**/*.spec.ts",
15 | "excludePrivate": true,
16 | "excludeExternals": false,
17 | "excludeNotExported": true,
18 | "pretty": true,
19 | "gaID": "UA-87346670-1",
20 | "lib": [
21 | "es6"
22 | ],
23 | "files": [
24 | "./node_modules/zone.js/dist/zone.js.d.ts"
25 | ]
26 | }
--------------------------------------------------------------------------------