1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT license.
3
3
4
- import { AppConfigurationClient , ConfigurationSetting , ListConfigurationSettingsOptions } from "@azure/app-configuration" ;
4
+ import { AppConfigurationClient , ConfigurationSetting , ConfigurationSettingId , ListConfigurationSettingsOptions } from "@azure/app-configuration" ;
5
5
import { AzureAppConfiguration } from "./AzureAppConfiguration" ;
6
6
import { AzureAppConfigurationOptions } from "./AzureAppConfigurationOptions" ;
7
7
import { IKeyValueAdapter } from "./IKeyValueAdapter" ;
@@ -24,9 +24,10 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
24
24
private sortedTrimKeyPrefixes : string [ ] | undefined ;
25
25
private readonly requestTracingEnabled : boolean ;
26
26
// Refresh
27
- private refreshIntervalInMs : number ;
28
- private onRefreshListeners : LinkedList < ( ) => any > ;
27
+ private refreshIntervalInMs : number | undefined ;
28
+ private onRefreshListeners : LinkedList < ( ) => any > | undefined ;
29
29
private lastUpdateTimestamp : number ;
30
+ private sentinels : ConfigurationSettingId [ ] | undefined ;
30
31
31
32
constructor (
32
33
private client : AppConfigurationClient ,
@@ -52,6 +53,15 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
52
53
this . refreshIntervalInMs = refreshIntervalInMs ;
53
54
}
54
55
}
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
+ } ) ;
55
65
}
56
66
57
67
// TODO: should add more adapters to process different type of values
@@ -84,6 +94,11 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
84
94
const keyValuePair = await this . processKeyValues ( setting ) ;
85
95
keyValues . push ( keyValuePair ) ;
86
96
}
97
+ // update etag of sentinels
98
+ const matchedSentinel = this . sentinels ?. find ( s => s . key === setting . key && ( s . label ?? null ) === setting . label ) ; // Workaround: as undefined label represents the same with null.
99
+ if ( matchedSentinel ) {
100
+ matchedSentinel . etag = setting . etag ;
101
+ }
87
102
}
88
103
}
89
104
for ( const [ k , v ] of keyValues ) {
@@ -94,7 +109,7 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
94
109
95
110
public async refresh ( ) : Promise < void > {
96
111
// if no refreshOptions set, return
97
- if ( this . options ?. refreshOptions === undefined || this . options . refreshOptions . watchedSettings . length === 0 ) {
112
+ if ( this . sentinels === undefined || this . sentinels . length === 0 || this . refreshIntervalInMs === undefined ) {
98
113
return Promise . resolve ( ) ;
99
114
}
100
115
// if still within refresh interval, return
@@ -104,26 +119,35 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
104
119
}
105
120
106
121
// try refresh if any of watched settings is changed.
107
- // TODO: watchedSettings as optional, etag based refresh if not specified.
108
122
let needRefresh = false ;
109
- for ( const watchedSetting of this . options . refreshOptions . watchedSettings ) {
110
- const response = await this . client . getConfigurationSetting ( watchedSetting ) ;
111
- const [ key , value ] = await this . processKeyValues ( response ) ;
112
- if ( value !== this . get ( key ) ) {
123
+ for ( const sentinel of this . sentinels ) {
124
+ const response = await this . client . getConfigurationSetting ( sentinel , {
125
+ onlyIfChanged : true
126
+ // TODO: do we trace this request by adding custom headers?
127
+ } ) ;
128
+ if ( response . statusCode !== 304 ) { // TODO: can be more robust, e.g. === 200?
129
+ // sentinel changed.
130
+ sentinel . etag = response . etag ; // update etag of the sentinel
113
131
needRefresh = true ;
114
132
break ;
115
133
}
116
134
}
117
135
if ( needRefresh ) {
118
136
await this . load ( RequestType . Watch ) ;
119
137
// run callbacks in async
120
- for ( const listener of this . onRefreshListeners ) {
121
- listener ( ) ;
138
+ if ( this . onRefreshListeners !== undefined ) {
139
+ for ( const listener of this . onRefreshListeners ) {
140
+ listener ( ) ;
141
+ }
122
142
}
123
143
}
124
144
}
125
145
126
146
public onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
147
+ if ( this . onRefreshListeners === undefined ) {
148
+ // TODO: Add unit tests
149
+ throw new Error ( "Illegal operation because refreshOptions is not provided on loading." ) ;
150
+ }
127
151
const boundedListener = listener . bind ( thisArg ) ;
128
152
const remove = this . onRefreshListeners . push ( boundedListener ) ;
129
153
return new Disposable ( remove ) ;
0 commit comments