@@ -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
4866 // validate selectors
@@ -55,23 +73,66 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
5573 } ;
5674 if ( this . requestTracingEnabled ) {
5775 listOptions . requestOptions = {
58- customHeaders : this . customHeaders ( )
76+ customHeaders : this . customHeaders ( requestType )
5977 }
6078 }
6179
6280 const settings = this . client . listConfigurationSettings ( listOptions ) ;
6381
6482 for await ( const setting of settings ) {
6583 if ( setting . key ) {
66- const [ key , value ] = await this . processAdapters ( setting ) ;
67- const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
68- keyValues . push ( [ trimmedKey , value ] ) ;
84+ const keyValuePair = await this . processKeyValues ( setting ) ;
85+ keyValues . push ( keyValuePair ) ;
6986 }
7087 }
7188 }
7289 for ( const [ k , v ] of keyValues ) {
7390 this . set ( k , v ) ;
7491 }
92+ this . lastUpdateTimestamp = Date . now ( ) ;
93+ }
94+
95+ public async refresh ( ) : Promise < void > {
96+ // if no refreshOptions set, return
97+ if ( this . options ?. refreshOptions === undefined || this . options . refreshOptions . watchedSettings . length === 0 ) {
98+ return Promise . resolve ( ) ;
99+ }
100+ // if still within refresh interval, return
101+ const now = Date . now ( ) ;
102+ if ( now < this . lastUpdateTimestamp + this . refreshIntervalInMs ) {
103+ return Promise . resolve ( ) ;
104+ }
105+
106+ // try refresh if any of watched settings is changed.
107+ // TODO: watchedSettings as optional, etag based refresh if not specified.
108+ let needRefresh = false ;
109+ for ( const watchedSetting of this . options . refreshOptions . watchedSettings ) {
110+ const response = await this . client . getConfigurationSetting ( watchedSetting ) ;
111+ const [ key , value ] = await this . processKeyValues ( response ) ;
112+ if ( value !== this . get ( key ) ) {
113+ needRefresh = true ;
114+ break ;
115+ }
116+ }
117+ if ( needRefresh ) {
118+ await this . load ( RequestType . Watch ) ;
119+ // run callbacks in async
120+ for ( const listener of this . onRefreshListeners ) {
121+ listener ( ) ;
122+ }
123+ }
124+ }
125+
126+ public onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
127+ const boundedListener = listener . bind ( thisArg ) ;
128+ const remove = this . onRefreshListeners . push ( boundedListener ) ;
129+ return new Disposable ( remove ) ;
130+ }
131+
132+ private async processKeyValues ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
133+ const [ key , value ] = await this . processAdapters ( setting ) ;
134+ const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
135+ return [ trimmedKey , value ] ;
75136 }
76137
77138 private async processAdapters ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
@@ -94,17 +155,13 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
94155 return key ;
95156 }
96157
97- private enableRequestTracing ( ) {
98- this . correlationContextHeader = createCorrelationContextHeader ( this . options ) ;
99- }
100-
101- private customHeaders ( ) {
158+ private customHeaders ( requestType : RequestType ) {
102159 if ( ! this . requestTracingEnabled ) {
103160 return undefined ;
104161 }
105162
106163 const headers = { } ;
107- headers [ CorrelationContextHeaderName ] = this . correlationContextHeader ;
164+ headers [ CorrelationContextHeaderName ] = createCorrelationContextHeader ( this . options , requestType ) ;
108165 return headers ;
109166 }
110167}
0 commit comments