Skip to content

Commit ac33118

Browse files
update to latest design
1 parent 96df9d9 commit ac33118

File tree

1 file changed

+36
-60
lines changed

1 file changed

+36
-60
lines changed

src/AzureAppConfigurationImpl.ts

Lines changed: 36 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -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

8687
export 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

Comments
 (0)