UNPKG

@angular/fire

Version:

The official library for Firebase and Angular

503 lines (497 loc) 15.7 kB
import { InjectionToken, Injectable, Inject, Optional, NgZone, PLATFORM_ID, ɵɵdefineInjectable, ɵɵinject, NgModule } from '@angular/core'; import { pipe, of, EMPTY, concat, Observable } from 'rxjs'; import { map, distinctUntilChanged, filter, withLatestFrom, scan, observeOn, switchMap, tap, startWith, shareReplay, groupBy, mergeMap, debounceTime } from 'rxjs/operators'; import { ɵAngularFireSchedulers, ɵfirebaseAppFactory, ɵkeepUnstableUntilFirstFactory, ɵlazySDKProxy, FIREBASE_OPTIONS, FIREBASE_APP_NAME } from '@angular/fire'; import 'firebase/app'; import { isPlatformBrowser } from '@angular/common'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @record */ function ConfigTemplate() { } /** @type {?} */ const SETTINGS = new InjectionToken('angularfire2.remoteConfig.settings'); /** @type {?} */ const DEFAULTS = new InjectionToken('angularfire2.remoteConfig.defaultConfig'); // WARNING: interface has both a type and a value, skipping emit /** @type {?} */ const AS_TO_FN = { strings: 'asString', numbers: 'asNumber', booleans: 'asBoolean' }; /** @type {?} */ const STATIC_VALUES = { numbers: 0, booleans: false, strings: undefined }; // TODO look into the types here, I don't like the anys /** @type {?} */ const proxyAll = (/** * @param {?} observable * @param {?} as * @return {?} */ (observable, as) => (/** @type {?} */ (new Proxy(observable.pipe(mapToObject((/** @type {?} */ (as)))), { get: (/** * @param {?} self * @param {?} name * @return {?} */ (self, name) => self[name] || observable.pipe(map((/** * @param {?} all * @return {?} */ all => all.find((/** * @param {?} p * @return {?} */ p => p.key === name)))), map((/** * @param {?} param * @return {?} */ param => param ? param[AS_TO_FN[as]]() : STATIC_VALUES[as])), distinctUntilChanged())) })))); const ɵ0 = proxyAll; // TODO export as implements Partial<...> so minor doesn't break us class Value { // tslint:disable-next-line:variable-name /** * @param {?} _source * @param {?} _value */ constructor(_source, _value) { this._source = _source; this._value = _value; } /** * @return {?} */ asBoolean() { return ['1', 'true', 't', 'y', 'yes', 'on'].indexOf(this._value.toLowerCase()) > -1; } /** * @return {?} */ asString() { return this._value; } /** * @return {?} */ asNumber() { return Number(this._value) || 0; } /** * @return {?} */ getSource() { return this._source; } } if (false) { /** @type {?} */ Value.prototype._source; /** @type {?} */ Value.prototype._value; } // SEMVER use ConstructorParameters when we can support Typescript 3.6 class Parameter extends Value { /** * @param {?} key * @param {?} fetchTimeMillis * @param {?} source * @param {?} value */ constructor(key, fetchTimeMillis, source, value) { super(source, value); this.key = key; this.fetchTimeMillis = fetchTimeMillis; } } if (false) { /** @type {?} */ Parameter.prototype.key; /** @type {?} */ Parameter.prototype.fetchTimeMillis; } // If it's a Parameter array, test any, else test the individual Parameter /** @type {?} */ const filterTest = (/** * @param {?} fn * @return {?} */ (fn) => filter((/** * @param {?} it * @return {?} */ it => Array.isArray(it) ? it.some(fn) : fn(it)))); const ɵ1 = filterTest; // Allow the user to bypass the default values and wait till they get something from the server, even if it's a cached copy; // if used in conjuntion with first() it will only fetch RC values from the server if they aren't cached locally /** @type {?} */ const filterRemote = (/** * @return {?} */ () => filterTest((/** * @param {?} p * @return {?} */ p => p.getSource() === 'remote'))); // filterFresh allows the developer to effectively set up a maximum cache time /** @type {?} */ const filterFresh = (/** * @param {?} howRecentInMillis * @return {?} */ (howRecentInMillis) => filterTest((/** * @param {?} p * @return {?} */ p => p.fetchTimeMillis + howRecentInMillis >= new Date().getTime()))); // I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation. // The idea here being that if they have a default that never loads from the server, they will be able to tell via fetchTimeMillis // on the Parameter. Also if it doesn't come from the server it won't emit again in .changes, due to the distinctUntilChanged, // which we can simplify to === rather than deep comparison /** @type {?} */ const scanToParametersArray = (/** * @param {?} remoteConfig * @return {?} */ (remoteConfig) => pipe(withLatestFrom(remoteConfig), scan((/** * @param {?} existing * @param {?} __1 * @return {?} */ (existing, [all, rc]) => { // SEMVER use "new Set" to unique once we're only targeting es6 // at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness // implementation. // const allKeys = [...new Set([...existing.map(p => p.key), ...Object.keys(all)])]; /** @type {?} */ const allKeys = [...existing.map((/** * @param {?} p * @return {?} */ p => p.key)), ...Object.keys(all)].filter((/** * @param {?} v * @param {?} i * @param {?} a * @return {?} */ (v, i, a) => a.indexOf(v) === i)); return allKeys.map((/** * @param {?} key * @return {?} */ key => { /** @type {?} */ const updatedValue = all[key]; return updatedValue ? new Parameter(key, rc ? rc.fetchTimeMillis : -1, updatedValue.getSource(), updatedValue.asString()) : existing.find((/** * @param {?} p * @return {?} */ p => p.key === key)); })); }), (/** @type {?} */ ([]))))); const ɵ2 = scanToParametersArray; class AngularFireRemoteConfig { /** * @param {?} options * @param {?} nameOrConfig * @param {?} settings * @param {?} defaultConfig * @param {?} zone * @param {?} platformId */ constructor(options, nameOrConfig, settings, defaultConfig, zone, // tslint:disable-next-line:ban-types platformId) { this.zone = zone; /** @type {?} */ const schedulers = new ɵAngularFireSchedulers(zone); /** @type {?} */ const remoteConfig$ = of(undefined).pipe(observeOn(schedulers.outsideAngular), switchMap((/** * @return {?} */ () => isPlatformBrowser(platformId) ? import('firebase/remote-config') : EMPTY)), map((/** * @return {?} */ () => ɵfirebaseAppFactory(options, zone, nameOrConfig))), map((/** * @param {?} app * @return {?} */ app => app.remoteConfig())), tap((/** * @param {?} rc * @return {?} */ rc => { if (settings) { rc.settings = settings; } if (defaultConfig) { rc.defaultConfig = defaultConfig; } })), // tslint:disable-next-line startWith(undefined), shareReplay({ bufferSize: 1, refCount: false })); /** @type {?} */ const loadedRemoteConfig$ = remoteConfig$.pipe(filter((/** * @param {?} rc * @return {?} */ rc => !!rc))); /** @type {?} */ const default$ = of(Object.keys(defaultConfig || {}).reduce((/** * @param {?} c * @param {?} k * @return {?} */ (c, k) => (Object.assign(Object.assign({}, c), { [k]: new Value('default', defaultConfig[k].toString()) }))), {})); // we should filter out the defaults we provided to RC, since we have our own implementation // that gives us a -1 for fetchTimeMillis (so filterFresh can filter them out) /** @type {?} */ const filterOutDefaults = map((/** * @param {?} all * @return {?} */ all => Object.keys(all) .filter((/** * @param {?} key * @return {?} */ key => all[key].getSource() !== 'default')) .reduce((/** * @param {?} acc * @param {?} key * @return {?} */ (acc, key) => (Object.assign(Object.assign({}, acc), { [key]: all[key] }))), {}))); /** @type {?} */ const existing$ = loadedRemoteConfig$.pipe(switchMap((/** * @param {?} rc * @return {?} */ rc => rc.activate() .then((/** * @return {?} */ () => rc.ensureInitialized())) .then((/** * @return {?} */ () => rc.getAll())))), filterOutDefaults); /** @type {?} */ const fresh$ = loadedRemoteConfig$.pipe(switchMap((/** * @param {?} rc * @return {?} */ rc => zone.runOutsideAngular((/** * @return {?} */ () => rc.fetchAndActivate() .then((/** * @return {?} */ () => rc.ensureInitialized())) .then((/** * @return {?} */ () => rc.getAll())))))), filterOutDefaults); this.parameters = concat(default$, existing$, fresh$).pipe(scanToParametersArray(remoteConfig$), ɵkeepUnstableUntilFirstFactory(schedulers), shareReplay({ bufferSize: 1, refCount: true })); this.changes = this.parameters.pipe(switchMap((/** * @param {?} params * @return {?} */ params => of(...params))), groupBy((/** * @param {?} param * @return {?} */ param => param.key)), mergeMap((/** * @param {?} group * @return {?} */ group => group.pipe(distinctUntilChanged())))); this.strings = proxyAll(this.parameters, 'strings'); this.booleans = proxyAll(this.parameters, 'booleans'); this.numbers = proxyAll(this.parameters, 'numbers'); return ɵlazySDKProxy(this, loadedRemoteConfig$, zone); } } AngularFireRemoteConfig.decorators = [ { type: Injectable, args: [{ providedIn: 'any' },] } ]; /** @nocollapse */ AngularFireRemoteConfig.ctorParameters = () => [ { type: undefined, decorators: [{ type: Inject, args: [FIREBASE_OPTIONS,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [FIREBASE_APP_NAME,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [SETTINGS,] }] }, { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DEFAULTS,] }] }, { type: NgZone }, { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] } ]; /** @nocollapse */ AngularFireRemoteConfig.ɵprov = ɵɵdefineInjectable({ factory: function AngularFireRemoteConfig_Factory() { return new AngularFireRemoteConfig(ɵɵinject(FIREBASE_OPTIONS), ɵɵinject(FIREBASE_APP_NAME, 8), ɵɵinject(SETTINGS, 8), ɵɵinject(DEFAULTS, 8), ɵɵinject(NgZone), ɵɵinject(PLATFORM_ID)); }, token: AngularFireRemoteConfig, providedIn: "any" }); if (false) { /** @type {?} */ AngularFireRemoteConfig.prototype.changes; /** @type {?} */ AngularFireRemoteConfig.prototype.parameters; /** @type {?} */ AngularFireRemoteConfig.prototype.numbers; /** @type {?} */ AngularFireRemoteConfig.prototype.booleans; /** @type {?} */ AngularFireRemoteConfig.prototype.strings; /** * @type {?} * @private */ AngularFireRemoteConfig.prototype.zone; } /** @type {?} */ const budget = (/** * @template T * @param {?} interval * @return {?} */ (interval) => (/** * @param {?} source * @return {?} */ (source) => new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ let timedOut = false; // TODO use scheduler task rather than settimeout /** @type {?} */ const timeout = setTimeout((/** * @return {?} */ () => { observer.complete(); timedOut = true; }), interval); return source.subscribe({ /** * @param {?} val * @return {?} */ next(val) { if (!timedOut) { observer.next(val); } }, /** * @param {?} err * @return {?} */ error(err) { if (!timedOut) { clearTimeout(timeout); observer.error(err); } }, /** * @return {?} */ complete() { if (!timedOut) { clearTimeout(timeout); observer.complete(); } } }); })))); /** @type {?} */ const typedMethod = (/** * @param {?} it * @return {?} */ (it) => { switch (typeof it) { case 'string': return 'asString'; case 'boolean': return 'asBoolean'; case 'number': return 'asNumber'; default: return 'asString'; } }); const ɵ3 = typedMethod; /** * @template T * @param {?=} to * @return {?} */ function scanToObject(to = 'strings') { return pipe( // TODO cleanup scan((/** * @param {?} c * @param {?} p * @return {?} */ (c, p) => (Object.assign(Object.assign({}, c), { [p.key]: typeof to === 'object' ? p[typedMethod(to[p.key])]() : p[AS_TO_FN[to]]() }))), typeof to === 'object' ? (/** @type {?} */ (to)) : (/** @type {?} */ ({}))), debounceTime(1), budget(10), distinctUntilChanged((/** * @param {?} a * @param {?} b * @return {?} */ (a, b) => JSON.stringify(a) === JSON.stringify(b)))); } /** * @template T * @param {?=} to * @return {?} */ function mapToObject(to = 'strings') { return pipe( // TODO this is getting a little long, cleanup map((/** * @param {?} params * @return {?} */ (params) => params.reduce((/** * @param {?} c * @param {?} p * @return {?} */ (c, p) => (Object.assign(Object.assign({}, c), { [p.key]: typeof to === 'object' ? p[typedMethod(to[p.key])]() : p[AS_TO_FN[to]]() }))), typeof to === 'object' ? (/** @type {?} */ (to)) : (/** @type {?} */ ({}))))), distinctUntilChanged((/** * @param {?} a * @param {?} b * @return {?} */ (a, b) => JSON.stringify(a) === JSON.stringify(b)))); } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class AngularFireRemoteConfigModule { } AngularFireRemoteConfigModule.decorators = [ { type: NgModule, args: [{ providers: [AngularFireRemoteConfig] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ export { AngularFireRemoteConfig, AngularFireRemoteConfigModule, DEFAULTS, Parameter, SETTINGS, Value, budget, filterFresh, filterRemote, mapToObject, scanToObject }; //# sourceMappingURL=angular-fire-remote-config.js.map