Skip to content

Commit 6a5d400

Browse files
chore(lint): Clean up remote config
1 parent ae0ef48 commit 6a5d400

File tree

2 files changed

+124
-75
lines changed

2 files changed

+124
-75
lines changed

src/remote-config/remote-config.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { inject, TestBed } from '@angular/core/testing';
1+
import { TestBed } from '@angular/core/testing';
22
import { AngularFireModule, FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseApp } from '@angular/fire';
33
import { AngularFireRemoteConfig, AngularFireRemoteConfigModule, DEFAULTS, SETTINGS } from './public_api';
44
import { COMMON_CONFIG } from '../test-config';
@@ -15,10 +15,9 @@ describe('AngularFireRemoteConfig', () => {
1515
AngularFireRemoteConfigModule
1616
]
1717
});
18-
inject([FirebaseApp, AngularFireRemoteConfig], (app_: FirebaseApp, _rc: AngularFireRemoteConfig) => {
19-
app = app_;
20-
rc = _rc;
21-
})();
18+
19+
app = TestBed.inject(FirebaseApp);
20+
rc = TestBed.inject(AngularFireRemoteConfig);
2221
});
2322

2423
afterEach(() => {
@@ -54,10 +53,9 @@ describe('AngularFireRemoteConfig with different app', () => {
5453
{ provide: DEFAULTS, useValue: {} }
5554
]
5655
});
57-
inject([FirebaseApp, AngularFireRemoteConfig], (app_: FirebaseApp, _rc: AngularFireRemoteConfig) => {
58-
app = app_;
59-
rc = _rc;
60-
})();
56+
57+
app = TestBed.inject(FirebaseApp);
58+
rc = TestBed.inject(AngularFireRemoteConfig);
6159
});
6260

