@@ -40,13 +40,17 @@ import { ETAG_LOOKUP_HEADER } from "./EtagUrlPipelinePolicy.js";
4040
4141type PagedSettingSelector = SettingSelector & {
4242 pageEtags ?: string [ ] ;
43+ } ;
44+
45+ type SettingSelectorCollection = {
46+ selectors : PagedSettingSelector [ ] ;
4347
4448 /**
45- * The etag which has changed after the last refresh. This is used to break the CDN cache.
49+ * The etag which has changed after the last refresh. This is used to append to the request url for breaking the CDN cache.
4650 * It can either be a page etag or etag of a watched setting.
4751 */
48- latestEtag ?: string ;
49- } ;
52+ etagToBreakCdnCache ?: string ;
53+ }
5054
5155export class AzureAppConfigurationImpl implements AzureAppConfiguration {
5256 /**
@@ -85,22 +89,17 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
8589 /**
8690 * Selectors of key-values obtained from @see AzureAppConfigurationOptions.selectors
8791 */
88- #kvSelectors: PagedSettingSelector [ ] = [ ] ;
92+ #kvSelectorCollection: SettingSelectorCollection = { selectors : [ ] } ;
8993 /**
9094 * Selectors of feature flags obtained from @see AzureAppConfigurationOptions.featureFlagOptions.selectors
9195 */
92- #ffSelectors: PagedSettingSelector [ ] = [ ] ;
96+ #ffSelectorCollection: SettingSelectorCollection = { selectors : [ ] } ;
9397
9498 // Load balancing
9599 #lastSuccessfulEndpoint: string = "" ;
96100
97101 // CDN
98102 #isCdnUsed: boolean ;
99- /**
100- * The etag of a watched setting which has changed after the last refresh. This is used to break the CDN cache.
101- * This property will not be used when using key value collection based refresh. It could only be used during updateWatchedKeyValuesEtag and refreshKeyValues.
102- */
103- #latestEtag?: string ;
104103
105104 constructor (
106105 clientManager : ConfigurationClientManager ,
@@ -145,12 +144,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
145144 this . #kvRefreshTimer = new RefreshTimer ( this . #kvRefreshInterval) ;
146145 }
147146
148- this . #kvSelectors = getValidKeyValueSelectors ( options ?. selectors ) ;
147+ this . #kvSelectorCollection . selectors = getValidKeyValueSelectors ( options ?. selectors ) ;
149148
150149 // feature flag options
151150 if ( options ?. featureFlagOptions ?. enabled ) {
152151 // validate feature flag selectors
153- this . #ffSelectors = getValidFeatureFlagSelectors ( options . featureFlagOptions . selectors ) ;
152+ this . #ffSelectorCollection . selectors = getValidFeatureFlagSelectors ( options . featureFlagOptions . selectors ) ;
154153
155154 if ( options . featureFlagOptions . refresh ?. enabled ) {
156155 const { refreshIntervalInMs } = options . featureFlagOptions . refresh ;
@@ -233,19 +232,36 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
233232 */
234233 async load ( ) {
235234 await this . #loadSelectedAndWatchedKeyValues( ) ;
236- if ( this . #watchAll) {
237- this . #kvSelectors. forEach ( selector => selector . latestEtag = selector . pageEtags ? selector . pageEtags [ 0 ] : undefined ) ;
238- } else if ( this . #refreshEnabled) {
239- this . #latestEtag = this . #sentinels. find ( s => s . etag !== undefined ) ?. etag ;
240- }
241235
242236 if ( this . #featureFlagEnabled) {
243237 await this . #loadFeatureFlags( ) ;
238+ }
239+
240+ if ( this . #isCdnUsed) {
241+ if ( this . #watchAll) { // collection monitoring based refresh
242+ // use the first page etag of the first kv selector
243+ const defaultSelector = this . #kvSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
244+ if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
245+ this . #kvSelectorCollection. etagToBreakCdnCache = defaultSelector . pageEtags ! [ 0 ] ;
246+ } else {
247+ this . #kvSelectorCollection. etagToBreakCdnCache = undefined ;
248+ }
249+ } else if ( this . #refreshEnabled) { // watched settings based refresh
250+ // use the etag of the first watched setting (sentinel)
251+ this . #kvSelectorCollection. etagToBreakCdnCache = this . #sentinels. find ( s => s . etag !== undefined ) ?. etag ;
252+ }
253+
244254 if ( this . #featureFlagRefreshEnabled) {
245- this . #ffSelectors. forEach ( selector => selector . latestEtag = selector . pageEtags ? selector . pageEtags [ 0 ] : undefined ) ;
255+ const defaultSelector = this . #ffSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
256+ if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
257+ this . #ffSelectorCollection. etagToBreakCdnCache = defaultSelector . pageEtags ! [ 0 ] ;
258+ } else {
259+ this . #ffSelectorCollection. etagToBreakCdnCache = undefined ;
260+ }
246261 }
247262 }
248- // Mark all settings have loaded at startup.
263+
264+ // mark all settings have loaded at startup.
249265 this . #isInitialLoadCompleted = true ;
250266 }
251267
@@ -369,12 +385,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
369385 * If false, loads key-value using the key-value selectors. Defaults to false.
370386 */
371387 async #loadConfigurationSettings( loadFeatureFlag : boolean = false ) : Promise < ConfigurationSetting [ ] > {
372- const selectors = loadFeatureFlag ? this . #ffSelectors : this . #kvSelectors ;
388+ const selectorCollection = loadFeatureFlag ? this . #ffSelectorCollection : this . #kvSelectorCollection ;
373389 const funcToExecute = async ( client ) => {
374390 const loadedSettings : ConfigurationSetting [ ] = [ ] ;
375391 // deep copy selectors to avoid modification if current client fails
376- const selectorsToUpdate = JSON . parse (
377- JSON . stringify ( selectors )
392+ const selectorsToUpdate : PagedSettingSelector [ ] = JSON . parse (
393+ JSON . stringify ( selectorCollection . selectors )
378394 ) ;
379395
380396 for ( const selector of selectorsToUpdate ) {
@@ -383,19 +399,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
383399 labelFilter : selector . labelFilter ,
384400 } ;
385401
402+ // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
386403 if ( this . #isCdnUsed) {
387- // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
388- if ( this . #watchAll && selector . latestEtag ) {
389- listOptions = {
390- ...listOptions ,
391- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selector . latestEtag } }
392- } ;
393- } else if ( this . #latestEtag) {
394- listOptions = {
395- ...listOptions ,
396- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #latestEtag } }
397- } ;
398- }
404+ listOptions = {
405+ ...listOptions ,
406+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . etagToBreakCdnCache ?? "" } }
407+ } ;
399408 }
400409
401410 const pageEtags : string [ ] = [ ] ;
@@ -405,21 +414,21 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
405414 listOptions
406415 ) . byPage ( ) ;
407416 for await ( const page of pageIterator ) {
408- pageEtags . push ( page . etag ?? "" ) ;
417+ pageEtags . push ( page . etag ?? "" ) ; // pageEtags is string[]
409418 for ( const setting of page . items ) {
410419 if ( loadFeatureFlag === isFeatureFlag ( setting ) ) {
411420 loadedSettings . push ( setting ) ;
412421 }
413422 }
414423 }
424+
425+ if ( pageEtags . length === 0 ) {
426+ console . warn ( `No page is found in the response of listing key-value selector: key=${ selector . keyFilter } and label=${ selector . labelFilter } .` ) ;
427+ }
415428 selector . pageEtags = pageEtags ;
416429 }
417430
418- if ( loadFeatureFlag ) {
419- this . #ffSelectors = selectorsToUpdate ;
420- } else {
421- this . #kvSelectors = selectorsToUpdate ;
422- }
431+ selectorCollection . selectors = selectorsToUpdate ;
423432 return loadedSettings ;
424433 } ;
425434
@@ -457,15 +466,13 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
457466 if ( matchedSetting ) {
458467 sentinel . etag = matchedSetting . etag ;
459468 } else {
460- // Send a request to retrieve key-value since it may be either not loaded or loaded with a different label or different casing
469+ // Send a request to retrieve watched key-value since it may be either not loaded or loaded with a different selector
461470 // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
462- const getOptions = this . #isCdnUsed && this . #latestEtag ? { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #latestEtag } } } : { } ;
471+ const getOptions = this . #isCdnUsed ?
472+ { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. etagToBreakCdnCache ?? "" } } } :
473+ { } ;
463474 const response = await this . #getConfigurationSetting( sentinel , { ...getOptions , onlyIfChanged : false } ) ; // always send non-conditional request
464- if ( response ) {
465- sentinel . etag = response . etag ;
466- } else {
467- sentinel . etag = undefined ;
468- }
475+ sentinel . etag = response ?. etag ;
469476 }
470477 }
471478 }
@@ -510,18 +517,20 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
510517 // try refresh if any of watched settings is changed.
511518 let needRefresh = false ;
512519 if ( this . #watchAll) {
513- needRefresh = await this . #checkConfigurationSettingsChange( this . #kvSelectors ) ;
520+ needRefresh = await this . #checkConfigurationSettingsChange( this . #kvSelectorCollection ) ;
514521 }
515522 for ( const sentinel of this . #sentinels. values ( ) ) {
516523 // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
517- const getOptions = this . #isCdnUsed && this . #latestEtag ? { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #latestEtag } } } : { } ;
518- const response = await this . #getConfigurationSetting( sentinel , { ...getOptions , onlyIfChanged : ! this . #isCdnUsed} ) ; // if CDN is used, do not send conditional request
524+ const getOptions = this . #isCdnUsed ?
525+ { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. etagToBreakCdnCache ?? "" } } } :
526+ { } ;
527+ const response = await this . #getConfigurationSetting( sentinel , { ...getOptions , onlyIfChanged : ! this . #isCdnUsed } ) ; // if CDN is used, do not send conditional request
519528
520529 if ( ( response ?. statusCode === 200 && sentinel . etag !== response ?. etag ) ||
521530 ( response === undefined && sentinel . etag !== undefined ) // deleted
522531 ) {
523532 sentinel . etag = response ?. etag ; // update etag of the sentinel
524- this . #latestEtag = response ? .etag ; // record the last changed etag
533+ this . #kvSelectorCollection . etagToBreakCdnCache = sentinel . etag ;
525534 needRefresh = true ;
526535 break ;
527536 }
@@ -545,7 +554,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
545554 return Promise . resolve ( false ) ;
546555 }
547556
548- const needRefresh = await this . #checkConfigurationSettingsChange( this . #ffSelectors ) ;
557+ const needRefresh = await this . #checkConfigurationSettingsChange( this . #ffSelectorCollection ) ;
549558 if ( needRefresh ) {
550559 await this . #loadFeatureFlags( ) ;
551560 }
@@ -556,40 +565,53 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
556565
557566 /**
558567 * Checks whether the key-value collection has changed.
559- * @param selectors - The @see PagedSettingSelector of the kev-value collection.
568+ * @param selectorCollection - The @see SettingSelectorCollection of the kev-value collection.
560569 * @returns true if key-value collection has changed, false otherwise.
561570 */
562- async #checkConfigurationSettingsChange( selectors : PagedSettingSelector [ ] ) : Promise < boolean > {
571+ async #checkConfigurationSettingsChange( selectorCollection : SettingSelectorCollection ) : Promise < boolean > {
563572 const funcToExecute = async ( client ) => {
564- for ( const selector of selectors ) {
565- const listOptions : ListConfigurationSettingsOptions = {
573+ for ( const selector of selectorCollection . selectors ) {
574+ let listOptions : ListConfigurationSettingsOptions = {
566575 keyFilter : selector . keyFilter ,
567- labelFilter : selector . labelFilter ,
568- ...( ! this . #isCdnUsed && { pageEtags : selector . pageEtags } ) , // if CDN is used, do not send conditional request
569- // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
570- ...( this . #isCdnUsed && selector . latestEtag && { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selector . latestEtag } } } )
576+ labelFilter : selector . labelFilter
571577 } ;
572578
579+ if ( this . #isCdnUsed) {
580+ // If cdn is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
581+ listOptions = {
582+ ...listOptions ,
583+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . etagToBreakCdnCache ?? "" } } } ;
584+ } else {
585+ // send conditional request if cdn is not used
586+ listOptions = { ...listOptions , pageEtags : selector . pageEtags } ;
587+ }
588+
573589 const pageIterator = listConfigurationSettingsWithTrace (
574590 this . #requestTraceOptions,
575591 client ,
576592 listOptions
577593 ) . byPage ( ) ;
578594
579595 if ( selector . pageEtags === undefined || selector . pageEtags . length === 0 ) {
596+ selectorCollection . etagToBreakCdnCache = undefined ;
580597 return true ; // no etag, always refresh
581598 }
582599
583600 let i = 0 ;
584601 for await ( const page of pageIterator ) {
585602 if ( i > selector . pageEtags . length + 1 || // new page
586603 ( page . _response . status === 200 && page . etag !== selector . pageEtags [ i ] ) ) { // page changed
587- selector . latestEtag = page . etag ; // record the last changed etag
604+ if ( this . #isCdnUsed) {
605+ selectorCollection . etagToBreakCdnCache = page . etag ;
606+ }
588607 return true ;
589608 }
590609 i ++ ;
591610 }
592611 if ( i !== selector . pageEtags . length ) { // page removed
612+ if ( this . #isCdnUsed) {
613+ selectorCollection . etagToBreakCdnCache = selector . pageEtags [ i ] ;
614+ }
593615 return true ;
594616 }
595617 }
0 commit comments