@@ -77,10 +77,11 @@ type SettingSelectorCollection = {
7777
7878 /**
7979 * This is used to append to the request url for breaking the CDN cache.
80- * It is a hash value calculated from all page etags.
81- * When the refresh is based on watched settings, the hash value will be calculated from the etags of all watched settings.
80+ * It uses the etag which has changed after the last refresh.
81+ * It can either be the page etag or etag of a watched setting depending on the refresh monitoring strategy.
82+ * When a watched setting is deleted, the token value will be SHA-256 hash of `ResourceDeleted\n{previous-etag}`.
8283 */
83- version ?: string ;
84+ cdnCacheConsistencyToken ?: string ;
8485}
8586
8687export class AzureAppConfigurationImpl implements AzureAppConfiguration {
@@ -413,31 +414,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
413414 if ( this . #featureFlagEnabled) {
414415 await this . #loadFeatureFlags( ) ;
415416 }
416-
417- if ( this . #isCdnUsed) {
418- if ( this . #watchAll) { // collection monitoring based refresh
419- // use the first page etag of the first kv selector
420- const defaultSelector = this . #kvSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
421- if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
422- this . #kvSelectorCollection. version = defaultSelector . pageEtags ! [ 0 ] ;
423- } else {
424- this . #kvSelectorCollection. version = undefined ;
425- }
426- } else if ( this . #refreshEnabled) { // watched settings based refresh
427- // use the etag of the first watched setting (sentinel)
428- this . #kvSelectorCollection. version = this . #sentinels. find ( s => s . etag !== undefined ) ?. etag ;
429- }
430-
431- if ( this . #featureFlagRefreshEnabled) {
432- const defaultSelector = this . #ffSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
433- if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
434- this . #ffSelectorCollection. version = defaultSelector . pageEtags ! [ 0 ] ;
435- } else {
436- this . #ffSelectorCollection. version = undefined ;
437- }
438- }
439- }
440-
441417 this . #isInitialLoadCompleted = true ;
442418 break ;
443419 } catch ( error ) {
@@ -520,7 +496,6 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
520496 const selectorsToUpdate : PagedSettingSelector [ ] = JSON . parse (
521497 JSON . stringify ( selectorCollection . selectors )
522498 ) ;
523-
524499 for ( const selector of selectorsToUpdate ) {
525500 if ( selector . snapshotName === undefined ) {
526501 let listOptions : ListConfigurationSettingsOptions = {
@@ -529,13 +504,12 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
529504 } ;
530505
531506 // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
532- if ( this . #isCdnUsed) {
507+ if ( this . #isCdnUsed && selectorCollection . cdnCacheConsistencyToken ) {
533508 listOptions = {
534509 ...listOptions ,
535- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . version ?? "" } }
510+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . cdnCacheConsistencyToken } }
536511 } ;
537512 }
538-
539513 const pageEtags : string [ ] = [ ] ;
540514 const pageIterator = listConfigurationSettingsWithTrace (
541515 this . #requestTraceOptions,
@@ -630,7 +604,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
630604 }
631605
632606 /**
633- * Updates etag of watched settings from loaded data. If a watched setting is not covered by any selector, a request will be sent to retrieve it.
607+ * Updates etag of watched settings from loaded data.
608+ * If a watched setting is not covered by any selector, a request will be sent to retrieve it.
609+ * If there is no watched setting(sentinel key), this method does nothing.
634610 */
635611 async #updateWatchedKeyValuesEtag( loadedSettings : ConfigurationSetting [ ] ) : Promise < void > {
636612 for ( const sentinel of this . #sentinels) {
@@ -641,8 +617,8 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
641617 // Send a request to retrieve watched key-value since it may be either not loaded or loaded with a different selector
642618 // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
643619 let getOptions : GetConfigurationSettingOptions = { } ;
644- if ( this . #isCdnUsed) {
645- getOptions = { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. version ?? "" } } } ;
620+ if ( this . #isCdnUsed && this . #kvSelectorCollection . cdnCacheConsistencyToken ) {
621+ getOptions = { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. cdnCacheConsistencyToken } } } ;
646622 }
647623 const response = await this . #getConfigurationSetting( sentinel , getOptions ) ;
648624 sentinel . etag = response ?. etag ;
@@ -699,26 +675,27 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
699675 }
700676 // if watchAll is true, there should be no sentinels
701677 for ( const sentinel of this . #sentinels. values ( ) ) {
702- // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
678+ // if CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
703679 let getOptions : GetConfigurationSettingOptions = { } ;
704- if ( this . #isCdnUsed) {
705- // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
680+ if ( this . #isCdnUsed && this . #kvSelectorCollection . cdnCacheConsistencyToken ) {
681+ // if CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
706682 getOptions = {
707- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. version ?? "" } } ,
708- } ;
709- } else {
710- // if CDN is not used, send conditional request
711- getOptions = {
712- onlyIfChanged : true
683+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. cdnCacheConsistencyToken ?? "" } } ,
713684 } ;
714685 }
715- const response = await this . #getConfigurationSetting( sentinel , getOptions ) ;
686+ // send conditional request only when CDN is not used
687+ const response = await this . #getConfigurationSetting( sentinel , { ...getOptions , onlyIfChanged : ! this . #isCdnUsed } ) ;
716688
717689 if ( ( response ?. statusCode === 200 && sentinel . etag !== response ?. etag ) ||
718690 ( response === undefined && sentinel . etag !== undefined ) // deleted
719691 ) {
720- sentinel . etag = response ?. etag ; // update etag of the sentinel
721- this . #kvSelectorCollection. version = sentinel . etag ;
692+ if ( response === undefined ) {
693+ this . #kvSelectorCollection. cdnCacheConsistencyToken =
694+ await this . #calculateResourceDeletedCacheConsistencyToken( sentinel . etag ! ) ;
695+ } else {
696+ this . #kvSelectorCollection. cdnCacheConsistencyToken = response . etag ;
697+ }
698+ sentinel . etag = response ?. etag ; // update etag of the sentinel
722699 needRefresh = true ;
723700 break ;
724701 }
@@ -767,17 +744,17 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
767744 labelFilter : selector . labelFilter
768745 } ;
769746
770- if ( this . #isCdnUsed) {
771- // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
747+ if ( ! this . #isCdnUsed) {
748+ // if CDN is not used, add page etags to the listOptions to send conditional request
772749 listOptions = {
773750 ...listOptions ,
774- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . version ?? "" } }
751+ pageEtags : selector . pageEtags
775752 } ;
776- } else {
777- // if CDN is not used, add page etags to the listOptions to send conditional request
753+ } else if ( selectorCollection . cdnCacheConsistencyToken ) {
754+ // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
778755 listOptions = {
779756 ...listOptions ,
780- pageEtags : selector . pageEtags
757+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . cdnCacheConsistencyToken } }
781758 } ;
782759 }
783760
@@ -788,24 +765,25 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
788765 ) . byPage ( ) ;
789766
790767 if ( selector . pageEtags === undefined || selector . pageEtags . length === 0 ) {
791- selectorCollection . version = undefined ;
792768 return true ; // no etag is retrieved from previous request, always refresh
793769 }
794770
795771 let i = 0 ;
796772 for await ( const page of pageIterator ) {
797773 if ( i >= selector . pageEtags . length || // new page
798774 ( page . _response . status === 200 && page . etag !== selector . pageEtags [ i ] ) ) { // page changed
775+ // 100 kvs will return two pages, one page with 100 items and another empty page
776+ // kv collection change will always be detected by page etag change
799777 if ( this . #isCdnUsed) {
800- selectorCollection . version = page . etag ;
778+ selectorCollection . cdnCacheConsistencyToken = page . etag ;
801779 }
802780 return true ;
803781 }
804782 i ++ ;
805783 }
806784 if ( i !== selector . pageEtags . length ) { // page removed
807785 if ( this . #isCdnUsed) {
808- selectorCollection . version = selector . pageEtags [ i ] ;
786+ selectorCollection . cdnCacheConsistencyToken = selector . pageEtags [ i ] ;
809787 }
810788 return true ;
811789 }
@@ -1095,11 +1073,9 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
10951073 }
10961074 }
10971075
1098- async #calculteCacheConsistencyToken ( etags : string [ ] ) : Promise < string > {
1076+ async #calculateResourceDeletedCacheConsistencyToken ( etag : string ) : Promise < string > {
10991077 const crypto = getCryptoModule ( ) ;
1100- const sortedEtags = etags . sort ( ) ;
1101- const rawString = "CacheConsistency\n" + sortedEtags . join ( "\n" ) ;
1102- // Convert to UTF-8 encoded bytes
1078+ const rawString = `ResourceDeleted\n${ etag } ` ;
11031079 const payload = new TextEncoder ( ) . encode ( rawString ) ;
11041080 // In the browser or Node.js 18+, use crypto.subtle.digest
11051081 if ( crypto . subtle ) {
0 commit comments