11import { 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' ;
44import { FirebaseAppConfig , FirebaseOptions , ɵlazySDKProxy , FIREBASE_OPTIONS , FIREBASE_APP_NAME } from '@angular/fire' ;
55import { remoteConfig } from 'firebase/app' ;
66
7- export interface DefaultConfig { [ key :string ] : string | number | boolean } ;
7+ export interface ConfigTemplate { [ key :string ] : string | number | boolean } ;
88
99export 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' ) ;
1111
1212import { FirebaseRemoteConfig , _firebaseAppFactory , runOutsideAngular } from '@angular/fire' ;
1313import { isPlatformServer } from '@angular/common' ;
@@ -65,15 +65,15 @@ export class AngularFireRemoteConfig {
6565
6666 readonly changes : Observable < Parameter > ;
6767 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 > } ;
7171
7272 constructor (
7373 @Inject ( FIREBASE_OPTIONS ) options :FirebaseOptions ,
7474 @Optional ( ) @Inject ( FIREBASE_APP_NAME ) nameOrConfig :string | FirebaseAppConfig | null | undefined ,
7575 @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 ,
7777 @Inject ( PLATFORM_ID ) platformId :Object ,
7878 private zone : NgZone
7979 ) {
@@ -123,9 +123,9 @@ export class AngularFireRemoteConfig {
123123 ) )
124124 ) ;
125125
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 ' ) ;
129129
130130 // TODO fix the proxy for server
131131 return isPlatformServer ( platformId ) ? this : ɵlazySDKProxy ( this , remoteConfig$ , zone ) ;
@@ -136,7 +136,7 @@ export class AngularFireRemoteConfig {
136136// I ditched loading the defaults into RC and a simple map for scan since we already have our own defaults implementation.
137137// 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.
138138// 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 (
140140 withLatestFrom ( remoteConfig ) ,
141141 scan ( ( existing , [ all , rc ] ) => {
142142 // SEMVER use "new Set" to unique once we're only targeting es6
@@ -151,28 +151,66 @@ const scanToParametersArray = (remoteConfig: Observable<remoteConfig.RemoteConfi
151151 } , [ ] as Array < Parameter > )
152152) ;
153153
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+ } ;
156179
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' ) {
161186 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 ) ,
163191 distinctUntilChanged ( ( a , b ) => JSON . stringify ( a ) === JSON . stringify ( b ) )
164192 ) ;
165193} ;
166194
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+ } ;
170207
171208// 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 ] ) ,
176214 distinctUntilChanged ( )
177215 )
178216 }
0 commit comments