@@ -9,8 +9,11 @@ import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter";
99import { KeyFilter } from "./KeyFilter" ;
1010import { LabelFilter } from "./LabelFilter" ;
1111import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter" ;
12- import { CorrelationContextHeaderName } from "./requestTracing/constants" ;
12+ import { CorrelationContextHeaderName , RequestType } from "./requestTracing/constants" ;
1313import { createCorrelationContextHeader , requestTracingEnabled } from "./requestTracing/utils" ;
14+ import { DefaultRefreshIntervalInMs , MinimumRefreshIntervalInMs } from "./RefreshOptions" ;
15+ import { LinkedList } from "./common/linkedList" ;
16+ import { Disposable } from "./common/disposable" ;
1417
1518export class AzureAppConfigurationImpl extends Map < string , unknown > implements AzureAppConfiguration {
1619 private adapters : IKeyValueAdapter [ ] = [ ] ;
@@ -20,7 +23,10 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
2023 */
2124 private sortedTrimKeyPrefixes : string [ ] | undefined ;
2225 private readonly requestTracingEnabled : boolean ;
23- private correlationContextHeader : string | undefined ;
26+ // Refresh
27+ private refreshIntervalInMs : number ;
28+ private onRefreshListeners : LinkedList < ( ) => any > ;
29+ private lastUpdateTimestamp : number ;
2430
2531 constructor (
2632 private client : AppConfigurationClient ,
@@ -29,20 +35,32 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
2935 super ( ) ;
3036 // Enable request tracing if not opt-out
3137 this . requestTracingEnabled = requestTracingEnabled ( ) ;
32- if ( this . requestTracingEnabled ) {
33- this . enableRequestTracing ( ) ;
34- }
3538
3639 if ( options ?. trimKeyPrefixes ) {
3740 this . sortedTrimKeyPrefixes = [ ...options . trimKeyPrefixes ] . sort ( ( a , b ) => b . localeCompare ( a ) ) ;
3841 }
42+
43+ if ( options ?. refreshOptions ) {
44+ this . onRefreshListeners = new LinkedList ( ) ;
45+ this . refreshIntervalInMs = DefaultRefreshIntervalInMs ;
46+
47+ const refreshIntervalInMs = this . options ?. refreshOptions ?. refreshIntervalInMs ;
48+ if ( refreshIntervalInMs !== undefined ) {
49+ if ( refreshIntervalInMs < MinimumRefreshIntervalInMs ) {
50+ throw new Error ( `The refresh interval time cannot be less than ${ MinimumRefreshIntervalInMs } milliseconds.` ) ;
51+ } else {
52+ this . refreshIntervalInMs = refreshIntervalInMs ;
53+ }
54+ }
55+ }
56+
3957 // TODO: should add more adapters to process different type of values
4058 // feature flag, others
4159 this . adapters . push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions ) ) ;
4260 this . adapters . push ( new JsonKeyValueAdapter ( ) ) ;
4361 }
4462
45- public async load ( ) {
63+ public async load ( requestType : RequestType = RequestType . Startup ) {
4664 const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
4765 const selectors = this . options ?. selectors ?? [ { keyFilter : KeyFilter . Any , labelFilter : LabelFilter . Null } ] ;
4866 for ( const selector of selectors ) {
@@ -52,23 +70,66 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
5270 } ;
5371 if ( this . requestTracingEnabled ) {
5472 listOptions . requestOptions = {
55- customHeaders : this . customHeaders ( )
73+ customHeaders : this . customHeaders ( requestType )
5674 }
5775 }
5876
5977 const settings = this . client . listConfigurationSettings ( listOptions ) ;
6078
6179 for await ( const setting of settings ) {
6280 if ( setting . key ) {
63- const [ key , value ] = await this . processAdapters ( setting ) ;
64- const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
65- keyValues . push ( [ trimmedKey , value ] ) ;
81+ const keyValuePair = await this . processKeyValues ( setting ) ;
82+ keyValues . push ( keyValuePair ) ;
6683 }
6784 }
6885 }
6986 for ( const [ k , v ] of keyValues ) {
7087 this . set ( k , v ) ;
7188 }
89+ this . lastUpdateTimestamp = Date . now ( ) ;
90+ }
91+
92+ public async refresh ( ) : Promise < void > {
93+ // if no refreshOptions set, return
94+ if ( this . options ?. refreshOptions === undefined || this . options . refreshOptions . watchedSettings . length === 0 ) {
95+ return Promise . resolve ( ) ;
96+ }
97+ // if still within refresh interval, return
98+ const now = Date . now ( ) ;
99+ if ( now < this . lastUpdateTimestamp + this . refreshIntervalInMs ) {
100+ return Promise . resolve ( ) ;
101+ }
102+
103+ // try refresh if any of watched settings is changed.
104+ // TODO: watchedSettings as optional, etag based refresh if not specified.
105+ let needRefresh = false ;
106+ for ( const watchedSetting of this . options . refreshOptions . watchedSettings ) {
107+ const response = await this . client . getConfigurationSetting ( watchedSetting ) ;
108+ const [ key , value ] = await this . processKeyValues ( response ) ;
109+ if ( value !== this . get ( key ) ) {
110+ needRefresh = true ;
111+ break ;
112+ }
113+ }
114+ if ( needRefresh ) {
115+ await this . load ( RequestType . Watch ) ;
116+ // run callbacks in async
117+ for ( const listener of this . onRefreshListeners ) {
118+ listener ( ) ;
119+ }
120+ }
121+ }
122+
123+ public onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
124+ const boundedListener = listener . bind ( thisArg ) ;
125+ const remove = this . onRefreshListeners . push ( boundedListener ) ;
126+ return new Disposable ( remove ) ;
127+ }
128+
129+ private async processKeyValues ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
130+ const [ key , value ] = await this . processAdapters ( setting ) ;
131+ const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
132+ return [ trimmedKey , value ] ;
72133 }
73134
74135 private async processAdapters ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
@@ -91,17 +152,13 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
91152 return key ;
92153 }
93154
94- private enableRequestTracing ( ) {
95- this . correlationContextHeader = createCorrelationContextHeader ( this . options ) ;
96- }
97-
98- private customHeaders ( ) {
155+ private customHeaders ( requestType : RequestType ) {
99156 if ( ! this . requestTracingEnabled ) {
100157 return undefined ;
101158 }
102159
103160 const headers = { } ;
104- headers [ CorrelationContextHeaderName ] = this . correlationContextHeader ;
161+ headers [ CorrelationContextHeaderName ] = createCorrelationContextHeader ( this . options , requestType ) ;
105162 return headers ;
106163 }
107164}
0 commit comments