@@ -23,7 +23,7 @@ import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter.js";
2323import { DEFAULT_STARTUP_TIMEOUT_IN_MS } from "./StartupOptions.js" ;
2424import { DEFAULT_REFRESH_INTERVAL_IN_MS , MIN_REFRESH_INTERVAL_IN_MS } from "./refresh/refreshOptions.js" ;
2525import { Disposable } from "./common/disposable.js" ;
26- import { base64Helper , jsonSorter } from "./common/utils.js" ;
26+ import { base64Helper , jsonSorter , getCryptoModule } from "./common/utils.js" ;
2727import {
2828 FEATURE_FLAGS_KEY_NAME ,
2929 FEATURE_MANAGEMENT_KEY_NAME ,
@@ -77,9 +77,10 @@ type SettingSelectorCollection = {
7777
7878 /**
7979 * This is used to append to the request url for breaking the CDN cache.
80- * It uses the etag which has changed after the last refresh. It can either be a page etag or etag of a watched setting.
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.
8182 */
82- cdnCacheBreakString ?: string ;
83+ version ?: string ;
8384}
8485
8586export class AzureAppConfigurationImpl implements AzureAppConfiguration {
@@ -418,21 +419,21 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
418419 // use the first page etag of the first kv selector
419420 const defaultSelector = this . #kvSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
420421 if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
421- this . #kvSelectorCollection. cdnCacheBreakString = defaultSelector . pageEtags ! [ 0 ] ;
422+ this . #kvSelectorCollection. version = defaultSelector . pageEtags ! [ 0 ] ;
422423 } else {
423- this . #kvSelectorCollection. cdnCacheBreakString = undefined ;
424+ this . #kvSelectorCollection. version = undefined ;
424425 }
425426 } else if ( this . #refreshEnabled) { // watched settings based refresh
426427 // use the etag of the first watched setting (sentinel)
427- this . #kvSelectorCollection. cdnCacheBreakString = this . #sentinels. find ( s => s . etag !== undefined ) ?. etag ;
428+ this . #kvSelectorCollection. version = this . #sentinels. find ( s => s . etag !== undefined ) ?. etag ;
428429 }
429430
430431 if ( this . #featureFlagRefreshEnabled) {
431432 const defaultSelector = this . #ffSelectorCollection. selectors . find ( s => s . pageEtags !== undefined ) ;
432433 if ( defaultSelector && defaultSelector . pageEtags ! . length > 0 ) {
433- this . #ffSelectorCollection. cdnCacheBreakString = defaultSelector . pageEtags ! [ 0 ] ;
434+ this . #ffSelectorCollection. version = defaultSelector . pageEtags ! [ 0 ] ;
434435 } else {
435- this . #ffSelectorCollection. cdnCacheBreakString = undefined ;
436+ this . #ffSelectorCollection. version = undefined ;
436437 }
437438 }
438439 }
@@ -531,7 +532,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
531532 if ( this . #isCdnUsed) {
532533 listOptions = {
533534 ...listOptions ,
534- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . cdnCacheBreakString ?? "" } }
535+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . version ?? "" } }
535536 } ;
536537 }
537538
@@ -641,7 +642,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
641642 // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
642643 let getOptions : GetConfigurationSettingOptions = { } ;
643644 if ( this . #isCdnUsed) {
644- getOptions = { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. cdnCacheBreakString ?? "" } } } ;
645+ getOptions = { requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. version ?? "" } } } ;
645646 }
646647 const response = await this . #getConfigurationSetting( sentinel , getOptions ) ;
647648 sentinel . etag = response ?. etag ;
@@ -703,7 +704,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
703704 if ( this . #isCdnUsed) {
704705 // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
705706 getOptions = {
706- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. cdnCacheBreakString ?? "" } } ,
707+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : this . #kvSelectorCollection. version ?? "" } } ,
707708 } ;
708709 } else {
709710 // if CDN is not used, send conditional request
@@ -717,7 +718,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
717718 ( response === undefined && sentinel . etag !== undefined ) // deleted
718719 ) {
719720 sentinel . etag = response ?. etag ; // update etag of the sentinel
720- this . #kvSelectorCollection. cdnCacheBreakString = sentinel . etag ;
721+ this . #kvSelectorCollection. version = sentinel . etag ;
721722 needRefresh = true ;
722723 break ;
723724 }
@@ -770,7 +771,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
770771 // If CDN is used, add etag to request header so that the pipeline policy can retrieve and append it to the request URL
771772 listOptions = {
772773 ...listOptions ,
773- requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . cdnCacheBreakString ?? "" } }
774+ requestOptions : { customHeaders : { [ ETAG_LOOKUP_HEADER ] : selectorCollection . version ?? "" } }
774775 } ;
775776 } else {
776777 // if CDN is not used, add page etags to the listOptions to send conditional request
@@ -787,7 +788,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
787788 ) . byPage ( ) ;
788789
789790 if ( selector . pageEtags === undefined || selector . pageEtags . length === 0 ) {
790- selectorCollection . cdnCacheBreakString = undefined ;
791+ selectorCollection . version = undefined ;
791792 return true ; // no etag is retrieved from previous request, always refresh
792793 }
793794
@@ -796,15 +797,15 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
796797 if ( i >= selector . pageEtags . length || // new page
797798 ( page . _response . status === 200 && page . etag !== selector . pageEtags [ i ] ) ) { // page changed
798799 if ( this . #isCdnUsed) {
799- selectorCollection . cdnCacheBreakString = page . etag ;
800+ selectorCollection . version = page . etag ;
800801 }
801802 return true ;
802803 }
803804 i ++ ;
804805 }
805806 if ( i !== selector . pageEtags . length ) { // page removed
806807 if ( this . #isCdnUsed) {
807- selectorCollection . cdnCacheBreakString = selector . pageEtags [ i ] ;
808+ selectorCollection . version = selector . pageEtags [ i ] ;
808809 }
809810 return true ;
810811 }
@@ -1070,57 +1071,50 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
10701071 }
10711072 }
10721073
1073- let crypto ;
1074-
1075- // Check for browser environment
1076- if ( typeof window !== "undefined" && window . crypto && window . crypto . subtle ) {
1077- crypto = window . crypto ;
1078- }
1079- // Check for Node.js environment
1080- else if ( typeof global !== "undefined" && global . crypto ) {
1081- crypto = global . crypto ;
1082- }
1083- // Fallback to native Node.js crypto module
1084- else {
1085- try {
1086- if ( typeof module !== "undefined" && module . exports ) {
1087- crypto = require ( "crypto" ) ;
1088- }
1089- else {
1090- crypto = await import ( "crypto" ) ;
1091- }
1092- } catch ( error ) {
1093- console . error ( "Failed to load the crypto module:" , error . message ) ;
1094- throw error ;
1095- }
1096- }
1097-
1074+ const crypto = getCryptoModule ( ) ;
10981075 // Convert to UTF-8 encoded bytes
1099- const data = new TextEncoder ( ) . encode ( rawAllocationId ) ;
1100-
1101- // In the browser, use crypto.subtle.digest
1076+ const payload = new TextEncoder ( ) . encode ( rawAllocationId ) ;
1077+ // In the browser or Node.js 18+, use crypto.subtle.digest
11021078 if ( crypto . subtle ) {
1103- const hashBuffer = await crypto . subtle . digest ( "SHA-256" , data ) ;
1079+ const hashBuffer = await crypto . subtle . digest ( "SHA-256" , payload ) ;
11041080 const hashArray = new Uint8Array ( hashBuffer ) ;
11051081
11061082 // Only use the first 15 bytes
11071083 const first15Bytes = hashArray . slice ( 0 , 15 ) ;
1108-
1109- // btoa/atob is also available in Node.js 18+
11101084 const base64String = btoa ( String . fromCharCode ( ...first15Bytes ) ) ;
11111085 const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
11121086 return base64urlString ;
11131087 }
1114- // In Node.js, use the crypto module's hash function
1088+ // Use the crypto module's hash function
11151089 else {
1116- const hash = crypto . createHash ( "sha256" ) . update ( data ) . digest ( ) ;
1090+ const hash = crypto . createHash ( "sha256" ) . update ( payload ) . digest ( ) ;
11171091
11181092 // Only use the first 15 bytes
11191093 const first15Bytes = hash . slice ( 0 , 15 ) ;
1120-
11211094 return first15Bytes . toString ( "base64url" ) ;
11221095 }
11231096 }
1097+
1098+ async #calculteCacheConsistencyToken( etags : string [ ] ) : Promise < string > {
1099+ const crypto = getCryptoModule ( ) ;
1100+ const sortedEtags = etags . sort ( ) ;
1101+ const rawString = "CacheConsistency\n" + sortedEtags . join ( "\n" ) ;
1102+ // Convert to UTF-8 encoded bytes
1103+ const payload = new TextEncoder ( ) . encode ( rawString ) ;
1104+ // In the browser or Node.js 18+, use crypto.subtle.digest
1105+ if ( crypto . subtle ) {
1106+ const hashBuffer = await crypto . subtle . digest ( "SHA-256" , payload ) ;
1107+ const hashArray = new Uint8Array ( hashBuffer ) ;
1108+ const base64String = btoa ( String . fromCharCode ( ...hashArray ) ) ;
1109+ const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
1110+ return base64urlString ;
1111+ }
1112+ // Use the crypto module's hash function
1113+ else {
1114+ const hash = crypto . createHash ( "sha256" ) . update ( payload ) . digest ( ) ;
1115+ return hash . toString ( "base64url" ) ;
1116+ }
1117+ }
11241118}
11251119
11261120function getValidSettingSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
0 commit comments