@tangential/firebase-util
Version:
Utility classes and shared functionality for Tangential services that use Firebase.
208 lines (199 loc) • 7.07 kB
JavaScript
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