11// Copyright (c) Microsoft Corporation.
22// Licensed under the MIT license.
33
4- import { AppConfigurationClient , ConfigurationSetting , ListConfigurationSettingsOptions } from "@azure/app-configuration" ;
4+ import { AppConfigurationClient , ConfigurationSetting , ConfigurationSettingId , ListConfigurationSettingsOptions } from "@azure/app-configuration" ;
55import { AzureAppConfiguration } from "./AzureAppConfiguration" ;
66import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions" ;
77import { IKeyValueAdapter } from "./IKeyValueAdapter" ;
@@ -24,9 +24,10 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
2424 private sortedTrimKeyPrefixes : string [ ] | undefined ;
2525 private readonly requestTracingEnabled : boolean ;
2626 // Refresh
27- private refreshIntervalInMs : number ;
28- private onRefreshListeners : LinkedList < ( ) => any > ;
27+ private refreshIntervalInMs : number | undefined ;
28+ private onRefreshListeners : LinkedList < ( ) => any > | undefined ;
2929 private lastUpdateTimestamp : number ;
30+ private sentinels : ConfigurationSettingId [ ] | undefined ;
3031
3132 constructor (
3233 private client : AppConfigurationClient ,
@@ -52,6 +53,15 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
5253 this . refreshIntervalInMs = refreshIntervalInMs ;
5354 }
5455 }
56+
57+ this . sentinels = options . refreshOptions . watchedSettings ?. map ( setting => {
58+ const key = setting . key ;
59+ const label = setting . label ;
60+ if ( key . includes ( "*" ) || label ?. includes ( "*" ) ) {
61+ throw new Error ( "Wildcard key or label filters are not supported for refresh." ) ;
62+ }
63+ return { key, label } ;
64+ } ) ;
5565 }
5666
5767 // TODO: should add more adapters to process different type of values
@@ -81,6 +91,11 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
8191 const keyValuePair = await this . processKeyValues ( setting ) ;
8292 keyValues . push ( keyValuePair ) ;
8393 }
94+ // update etag of sentinels
95+ const matchedSentinel = this . sentinels ?. find ( s => s . key === setting . key && ( s . label ?? null ) === setting . label ) ; // Workaround: as undefined label represents the same with null.
96+ if ( matchedSentinel ) {
97+ matchedSentinel . etag = setting . etag ;
98+ }
8499 }
85100 }
86101 for ( const [ k , v ] of keyValues ) {
@@ -91,7 +106,7 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
91106
92107 public async refresh ( ) : Promise < void > {
93108 // if no refreshOptions set, return
94- if ( this . options ?. refreshOptions === undefined || this . options . refreshOptions . watchedSettings . length === 0 ) {
109+ if ( this . sentinels === undefined || this . sentinels . length === 0 || this . refreshIntervalInMs === undefined ) {
95110 return Promise . resolve ( ) ;
96111 }
97112 // if still within refresh interval, return
@@ -101,26 +116,35 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
101116 }
102117
103118 // try refresh if any of watched settings is changed.
104- // TODO: watchedSettings as optional, etag based refresh if not specified.
105119 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 ) ) {
120+ for ( const sentinel of this . sentinels ) {
121+ const response = await this . client . getConfigurationSetting ( sentinel , {
122+ onlyIfChanged : true
123+ // TODO: do we trace this request by adding custom headers?
124+ } ) ;
125+ if ( response . statusCode !== 304 ) { // TODO: can be more robust, e.g. === 200?
126+ // sentinel changed.
127+ sentinel . etag = response . etag ; // update etag of the sentinel
110128 needRefresh = true ;
111129 break ;
112130 }
113131 }
114132 if ( needRefresh ) {
115133 await this . load ( RequestType . Watch ) ;
116134 // run callbacks in async
117- for ( const listener of this . onRefreshListeners ) {
118- listener ( ) ;
135+ if ( this . onRefreshListeners !== undefined ) {
136+ for ( const listener of this . onRefreshListeners ) {
137+ listener ( ) ;
138+ }
119139 }
120140 }
121141 }
122142
123143 public onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
144+ if ( this . onRefreshListeners === undefined ) {
145+ // TODO: Add unit tests
146+ throw new Error ( "Illegal operation because refreshOptions is not provided on loading." ) ;
147+ }
124148 const boundedListener = listener . bind ( thisArg ) ;
125149 const remove = this . onRefreshListeners . push ( boundedListener ) ;
126150 return new Disposable ( remove ) ;
0 commit comments