UNPKG

18.3 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { InjectionToken, PLATFORM_ID, Injectable, Inject, Optional, NgModule } from '@angular/core';
3import { asyncScheduler, Observable, of, merge } from 'rxjs';
4import { map, share, switchMap, scan, distinctUntilChanged, withLatestFrom, skipWhile } from 'rxjs/operators';
5import * as i1 from '@angular/fire';
6import { keepUnstableUntilFirst, VERSION } from '@angular/fire';
7import { ɵfirebaseAppFactory, ɵcacheInstance, FIREBASE_OPTIONS, FIREBASE_APP_NAME } from '@angular/fire/compat';
8import 'firebase/compat/auth';
9import 'firebase/compat/database';
10import * as i2 from '@angular/fire/compat/auth';
11import { ɵauthFactory, USE_EMULATOR as USE_EMULATOR$1, SETTINGS, TENANT_ID, LANGUAGE_CODE, USE_DEVICE_LANGUAGE, PERSISTENCE } from '@angular/fire/compat/auth';
12import * as i3 from '@angular/fire/app-check';
13import firebase from 'firebase/compat/app';
14
15function isString(value) {
16 return typeof value === 'string';
17}
18function isFirebaseDataSnapshot(value) {
19 return typeof value.exportVal === 'function';
20}
21function isNil(obj) {
22 return obj === undefined || obj === null;
23}
24function isFirebaseRef(value) {
25 return typeof value.set === 'function';
26}
27/**
28 * Returns a database reference given a Firebase App and an
29 * absolute or relative path.
30 * @param database - Firebase Database
31 * @param pathRef - Database path, relative or absolute
32 */
33function getRef(database, pathRef) {
34 // if a db ref was passed in, just return it
35 return isFirebaseRef(pathRef) ? pathRef
36 : database.ref(pathRef);
37}
38function checkOperationCases(item, cases) {
39 if (isString(item)) {
40 return cases.stringCase();
41 }
42 else if (isFirebaseRef(item)) {
43 return cases.firebaseCase();
44 }
45 else if (isFirebaseDataSnapshot(item)) {
46 return cases.snapshotCase();
47 }
48 throw new Error(`Expects a string, snapshot, or reference. Got: ${typeof item}`);
49}
50
51/**
52 * Create an observable from a Database Reference or Database Query.
53 * @param ref Database Reference
54 * @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved')
55 * @param listenType 'on' or 'once'
56 * @param scheduler - Rxjs scheduler
57 */
58function fromRef(ref, event, listenType = 'on', scheduler = asyncScheduler) {
59 return new Observable(subscriber => {
60 let fn = null;
61 fn = ref[listenType](event, (snapshot, prevKey) => {
62 scheduler.schedule(() => {
63 subscriber.next({ snapshot, prevKey });
64 });
65 if (listenType === 'once') {
66 scheduler.schedule(() => subscriber.complete());
67 }
68 }, err => {
69 scheduler.schedule(() => subscriber.error(err));
70 });
71 if (listenType === 'on') {
72 return {
73 unsubscribe() {
74 if (fn != null) {
75 ref.off(event, fn);
76 }
77 }
78 };
79 }
80 else {
81 return {
82 unsubscribe() {
83 }
84 };
85 }
86 }).pipe(map(payload => {
87 const { snapshot, prevKey } = payload;
88 let key = null;
89 if (snapshot.exists()) {
90 key = snapshot.key;
91 }
92 return { type: event, payload: snapshot, prevKey, key };
93 }), share());
94}
95
96function listChanges(ref, events, scheduler) {
97 return fromRef(ref, 'value', 'once', scheduler).pipe(switchMap(snapshotAction => {
98 const childEvent$ = [of(snapshotAction)];
99 events.forEach(event => childEvent$.push(fromRef(ref, event, 'on', scheduler)));
100 return merge(...childEvent$).pipe(scan(buildView, []));
101 }), distinctUntilChanged());
102}
103function positionFor(changes, key) {
104 const len = changes.length;
105 for (let i = 0; i < len; i++) {
106 if (changes[i].payload.key === key) {
107 return i;
108 }
109 }
110 return -1;
111}
112function positionAfter(changes, prevKey) {
113 if (isNil(prevKey)) {
114 return 0;
115 }
116 else {
117 const i = positionFor(changes, prevKey);
118 if (i === -1) {
119 return changes.length;
120 }
121 else {
122 return i + 1;
123 }
124 }
125}
126function buildView(current, action) {
127 const { payload, prevKey, key } = action;
128 const currentKeyPosition = positionFor(current, key);
129 const afterPreviousKeyPosition = positionAfter(current, prevKey);
130 switch (action.type) {
131 case 'value':
132 if (action.payload && action.payload.exists()) {
133 let prevKey = null;
134 action.payload.forEach(payload => {
135 const action = { payload, type: 'value', prevKey, key: payload.key };
136 prevKey = payload.key;
137 current = [...current, action];
138 return false;
139 });
140 }
141 return current;
142 case 'child_added':
143 if (currentKeyPosition > -1) {
144 // check that the previouskey is what we expect, else reorder
145 const previous = current[currentKeyPosition - 1];
146 if ((previous && previous.key || null) !== prevKey) {
147 current = current.filter(x => x.payload.key !== payload.key);
148 current.splice(afterPreviousKeyPosition, 0, action);
149 }
150 }
151 else if (prevKey == null) {
152 return [action, ...current];
153 }
154 else {
155 current = current.slice();
156 current.splice(afterPreviousKeyPosition, 0, action);
157 }
158 return current;
159 case 'child_removed':
160 return current.filter(x => x.payload.key !== payload.key);
161 case 'child_changed':
162 return current.map(x => x.payload.key === key ? action : x);
163 case 'child_moved':
164 if (currentKeyPosition > -1) {
165 const data = current.splice(currentKeyPosition, 1)[0];
166 current = current.slice();
167 current.splice(afterPreviousKeyPosition, 0, data);
168 return current;
169 }
170 return current;
171 // default will also remove null results
172 default:
173 return current;
174 }
175}
176
177function validateEventsArray(events) {
178 if (isNil(events) || events.length === 0) {
179 events = ['child_added', 'child_removed', 'child_changed', 'child_moved'];
180 }
181 return events;
182}
183
184function snapshotChanges(query, events, scheduler) {
185 events = validateEventsArray(events);
186 return listChanges(query, events, scheduler);
187}
188
189function stateChanges(query, events, scheduler) {
190 events = validateEventsArray(events);
191 const childEvent$ = events.map(event => fromRef(query, event, 'on', scheduler));
192 return merge(...childEvent$);
193}
194
195function auditTrail(query, events, scheduler) {
196 const auditTrail$ = stateChanges(query, events)
197 .pipe(scan((current, action) => [...current, action], []));
198 return waitForLoaded(query, auditTrail$, scheduler);
199}
200function loadedData(query, scheduler) {
201 // Create an observable of loaded values to retrieve the
202 // known dataset. This will allow us to know what key to
203 // emit the "whole" array at when listening for child events.
204 return fromRef(query, 'value', 'on', scheduler)
205 .pipe(map(data => {
206 // Store the last key in the data set
207 let lastKeyToLoad;
208 // Loop through loaded dataset to find the last key
209 data.payload.forEach(child => {
210 lastKeyToLoad = child.key;
211 return false;
212 });
213 // return data set and the current last key loaded
214 return { data, lastKeyToLoad };
215 }));
216}
217function waitForLoaded(query, action$, scheduler) {
218 const loaded$ = loadedData(query, scheduler);
219 return loaded$
220 .pipe(withLatestFrom(action$),
221 // Get the latest values from the "loaded" and "child" datasets
222 // We can use both datasets to form an array of the latest values.
223 map(([loaded, actions]) => {
224 // Store the last key in the data set
225 const lastKeyToLoad = loaded.lastKeyToLoad;
226 // Store all child keys loaded at this point
227 const loadedKeys = actions.map(snap => snap.key);
228 return { actions, lastKeyToLoad, loadedKeys };
229 }),
230 // This is the magical part, only emit when the last load key
231 // in the dataset has been loaded by a child event. At this point
232 // we can assume the dataset is "whole".
233 skipWhile(meta => meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1),
234 // Pluck off the meta data because the user only cares
235 // to iterate through the snapshots
236 map(meta => meta.actions));
237}
238
239function createDataOperationMethod(ref, operation) {
240 return function dataOperation(item, value) {
241 return checkOperationCases(item, {
242 stringCase: () => ref.child(item)[operation](value),
243 firebaseCase: () => item[operation](value),
244 snapshotCase: () => item.ref[operation](value)
245 });
246 };
247}
248
249// TODO(davideast): Find out why TS thinks this returns firebase.Primise
250// instead of Promise.
251function createRemoveMethod(ref) {
252 return function remove(item) {
253 if (!item) {
254 return ref.remove();
255 }
256 return checkOperationCases(item, {
257 stringCase: () => ref.child(item).remove(),
258 firebaseCase: () => item.remove(),
259 snapshotCase: () => item.ref.remove()
260 });
261 };
262}
263
264function createListReference(query, afDatabase) {
265 const outsideAngularScheduler = afDatabase.schedulers.outsideAngular;
266 const refInZone = afDatabase.schedulers.ngZone.run(() => query.ref);
267 return {
268 query,
269 update: createDataOperationMethod(refInZone, 'update'),
270 set: createDataOperationMethod(refInZone, 'set'),
271 push: (data) => refInZone.push(data),
272 remove: createRemoveMethod(refInZone),
273 snapshotChanges(events) {
274 return snapshotChanges(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
275 },
276 stateChanges(events) {
277 return stateChanges(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
278 },
279 auditTrail(events) {
280 return auditTrail(query, events, outsideAngularScheduler).pipe(keepUnstableUntilFirst);
281 },
282 valueChanges(events, options) {
283 const snapshotChanges$ = snapshotChanges(query, events, outsideAngularScheduler);
284 return snapshotChanges$.pipe(map(actions => actions.map(a => {
285 if (options && options.idField) {
286 return Object.assign(Object.assign({}, a.payload.val()), {
287 [options.idField]: a.key
288 });
289 }
290 else {
291 return a.payload.val();
292 }
293 })), keepUnstableUntilFirst);
294 }
295 };
296}
297
298function createObjectSnapshotChanges(query, scheduler) {
299 return function snapshotChanges() {
300 return fromRef(query, 'value', 'on', scheduler);
301 };
302}
303
304function createObjectReference(query, afDatabase) {
305 return {
306 query,
307 snapshotChanges() {
308 return createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)().pipe(keepUnstableUntilFirst);
309 },
310 update(data) { return query.ref.update(data); },
311 set(data) { return query.ref.set(data); },
312 remove() { return query.ref.remove(); },
313 valueChanges() {
314 const snapshotChanges$ = createObjectSnapshotChanges(query, afDatabase.schedulers.outsideAngular)();
315 return snapshotChanges$.pipe(keepUnstableUntilFirst, map(action => action.payload.exists() ? action.payload.val() : null));
316 },
317 };
318}
319
320const URL = new InjectionToken('angularfire2.realtimeDatabaseURL');
321const USE_EMULATOR = new InjectionToken('angularfire2.database.use-emulator');
322class AngularFireDatabase {
323 constructor(options, name, databaseURL,
324 // tslint:disable-next-line:ban-types
325 platformId, zone, schedulers, _useEmulator, // tuple isn't working here
326 auth, useAuthEmulator, authSettings, // can't use firebase.auth.AuthSettings here
327 tenantId, languageCode, useDeviceLanguage, persistence, _appCheckInstances) {
328 this.schedulers = schedulers;
329 const useEmulator = _useEmulator;
330 const app = ɵfirebaseAppFactory(options, zone, name);
331 if (auth) {
332 ɵauthFactory(app, zone, useAuthEmulator, tenantId, languageCode, useDeviceLanguage, authSettings, persistence);
333 }
334 this.database = ɵcacheInstance(`${app.name}.database.${databaseURL}`, 'AngularFireDatabase', app.name, () => {
335 const database = zone.runOutsideAngular(() => app.database(databaseURL || undefined));
336 if (useEmulator) {
337 database.useEmulator(...useEmulator);
338 }
339 return database;
340 }, [useEmulator]);
341 }
342 list(pathOrRef, queryFn) {
343 const ref = this.schedulers.ngZone.runOutsideAngular(() => getRef(this.database, pathOrRef));
344 let query = ref;
345 if (queryFn) {
346 query = queryFn(ref);
347 }
348 return createListReference(query, this);
349 }
350 object(pathOrRef) {
351 const ref = this.schedulers.ngZone.runOutsideAngular(() => getRef(this.database, pathOrRef));
352 return createObjectReference(ref, this);
353 }
354 createPushId() {
355 const ref = this.schedulers.ngZone.runOutsideAngular(() => this.database.ref());
356 return ref.push().key;
357 }
358}
359AngularFireDatabase.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, deps: [{ token: FIREBASE_OPTIONS }, { token: FIREBASE_APP_NAME, optional: true }, { token: URL, optional: true }, { token: PLATFORM_ID }, { token: i0.NgZone }, { token: i1.ɵAngularFireSchedulers }, { token: USE_EMULATOR, optional: true }, { token: i2.AngularFireAuth, optional: true }, { token: USE_EMULATOR$1, optional: true }, { token: SETTINGS, optional: true }, { token: TENANT_ID, optional: true }, { token: LANGUAGE_CODE, optional: true }, { token: USE_DEVICE_LANGUAGE, optional: true }, { token: PERSISTENCE, optional: true }, { token: i3.AppCheckInstances, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
360AngularFireDatabase.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, providedIn: 'any' });
361i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabase, decorators: [{
362 type: Injectable,
363 args: [{
364 providedIn: 'any'
365 }]
366 }], ctorParameters: function () { return [{ type: undefined, decorators: [{
367 type: Inject,
368 args: [FIREBASE_OPTIONS]
369 }] }, { type: undefined, decorators: [{
370 type: Optional
371 }, {
372 type: Inject,
373 args: [FIREBASE_APP_NAME]
374 }] }, { type: undefined, decorators: [{
375 type: Optional
376 }, {
377 type: Inject,
378 args: [URL]
379 }] }, { type: Object, decorators: [{
380 type: Inject,
381 args: [PLATFORM_ID]
382 }] }, { type: i0.NgZone }, { type: i1.ɵAngularFireSchedulers }, { type: undefined, decorators: [{
383 type: Optional
384 }, {
385 type: Inject,
386 args: [USE_EMULATOR]
387 }] }, { type: i2.AngularFireAuth, decorators: [{
388 type: Optional
389 }] }, { type: undefined, decorators: [{
390 type: Optional
391 }, {
392 type: Inject,
393 args: [USE_EMULATOR$1]
394 }] }, { type: undefined, decorators: [{
395 type: Optional
396 }, {
397 type: Inject,
398 args: [SETTINGS]
399 }] }, { type: undefined, decorators: [{
400 type: Optional
401 }, {
402 type: Inject,
403 args: [TENANT_ID]
404 }] }, { type: undefined, decorators: [{
405 type: Optional
406 }, {
407 type: Inject,
408 args: [LANGUAGE_CODE]
409 }] }, { type: undefined, decorators: [{
410 type: Optional
411 }, {
412 type: Inject,
413 args: [USE_DEVICE_LANGUAGE]
414 }] }, { type: undefined, decorators: [{
415 type: Optional
416 }, {
417 type: Inject,
418 args: [PERSISTENCE]
419 }] }, { type: i3.AppCheckInstances, decorators: [{
420 type: Optional
421 }] }]; } });
422
423class AngularFireDatabaseModule {
424 constructor() {
425 firebase.registerVersion('angularfire', VERSION.full, 'rtdb-compat');
426 }
427}
428AngularFireDatabaseModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
429AngularFireDatabaseModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule });
430AngularFireDatabaseModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, providers: [AngularFireDatabase] });
431i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.3", ngImport: i0, type: AngularFireDatabaseModule, decorators: [{
432 type: NgModule,
433 args: [{
434 providers: [AngularFireDatabase]
435 }]
436 }], ctorParameters: function () { return []; } });
437
438/**
439 * Generated bundle index. Do not edit.
440 */
441
442export { AngularFireDatabase, AngularFireDatabaseModule, URL, USE_EMULATOR, auditTrail, createListReference, fromRef, listChanges, snapshotChanges, stateChanges };
443//# sourceMappingURL=angular-fire-compat-database.js.map