UNPKG

10.8 kBJavaScriptView Raw
1import * as i0 from '@angular/core';
2import { InjectionToken, Injectable, Inject, Optional, NgZone, PLATFORM_ID, NgModule } from '@angular/core';
3import { pipe, of, EMPTY, concat, Observable } from 'rxjs';
4import { map, distinctUntilChanged, filter, withLatestFrom, scan, observeOn, switchMap, tap, startWith, shareReplay, groupBy, mergeMap, debounceTime } from 'rxjs/operators';
5import * as i1 from '@angular/fire';
6import { ɵAngularFireSchedulers, ɵfirebaseAppFactory, ɵfetchInstance, ɵkeepUnstableUntilFirstFactory, ɵlazySDKProxy, FIREBASE_OPTIONS, FIREBASE_APP_NAME, ɵapplyMixins } from '@angular/fire';
7import { isPlatformBrowser } from '@angular/common';
8import firebase from 'firebase/app';
9
10const proxyPolyfillCompat = {
11 settings: null,
12 defaultConfig: null,
13 fetchTimeMillis: null,
14 lastFetchStatus: null,
15 activate: null,
16 ensureInitialized: null,
17 fetch: null,
18 fetchAndActivate: null,
19 getAll: null,
20 getBoolean: null,
21 getNumber: null,
22 getString: null,
23 getValue: null,
24 setLogLevel: null,
25};
26
27const SETTINGS = new InjectionToken('angularfire2.remoteConfig.settings');
28const DEFAULTS = new InjectionToken('angularfire2.remoteConfig.defaultConfig');
29const AS_TO_FN = { strings: 'asString', numbers: 'asNumber', booleans: 'asBoolean' };
30const STATIC_VALUES = { numbers: 0, booleans: false, strings: undefined };
31// TODO look into the types here, I don't like the anys
32const proxyAll = (observable, as) => new Proxy(observable.pipe(mapToObject(as)), {
33 get: (self, name) => self[name] || observable.pipe(map(all => all.find(p => p.key === name)), map(param => param ? param[AS_TO_FN[as]]() : STATIC_VALUES[as]), distinctUntilChanged())
34});
35const ɵ0 = proxyAll;
36// TODO export as implements Partial<...> so minor doesn't break us
37class Value {
38 // tslint:disable-next-line:variable-name
39 constructor(_source, _value) {
40 this._source = _source;
41 this._value = _value;
42 }
43 asBoolean() {
44 return ['1', 'true', 't', 'y', 'yes', 'on'].indexOf(this._value.toLowerCase()) > -1;
45 }
46 asString() {
47 return this._value;
48 }
49 asNumber() {
50 return Number(this._value) || 0;
51 }
52 getSource() {
53 return this._source;
54 }
55}
56// SEMVER use ConstructorParameters when we can support Typescript 3.6
57class Parameter extends Value {
58 constructor(key, fetchTimeMillis, source, value) {
59 super(source, value);
60 this.key = key;
61 this.fetchTimeMillis = fetchTimeMillis;
62 }
63}
64// If it's a Parameter array, test any, else test the individual Parameter
65const filterTest = (fn) => filter(it => Array.isArray(it) ? it.some(fn) : fn(it));
66const ɵ1 = filterTest;
67// Allow the user to bypass the default values and wait till they get something from the server, even if it's a cached copy;
68// if used in conjuntion with first() it will only fetch RC values from the server if they aren't cached locally
69const filterRemote = () => filterTest(p => p.getSource() === 'remote');
70// filterFresh allows the developer to effectively set up a maximum cache time
71const filterFresh = (howRecentInMillis) => filterTest(p => p.fetchTimeMillis + howRecentInMillis >= new Date().getTime());
72// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation.
73// The idea here being that if they have a default that never loads from the server, they will be able to tell via fetchTimeMillis
74// on the Parameter. Also if it doesn't come from the server it won't emit again in .changes, due to the distinctUntilChanged,
75// which we can simplify to === rather than deep comparison
76const scanToParametersArray = (remoteConfig) => pipe(withLatestFrom(remoteConfig), scan((existing, [all, rc]) => {
77 // SEMVER use "new Set" to unique once we're only targeting es6
78 // at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness
79 // implementation.
80 // const allKeys = [...new Set([...existing.map(p => p.key), ...Object.keys(all)])];
81 const allKeys = [...existing.map(p => p.key), ...Object.keys(all)].filter((v, i, a) => a.indexOf(v) === i);
82 return allKeys.map(key => {
83 const updatedValue = all[key];
84 return updatedValue ? new Parameter(key, rc ? rc.fetchTimeMillis : -1, updatedValue.getSource(), updatedValue.asString())
85 : existing.find(p => p.key === key);
86 });
87}, []));
88const ɵ2 = scanToParametersArray;
89class AngularFireRemoteConfig {
90 constructor(options, nameOrConfig, settings, defaultConfig, zone,
91 // tslint:disable-next-line:ban-types
92 platformId) {
93 this.zone = zone;
94 const schedulers = new ɵAngularFireSchedulers(zone);
95 const remoteConfig$ = of(undefined).pipe(observeOn(schedulers.outsideAngular), switchMap(() => isPlatformBrowser(platformId) ? import('firebase/remote-config') : EMPTY), switchMap(() => import('@firebase/remote-config')), tap(rc => rc.registerRemoteConfig && rc.registerRemoteConfig(firebase)), map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)), map(app => ɵfetchInstance(`${app.name}.remote-config`, 'AngularFireRemoteConfig', app, () => {
96 const rc = app.remoteConfig();
97 if (settings) {
98 rc.settings = settings;
99 }
100 if (defaultConfig) {
101 rc.defaultConfig = defaultConfig;
102 }
103 return rc;
104 }, [settings, defaultConfig])),
105 // tslint:disable-next-line
106 startWith(undefined), shareReplay({ bufferSize: 1, refCount: false }));
107 const loadedRemoteConfig$ = remoteConfig$.pipe(filter(rc => !!rc));
108 const default$ = of(Object.keys(defaultConfig || {}).reduce((c, k) => (Object.assign(Object.assign({}, c), { [k]: new Value('default', defaultConfig[k].toString()) })), {}));
109 // we should filter out the defaults we provided to RC, since we have our own implementation
110 // that gives us a -1 for fetchTimeMillis (so filterFresh can filter them out)
111 const filterOutDefaults = map(all => Object.keys(all)
112 .filter(key => all[key].getSource() !== 'default')
113 .reduce((acc, key) => (Object.assign(Object.assign({}, acc), { [key]: all[key] })), {}));
114 const existing$ = loadedRemoteConfig$.pipe(switchMap(rc => rc.activate()
115 .then(() => rc.ensureInitialized())
116 .then(() => rc.getAll())), filterOutDefaults);
117 const fresh$ = loadedRemoteConfig$.pipe(switchMap(rc => zone.runOutsideAngular(() => rc.fetchAndActivate()
118 .then(() => rc.ensureInitialized())
119 .then(() => rc.getAll()))), filterOutDefaults);
120 this.parameters = concat(default$, existing$, fresh$).pipe(scanToParametersArray(remoteConfig$), ɵkeepUnstableUntilFirstFactory(schedulers), shareReplay({ bufferSize: 1, refCount: true }));
121 this.changes = this.parameters.pipe(switchMap(params => of(...params)), groupBy(param => param.key), mergeMap(group => group.pipe(distinctUntilChanged())));
122 this.strings = proxyAll(this.parameters, 'strings');
123 this.booleans = proxyAll(this.parameters, 'booleans');
124 this.numbers = proxyAll(this.parameters, 'numbers');
125 return ɵlazySDKProxy(this, loadedRemoteConfig$, zone);
126 }
127}
128/** @nocollapse */ AngularFireRemoteConfig.ɵprov = i0.ɵɵdefineInjectable({ factory: function AngularFireRemoteConfig_Factory() { return new AngularFireRemoteConfig(i0.ɵɵinject(i1.FIREBASE_OPTIONS), i0.ɵɵinject(i1.FIREBASE_APP_NAME, 8), i0.ɵɵinject(SETTINGS, 8), i0.ɵɵinject(DEFAULTS, 8), i0.ɵɵinject(i0.NgZone), i0.ɵɵinject(i0.PLATFORM_ID)); }, token: AngularFireRemoteConfig, providedIn: "any" });
129AngularFireRemoteConfig.decorators = [
130 { type: Injectable, args: [{
131 providedIn: 'any'
132 },] }
133];
134/** @nocollapse */
135AngularFireRemoteConfig.ctorParameters = () => [
136 { type: undefined, decorators: [{ type: Inject, args: [FIREBASE_OPTIONS,] }] },
137 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [FIREBASE_APP_NAME,] }] },
138 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [SETTINGS,] }] },
139 { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [DEFAULTS,] }] },
140 { type: NgZone },
141 { type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
142];
143const budget = (interval) => (source) => new Observable(observer => {
144 let timedOut = false;
145 // TODO use scheduler task rather than settimeout
146 const timeout = setTimeout(() => {
147 observer.complete();
148 timedOut = true;
149 }, interval);
150 return source.subscribe({
151 next(val) {
152 if (!timedOut) {
153 observer.next(val);
154 }
155 },
156 error(err) {
157 if (!timedOut) {
158 clearTimeout(timeout);
159 observer.error(err);
160 }
161 },
162 complete() {
163 if (!timedOut) {
164 clearTimeout(timeout);
165 observer.complete();
166 }
167 }
168 });
169});
170const typedMethod = (it) => {
171 switch (typeof it) {
172 case 'string':
173 return 'asString';
174 case 'boolean':
175 return 'asBoolean';
176 case 'number':
177 return 'asNumber';
178 default:
179 return 'asString';
180 }
181};
182const ɵ3 = typedMethod;
183function scanToObject(to = 'strings') {
184 return pipe(
185 // TODO cleanup
186 scan((c, p) => (Object.assign(Object.assign({}, c), { [p.key]: typeof to === 'object' ?
187 p[typedMethod(to[p.key])]() :
188 p[AS_TO_FN[to]]() })), typeof to === 'object' ?
189 to :
190 {}), debounceTime(1), budget(10), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)));
191}
192function mapToObject(to = 'strings') {
193 return pipe(
194 // TODO this is getting a little long, cleanup
195 map((params) => params.reduce((c, p) => (Object.assign(Object.assign({}, c), { [p.key]: typeof to === 'object' ?
196 p[typedMethod(to[p.key])]() :
197 p[AS_TO_FN[to]]() })), typeof to === 'object' ?
198 to :
199 {})), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)));
200}
201ɵapplyMixins(AngularFireRemoteConfig, [proxyPolyfillCompat]);
202
203class AngularFireRemoteConfigModule {
204}
205AngularFireRemoteConfigModule.decorators = [
206 { type: NgModule, args: [{
207 providers: [AngularFireRemoteConfig]
208 },] }
209];
210
211/**
212 * Generated bundle index. Do not edit.
213 */
214
215export { AngularFireRemoteConfig, AngularFireRemoteConfigModule, DEFAULTS, Parameter, SETTINGS, Value, budget, filterFresh, filterRemote, mapToObject, scanToObject, ɵ0, ɵ1, ɵ2, ɵ3 };
216//# sourceMappingURL=angular-fire-remote-config.js.map