1
1
import { Injectable , Inject , Optional , NgZone , InjectionToken , PLATFORM_ID } from '@angular/core' ;
2
- import { Observable , concat , of , pipe , OperatorFunction , UnaryFunction } from 'rxjs' ;
3
- import { map , switchMap , tap , shareReplay , distinctUntilChanged , filter , groupBy , mergeMap , scan , withLatestFrom , startWith } from 'rxjs/operators' ;
2
+ import { Observable , concat , of , pipe , OperatorFunction } from 'rxjs' ;
3
+ import { map , switchMap , tap , shareReplay , distinctUntilChanged , filter , groupBy , mergeMap , scan , withLatestFrom , startWith , debounceTime } from 'rxjs/operators' ;
4
4
import { FirebaseAppConfig , FirebaseOptions , ɵlazySDKProxy , FIREBASE_OPTIONS , FIREBASE_APP_NAME } from '@angular/fire' ;
5
5
import { remoteConfig } from 'firebase/app' ;
6
6
7
- export interface DefaultConfig { [ key :string ] : string | number | boolean } ;
7
+ export interface ConfigTemplate { [ key :string ] : string | number | boolean } ;
8
8
9
9
export const REMOTE_CONFIG_SETTINGS = new InjectionToken < remoteConfig . Settings > ( 'angularfire2.remoteConfig.settings' ) ;
10
- export const DEFAULT_CONFIG = new InjectionToken < DefaultConfig > ( 'angularfire2.remoteConfig.defaultConfig' ) ;
10
+ export const DEFAULT_CONFIG = new InjectionToken < ConfigTemplate > ( 'angularfire2.remoteConfig.defaultConfig' ) ;
11
11
12
12
import { FirebaseRemoteConfig , _firebaseAppFactory , runOutsideAngular } from '@angular/fire' ;
13
13
import { isPlatformServer } from '@angular/common' ;
@@ -65,15 +65,15 @@ export class AngularFireRemoteConfig {
65
65
66
66
readonly changes : Observable < Parameter > ;
67
67
readonly parameters : Observable < Parameter [ ] > ;
68
- readonly numbers : Observable < Record < string , number > > & Record < string , Observable < number > > ;
69
- readonly booleans : Observable < Record < string , boolean > > & Record < string , Observable < boolean > > ;
70
- readonly strings : Observable < Record < string , string > > & Record < string , Observable < string | undefined > > ;
68
+ readonly numbers : Observable < { [ key : string ] : number } > & { [ key : string ] : Observable < number > } ;
69
+ readonly booleans : Observable < { [ key : string ] : boolean } > & { [ key : string ] : Observable < boolean > } ;
70
+ readonly strings : Observable < { [ key : string ] : string } > & { [ key : string ] : Observable < string | undefined > } ;
71
71
72
72
constructor (
73
73
@Inject ( FIREBASE_OPTIONS ) options :FirebaseOptions ,
74
74
@Optional ( ) @Inject ( FIREBASE_APP_NAME ) nameOrConfig :string | FirebaseAppConfig | null | undefined ,
75
75
@Optional ( ) @Inject ( REMOTE_CONFIG_SETTINGS ) settings :remoteConfig . Settings | null ,
76
- @Optional ( ) @Inject ( DEFAULT_CONFIG ) defaultConfig :DefaultConfig | null ,
76
+ @Optional ( ) @Inject ( DEFAULT_CONFIG ) defaultConfig :ConfigTemplate | null ,
77
77
@Inject ( PLATFORM_ID ) platformId :Object ,
78
78
private zone : NgZone
79
79
) {
@@ -123,9 +123,9 @@ export class AngularFireRemoteConfig {
123
123
) )
124
124
) ;
125
125
126
- this . strings = proxyAll ( this . parameters , 'asString ' ) ;
127
- this . booleans = proxyAll ( this . parameters , 'asBoolean ' ) ;
128
- this . numbers = proxyAll ( this . parameters , 'asNumber ' ) ;
126
+ this . strings = proxyAll ( this . parameters , 'strings ' ) ;
127
+ this . booleans = proxyAll ( this . parameters , 'booleans ' ) ;
128
+ this . numbers = proxyAll ( this . parameters , 'numbers ' ) ;
129
129
130
130
// TODO fix the proxy for server
131
131
return isPlatformServer ( platformId ) ? this : ɵlazySDKProxy ( this , remoteConfig$ , zone ) ;
@@ -136,7 +136,7 @@ export class AngularFireRemoteConfig {
136
136
// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation.
137
137
// 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.
138
138
// 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
139
- const scanToParametersArray = ( remoteConfig : Observable < remoteConfig . RemoteConfig | undefined > ) : OperatorFunction < Record < string , remoteConfig . Value > , Parameter [ ] > => pipe (
139
+ const scanToParametersArray = ( remoteConfig : Observable < remoteConfig . RemoteConfig | undefined > ) : OperatorFunction < { [ key : string ] : remoteConfig . Value } , Parameter [ ] > => pipe (
140
140
withLatestFrom ( remoteConfig ) ,
141
141
scan ( ( existing , [ all , rc ] ) => {
142
142
// SEMVER use "new Set" to unique once we're only targeting es6
@@ -151,28 +151,66 @@ const scanToParametersArray = (remoteConfig: Observable<remoteConfig.RemoteConfi
151
151
} , [ ] as Array < Parameter > )
152
152
) ;
153
153
154
- const PROXY_DEFAULTS = { 'asNumber' : 0 , 'asBoolean' : false , 'asString' : undefined } ;
155
-
154
+ const AS_TO_FN = { 'strings' : 'asString' , 'numbers' : 'asNumber' , 'booleans' : 'asBoolean' } ;
155
+ const PROXY_DEFAULTS = { 'numbers' : 0 , 'booleans' : false , 'strings' : undefined } ;
156
+
157
+ export const budget = ( interval : number ) => < T > ( source : Observable < T > ) => new Observable < T > ( observer => {
158
+ let timedOut = false ;
159
+ // TODO use scheduler task rather than settimeout
160
+ const timeout = setTimeout ( ( ) => {
161
+ observer . complete ( ) ;
162
+ timedOut = true ;
163
+ } , interval ) ;
164
+ return source . subscribe ( {
165
+ next ( val ) { if ( ! timedOut ) { observer . next ( val ) ; } } ,
166
+ error ( err ) { if ( ! timedOut ) { clearTimeout ( timeout ) ; observer . error ( err ) ; } } ,
167
+ complete ( ) { if ( ! timedOut ) { clearTimeout ( timeout ) ; observer . complete ( ) ; } }
168
+ } )
169
+ } ) ;
170
+
171
+ const typedMethod = ( it :any ) => {
172
+ switch ( typeof it ) {
173
+ case 'string' : return 'asString' ;
174
+ case 'boolean' : return 'asBoolean' ;
175
+ case 'number' : return 'asNumber' ;
176
+ default : return 'asString' ;
177
+ }
178
+ } ;
156
179
157
- function mapToObject ( fn : 'asNumber' ) : UnaryFunction < Observable < Parameter [ ] > , Observable < Record < string , number > > > ;
158
- function mapToObject ( fn : 'asBoolean' ) : UnaryFunction < Observable < Parameter [ ] > , Observable < Record < string , boolean > > > ;
159
- function mapToObject ( fn : 'asString' ) : UnaryFunction < Observable < Parameter [ ] > , Observable < Record < string , string | undefined > > > ;
160
- function mapToObject ( fn : 'asNumber' | 'asBoolean' | 'asString' ) {
180
+ export function scanToObject ( ) : OperatorFunction < Parameter , { [ key :string ] : string } > ;
181
+ export function scanToObject ( as : 'numbers' ) : OperatorFunction < Parameter , { [ key :string ] : number } > ;
182
+ export function scanToObject ( as : 'booleans' ) : OperatorFunction < Parameter , { [ key :string ] : boolean } > ;
183
+ export function scanToObject ( as : 'strings' ) : OperatorFunction < Parameter , { [ key :string ] : string } > ;
184
+ export function scanToObject < T extends ConfigTemplate > ( template : T ) : OperatorFunction < Parameter , T & { [ key :string ] : string | undefined } > ;
185
+ export function scanToObject ( as : 'numbers' | 'booleans' | 'strings' | ConfigTemplate = 'strings' ) {
161
186
return pipe (
162
- map ( ( params : Parameter [ ] ) => params . reduce ( ( c , p ) => ( { ...c , [ p . key ] : p [ fn ] ( ) } ) , { } as Record < string , number | boolean | string | undefined > ) ) ,
187
+ // TODO cleanup
188
+ scan ( ( c , p : Parameter ) => ( { ...c , [ p . key ] : typeof as === 'object' ? p [ typedMethod ( as [ p . key ] ) ] ( ) : p [ AS_TO_FN [ as ] ] ( ) } ) , typeof as === 'object' ? as : { } as { [ key :string ] : number | boolean | string } ) ,
189
+ debounceTime ( 1 ) ,
190
+ budget ( 10 ) ,
163
191
distinctUntilChanged ( ( a , b ) => JSON . stringify ( a ) === JSON . stringify ( b ) )
164
192
) ;
165
193
} ;
166
194
167
- export const mapAsStrings = ( ) => mapToObject ( 'asString' ) ;
168
- export const mapAsBooleans = ( ) => mapToObject ( 'asBoolean' ) ;
169
- export const mapAsNumbers = ( ) => mapToObject ( 'asNumber' ) ;
195
+ export function mapToObject ( ) : OperatorFunction < Parameter [ ] , { [ key :string ] : string } > ;
196
+ export function mapToObject ( as : 'numbers' ) : OperatorFunction < Parameter [ ] , { [ key :string ] : number } > ;
197
+ export function mapToObject ( as : 'booleans' ) : OperatorFunction < Parameter [ ] , { [ key :string ] : boolean } > ;
198
+ export function mapToObject ( as : 'strings' ) : OperatorFunction < Parameter [ ] , { [ key :string ] : string } > ;
199
+ export function mapToObject < T extends ConfigTemplate > ( template : T ) : OperatorFunction < Parameter [ ] , T & { [ key :string ] : string | undefined } > ;
200
+ export function mapToObject ( as : 'numbers' | 'booleans' | 'strings' | ConfigTemplate = 'strings' ) {
201
+ return pipe (
202
+ // TODO this is getting a little long, cleanup
203
+ map ( ( params : Parameter [ ] ) => params . reduce ( ( c , p ) => ( { ...c , [ p . key ] : typeof as === 'object' ? p [ typedMethod ( as [ p . key ] ) ] ( ) : p [ AS_TO_FN [ as ] ] ( ) } ) , typeof as === 'object' ? as : { } as { [ key :string ] : number | boolean | string } ) ) ,
204
+ distinctUntilChanged ( ( a , b ) => JSON . stringify ( a ) === JSON . stringify ( b ) )
205
+ ) ;
206
+ } ;
170
207
171
208
// TODO look into the types here, I don't like the anys
172
- const proxyAll = ( observable : Observable < Parameter [ ] > , fn : 'asNumber' | 'asBoolean' | 'asString' ) => new Proxy (
173
- observable . pipe ( mapToObject ( fn as any ) ) , {
174
- get : ( self , name :string ) => self [ name ] || self . pipe (
175
- map ( all => all [ name ] || PROXY_DEFAULTS [ fn ] ) ,
209
+ const proxyAll = ( observable : Observable < Parameter [ ] > , as : 'numbers' | 'booleans' | 'strings' ) => new Proxy (
210
+ observable . pipe ( mapToObject ( as as any ) ) , {
211
+ get : ( self , name :string ) => self [ name ] || observable . pipe (
212
+ map ( all => all . find ( p => p . key === name ) ) ,
213
+ map ( param => param ? param [ AS_TO_FN [ as ] ] ( ) : PROXY_DEFAULTS [ as ] ) ,
176
214
distinctUntilChanged ( )
177
215
)
178
216
}
0 commit comments