UNPKG

@tangential/firebase-util

Version:

Utility classes and shared functionality for Tangential services that use Firebase.

208 lines (199 loc) 7.07 kB
import { onValue, set, push, update, remove, getDatabase } from 'firebase/database'; import { BehaviorSubject } from 'rxjs'; import { filter, first } from 'rxjs/operators'; import * as i0 from '@angular/core'; import { Injectable } from '@angular/core'; import { initializeApp, getApp } from 'firebase/app'; const Placeholder = Symbol('placeholder'); const OnRefKeys = { value: 'value', child_added: 'child_added', child_removed: 'child_removed', child_changed: 'child_changed', child_moved: 'child_moved', }; /** * Copy-paste for local use, rather than create a dependency on core. */ const isObject = function (value) { return (typeof value === 'object' || value['constructor'] === Object); }; /** * Prevent typescript casting issues while maintaining/enhancing type safety. */ class FireBlanket { /** * Read the value once and return. * @param query */ static value(query) { return new Promise((resolve, reject) => { onValue(query, (snap) => { resolve(snap); }, { onlyOnce: true }); }); } static value$(query) { const subject = new BehaviorSubject(Placeholder); // this semicolon is required. /** @todo: ggranum: The unsubscribe is hacky, and won't actually remove the firebase * listener unless there is a 'next' element called * @maybeBug: Possible memory leak for long-running sessions with many value listeners * */ subject['_firebaseUnsubscribe'] = onValue(query, (snap) => { if (subject.closed) { subject['_firebaseUnsubscribe'](); delete subject['_firebaseUnsubscribe']; } subject.next(snap); }, (error) => { subject.error(error); }); return subject; } static awaitValue$(query) { return this.value$(query).pipe(filter(v => v !== Placeholder)); } static valueOnce$(query) { return this.value$(query).pipe(first(v => v !== Placeholder)); } static set(ref, value) { return new Promise((resolve, reject) => { try { set(ref, value).then(() => { }).catch((e) => { if (e) { reject(e); } else { resolve(); } }); } catch (e) { reject(e); } }); } static push(ref, value) { return new Promise((resolve, reject) => { try { push(ref, value).catch((e) => { if (e) { reject(e); } else { resolve(); } }); } catch (e) { reject(e); } }); } static update(ref, value) { return update(ref, value); } static remove(ref) { return remove(ref); } } FireBlanket.util = { clean(obj, deep = true) { const cleanObj = {}; Object.keys(obj).forEach((key) => { let value = obj[key]; if (FireBlanket.util.isLegalFirebaseKey(key) && FireBlanket.util.isLegalFirebaseValue(value)) { cleanObj[key] = (deep && isObject(value)) ? FireBlanket.util.clean(value) : value; } }); return cleanObj; }, removeIllegalKeys(obj) { const cleanObj = {}; Object.keys(obj).forEach((key) => { if (FireBlanket.util.isLegalFirebaseKey(key)) { cleanObj[key] = obj[key]; } }); return cleanObj; }, isLegalFirebaseKey(key) { return key !== null && key !== undefined && !key.startsWith('$'); }, isLegalFirebaseValue(value) { return value !== null && value !== undefined; }, isFirebaseGeneratedId(key) { let isKey = false; // starts with "-" will be true for over a decade. if (key && key.length === 20 && key.startsWith('-')) { isKey = true; } return isKey; } }; /** * @todo: Maybe instantiate this via the config object that we inject? * This can't be an interface because of inject, but we never new it up (it's treated as an interface basically) */ class FirebaseConfig { constructor() { this.apiKey = ''; this.authDomain = ''; this.databaseURL = ''; this.storageBucket = ''; } } FirebaseConfig.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseConfig, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); FirebaseConfig.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseConfig }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseConfig, decorators: [{ type: Injectable }] }); /** * With the new function route in Firebase 9, this is basically useless. Possibly worse than useless given the complications that * injecting multiple providers involve. */ class FirebaseProvider { constructor(config) { this.config = config; const name = 'defaultDb'; if (!config || !config.apiKey) { console.log('FirebaseProvider', 'constructor', config); throw new Error('FirebaseProvider requires an instance of FirebaseConfig to be set as a provider in your module.'); } try { this.app = initializeApp(config, name); } catch (e) { // re-init happens in unit tests. console.log("FirebaseProvider', 'Reinitializing firebase - if you're not running unit tests this is bad."); this.app = getApp(name); } } /** * refactoring stub so we can inline later, hopefully. */ getDatabase() { return getDatabase(this.app); } } FirebaseProvider.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseProvider, deps: [{ token: FirebaseConfig }], target: i0.ɵɵFactoryTarget.Injectable }); FirebaseProvider.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseProvider }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.10", ngImport: i0, type: FirebaseProvider, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: FirebaseConfig }]; } }); const FirebaseErrors = { invalidEmail: 'auth/invalid-email', userNotFound: 'auth/user-not-found', badPassword: 'auth/wrong-password', }; /* * Public API Surface of firebase-util */ /** * Generated bundle index. Do not edit. */ export { FireBlanket, FirebaseConfig, FirebaseErrors, FirebaseProvider, OnRefKeys, Placeholder }; //# sourceMappingURL=tangential-firebase-util.mjs.map