6361
afterEach(() => {

src/remote-config/remote-config.ts

Lines changed: 117 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,37 @@ import { FIREBASE_APP_NAME, FIREBASE_OPTIONS, FirebaseAppConfig, FirebaseOptions
55
import { remoteConfig } from 'firebase/app';
66
import { isPlatformBrowser } from '@angular/common';
77

8-
export interface ConfigTemplate {[key: string]: string|number|boolean; }
8+
export interface ConfigTemplate {
9+
[key: string]: string | number | boolean;
10+
}
911

1012
export const SETTINGS = new InjectionToken<remoteConfig.Settings>('angularfire2.remoteConfig.settings');
1113
export const DEFAULTS = new InjectionToken<ConfigTemplate>('angularfire2.remoteConfig.defaultConfig');
1214

13-
export interface AngularFireRemoteConfig extends ɵPromiseProxy<remoteConfig.RemoteConfig> {}
15+
export interface AngularFireRemoteConfig extends ɵPromiseProxy<remoteConfig.RemoteConfig> {
16+
}
1417

1518
// TODO export as implements Partial<...> so minor doesn't break us
1619
export class Value implements remoteConfig.Value {
17-
asBoolean() { return ['1', 'true', 't', 'y', 'yes', 'on'].indexOf(this._value.toLowerCase()) > -1; }
18-
asString() { return this._value; }
19-
asNumber() { return Number(this._value) || 0; }
20-
getSource() { return this._source; }
21-
constructor(public _source: remoteConfig.ValueSource, public _value: string) { }
20+
asBoolean() {
21+
return ['1', 'true', 't', 'y', 'yes', 'on'].indexOf(this._value.toLowerCase()) > -1;
22+
}
23+
24+
asString() {
25+
return this._value;
26+
}
27+
28+
asNumber() {
29+
return Number(this._value) || 0;
30+
}
31+
32+
getSource() {
33+
return this._source;
34+
}
35+
36+
// tslint:disable-next-line:variable-name
37+
constructor(public _source: remoteConfig.ValueSource, public _value: string) {
38+
}
2239
}
2340

2441
// SEMVER use ConstructorParameters when we can support Typescript 3.6
@@ -29,7 +46,7 @@ export class Parameter extends Value {
2946
}
3047

3148
// If it's a Parameter array, test any, else test the individual Parameter
32-
const filterTest = (fn: (param: Parameter) => boolean) => filter<Parameter|Parameter[]>(it => Array.isArray(it) ? it.some(fn) : fn(it));
49+
const filterTest = (fn: (param: Parameter) => boolean) => filter<Parameter | Parameter[]>(it => Array.isArray(it) ? it.some(fn) : fn(it));
3350

3451
// Allow the user to bypass the default values and wait till they get something from the server, even if it's a cached copy;
3552
// if used in conjuntion with first() it will only fetch RC values from the server if they aren't cached locally
@@ -45,16 +62,17 @@ export class AngularFireRemoteConfig {
4562

4663
readonly changes: Observable<Parameter>;
4764
readonly parameters: Observable<Parameter[]>;
48-
readonly numbers: Observable<{[key: string]: number|undefined}> & {[key: string]: Observable<number>};
49-
readonly booleans: Observable<{[key: string]: boolean|undefined}> & {[key: string]: Observable<boolean>};
50-
readonly strings: Observable<{[key: string]: string|undefined}> & {[key: string]: Observable<string|undefined>};
65+
readonly numbers: Observable<{ [key: string]: number | undefined }> & { [key: string]: Observable<number> };
66+
readonly booleans: Observable<{ [key: string]: boolean | undefined }> & { [key: string]: Observable<boolean> };
67+
readonly strings: Observable<{ [key: string]: string | undefined }> & { [key: string]: Observable<string | undefined> };
5168

5269
constructor(
5370
@Inject(FIREBASE_OPTIONS) options: FirebaseOptions,
54-
@Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string|FirebaseAppConfig|null|undefined,
55-
@Optional() @Inject(SETTINGS) settings: remoteConfig.Settings|null,
56-
@Optional() @Inject(DEFAULTS) defaultConfig: ConfigTemplate|null,
71+
@Optional() @Inject(FIREBASE_APP_NAME) nameOrConfig: string | FirebaseAppConfig | null | undefined,
72+
@Optional() @Inject(SETTINGS) settings: remoteConfig.Settings | null,
73+
@Optional() @Inject(DEFAULTS) defaultConfig: ConfigTemplate | null,
5774
private zone: NgZone,
75+
// tslint:disable-next-line:ban-types
5876
@Inject(PLATFORM_ID) platformId: Object
5977
) {
6078

@@ -66,8 +84,12 @@ export class AngularFireRemoteConfig {
6684
map(() => ɵfirebaseAppFactory(options, zone, nameOrConfig)),
6785
map(app => app.remoteConfig()),
6886
tap(rc => {
69-
if (settings) { rc.settings = settings; }
70-
if (defaultConfig) { rc.defaultConfig = defaultConfig; }
87+
if (settings) {
88+
rc.settings = settings;
89+
}
90+
if (defaultConfig) {
91+
rc.defaultConfig = defaultConfig;
92+
}
7193
}),
7294
startWith(undefined),
7395
shareReplay({ bufferSize: 1, refCount: false })
@@ -77,16 +99,16 @@ export class AngularFireRemoteConfig {
7799
filter<remoteConfig.RemoteConfig>(rc => !!rc)
78100
);
79101

80-
const default$: Observable<{[key: string]: remoteConfig.Value}> = of(Object.keys(defaultConfig || {}).reduce(
81-
(c, k) => ({...c, [k]: new Value('default', defaultConfig![k].toString()) }), {}
102+
const default$: Observable<{ [key: string]: remoteConfig.Value }> = of(Object.keys(defaultConfig || {}).reduce(
103+
(c, k) => ({ ...c, [k]: new Value('default', defaultConfig[k].toString()) }), {}
82104
));
83105

84106
// we should filter out the defaults we provided to RC, since we have our own implementation
85107
// that gives us a -1 for fetchTimeMillis (so filterFresh can filter them out)
86-
const filterOutDefaults = map<{[key: string]: remoteConfig.Value}, {[key: string]: remoteConfig.Value}>(all =>
108+
const filterOutDefaults = map<{ [key: string]: remoteConfig.Value }, { [key: string]: remoteConfig.Value }>(all =>
87109
Object.keys(all)
88-
.filter(key => all[key].getSource() != 'default')
89-
.reduce((acc, key) => ({...acc, [key]: all[key]}), {})
110+
.filter(key => all[key].getSource() !== 'default')
111+
.reduce((acc, key) => ({ ...acc, [key]: all[key] }), {})
90112
);
91113

92114
const existing$ = loadedRemoteConfig$.pipe(
@@ -120,29 +142,33 @@ export class AngularFireRemoteConfig {
120142
))
121143
);
122144

123-
this.strings = proxyAll(this.parameters, 'strings');
145+
this.strings = proxyAll(this.parameters, 'strings');
124146
this.booleans = proxyAll(this.parameters, 'booleans');
125-
this.numbers = proxyAll(this.parameters, 'numbers');
147+
this.numbers = proxyAll(this.parameters, 'numbers');
126148

127149
return ɵlazySDKProxy(this, loadedRemoteConfig$, zone);
128150
}
129151

130152
}
131153

132154
// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation.
133-
// 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.
134-
// 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
135-
const scanToParametersArray = (remoteConfig: Observable<remoteConfig.RemoteConfig|undefined>): OperatorFunction<{[key: string]: remoteConfig.Value}, Parameter[]> => pipe(
155+
// The idea here being that if they have a default that never loads from the server, they will be able to tell via fetchTimeMillis
156+
// on the Parameter. Also if it doesn't come from the server it won't emit again in .changes, due to the distinctUntilChanged,
157+
// which we can simplify to === rather than deep comparison
158+
const scanToParametersArray = (
159+
remoteConfig: Observable<remoteConfig.RemoteConfig | undefined>
160+
): OperatorFunction<{ [key: string]: remoteConfig.Value }, Parameter[]> => pipe(
136161
withLatestFrom(remoteConfig),
137162
scan((existing, [all, rc]) => {
138163
// SEMVER use "new Set" to unique once we're only targeting es6
139-
// at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness implementation
164+
// at the scale we expect remote config to be at, we probably won't see a performance hit from this unoptimized uniqueness
165+
// implementation.
140166
// const allKeys = [...new Set([...existing.map(p => p.key), ...Object.keys(all)])];
141167
const allKeys = [...existing.map(p => p.key), ...Object.keys(all)].filter((v, i, a) => a.indexOf(v) === i);
142168
return allKeys.map(key => {
143169
const updatedValue = all[key];
144170
return updatedValue ? new Parameter(key, rc ? rc.fetchTimeMillis : -1, updatedValue.getSource(), updatedValue.asString())
145-
: existing.find(p => p.key === key)!;
171+
: existing.find(p => p.key === key);
146172
});
147173
}, [] as Array<Parameter>)
148174
);
@@ -151,72 +177,97 @@ const AS_TO_FN = { strings: 'asString', numbers: 'asNumber', booleans: 'asBoolea
151177
const STATIC_VALUES = { numbers: 0, booleans: false, strings: undefined };
152178

153179
export const budget = <T>(interval: number): MonoTypeOperatorFunction<T> => (source: Observable<T>) => new Observable<T>(observer => {
154-
let timedOut = false;
155-
// TODO use scheduler task rather than settimeout
156-
const timeout = setTimeout(() => {
157-
observer.complete();
158-
timedOut = true;
159-
}, interval);
160-
return source.subscribe({
161-
next(val) { if (!timedOut) { observer.next(val); } },
162-
error(err) { if (!timedOut) { clearTimeout(timeout); observer.error(err); } },
163-
complete() { if (!timedOut) { clearTimeout(timeout); observer.complete(); } }
164-
});
180+
let timedOut = false;
181+
// TODO use scheduler task rather than settimeout
182+
const timeout = setTimeout(() => {
183+
observer.complete();
184+
timedOut = true;
185+
}, interval);
186+
return source.subscribe({
187+
next(val) {
188+
if (!timedOut) {
189+
observer.next(val);
190+
}
191+
},
192+
error(err) {
193+
if (!timedOut) {
194+
clearTimeout(timeout);
195+
observer.error(err);
196+
}
197+
},
198+
complete() {
199+
if (!timedOut) {
200+
clearTimeout(timeout);
201+
observer.complete();
202+
}
203+
}
165204
});
205+
});
166206

167207
const typedMethod = (it: any) => {
168208
switch (typeof it) {
169-
case 'string': return 'asString';
170-
case 'boolean': return 'asBoolean';
171-
case 'number': return 'asNumber';
172-
default: return 'asString';
209+
case 'string':
210+
return 'asString';
211+
case 'boolean':
212+
return 'asBoolean';
213+
case 'number':
214+
return 'asNumber';
215+
default:
216+
return 'asString';
173217
}
174218
};
175219

176-
export function scanToObject(): OperatorFunction<Parameter, {[key: string]: string|undefined}>;
177-
export function scanToObject(to: 'numbers'): OperatorFunction<Parameter, {[key: string]: number|undefined}>;
178-
export function scanToObject(to: 'booleans'): OperatorFunction<Parameter, {[key: string]: boolean|undefined}>;
179-
export function scanToObject(to: 'strings'): OperatorFunction<Parameter, {[key: string]: string|undefined}>;
180-
export function scanToObject<T extends ConfigTemplate>(template: T): OperatorFunction<Parameter, T & {[key: string]: string|undefined}>;
181-
export function scanToObject<T extends ConfigTemplate>(to: 'numbers'|'booleans'|'strings'|T = 'strings') {
220+
export function scanToObject(): OperatorFunction<Parameter, { [key: string]: string | undefined }>;
221+
export function scanToObject(to: 'numbers'): OperatorFunction<Parameter, { [key: string]: number | undefined }>;
222+
export function scanToObject(to: 'booleans'): OperatorFunction<Parameter, { [key: string]: boolean | undefined }>;
223+
// tslint:disable-next-line:unified-signatures
224+
export function scanToObject(to: 'strings'): OperatorFunction<Parameter, { [key: string]: string | undefined }>;
225+
export function scanToObject<T extends ConfigTemplate>(template: T): OperatorFunction<Parameter, T & { [key: string]: string | undefined }>;
226+
export function scanToObject<T extends ConfigTemplate>(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') {
182227
return pipe(
183228
// TODO cleanup
184229
scan(
185-
(c, p: Parameter) => ({...c, [p.key]: typeof to === 'object' ?
186-
p[typedMethod(to[p.key])]() :
187-
p[AS_TO_FN[to]]() }),
230+
(c, p: Parameter) => ({
231+
...c, [p.key]: typeof to === 'object' ?
232+
p[typedMethod(to[p.key])]() :
233+
p[AS_TO_FN[to]]()
234+
}),
188235
typeof to === 'object' ?
189-
to as T & {[key: string]: string|undefined} :
190-
{} as {[key: string]: number|boolean|string}
236+
to as T & { [key: string]: string | undefined } :
237+
{} as { [key: string]: number | boolean | string }
191238
),
192239
debounceTime(1),
193240
budget(10),
194241
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
195242
);
196243
}
197244

198-
export function mapToObject(): OperatorFunction<Parameter[], {[key: string]: string|undefined}>;
199-
export function mapToObject(to: 'numbers'): OperatorFunction<Parameter[], {[key: string]: number|undefined}>;
200-
export function mapToObject(to: 'booleans'): OperatorFunction<Parameter[], {[key: string]: boolean|undefined}>;
201-
export function mapToObject(to: 'strings'): OperatorFunction<Parameter[], {[key: string]: string|undefined}>;
202-
export function mapToObject<T extends ConfigTemplate>(template: T): OperatorFunction<Parameter[], T & {[key: string]: string|undefined}>;
203-
export function mapToObject<T extends ConfigTemplate>(to: 'numbers'|'booleans'|'strings'|T = 'strings') {
245+
export function mapToObject(): OperatorFunction<Parameter[], { [key: string]: string | undefined }>;
246+
export function mapToObject(to: 'numbers'): OperatorFunction<Parameter[], { [key: string]: number | undefined }>;
247+
export function mapToObject(to: 'booleans'): OperatorFunction<Parameter[], { [key: string]: boolean | undefined }>;
248+
// tslint:disable-next-line:unified-signatures
249+
export function mapToObject(to: 'strings'): OperatorFunction<Parameter[], { [key: string]: string | undefined }>;
250+
export function mapToObject<T extends ConfigTemplate>(template: T):
251+
OperatorFunction<Parameter[], T & { [key: string]: string | undefined }>;
252+
export function mapToObject<T extends ConfigTemplate>(to: 'numbers' | 'booleans' | 'strings' | T = 'strings') {
204253
return pipe(
205254
// TODO this is getting a little long, cleanup
206255
map((params: Parameter[]) => params.reduce(
207-
(c, p) => ({...c, [p.key]: typeof to === 'object' ?
208-
p[typedMethod(to[p.key])]() :
209-
p[AS_TO_FN[to]]() }),
256+
(c, p) => ({
257+
...c, [p.key]: typeof to === 'object' ?
258+
p[typedMethod(to[p.key])]() :
259+
p[AS_TO_FN[to]]()
260+
}),
210261
typeof to === 'object' ?
211-
to as T & {[key: string]: string|undefined} :
212-
{} as {[key: string]: number|boolean|string}
262+
to as T & { [key: string]: string | undefined } :
263+
{} as { [key: string]: number | boolean | string }
213264
)),
214265
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
215266
);
216267
}
217268

218269
// TODO look into the types here, I don't like the anys
219-
const proxyAll = (observable: Observable<Parameter[]>, as: 'numbers'|'booleans'|'strings') => new Proxy(
270+
const proxyAll = (observable: Observable<Parameter[]>, as: 'numbers' | 'booleans' | 'strings') => new Proxy(
220271
observable.pipe(mapToObject(as as any)), {
221272
get: (self, name: string) => self[name] || observable.pipe(
222273
map(all => all.find(p => p.key === name)),

0 commit comments

Comments
 (0)