@@ -9,8 +9,11 @@ import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter";
9
9
import { KeyFilter } from "./KeyFilter" ;
10
10
import { LabelFilter } from "./LabelFilter" ;
11
11
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter" ;
12
- import { CorrelationContextHeaderName } from "./requestTracing/constants" ;
12
+ import { CorrelationContextHeaderName , RequestType } from "./requestTracing/constants" ;
13
13
import { createCorrelationContextHeader , requestTracingEnabled } from "./requestTracing/utils" ;
14
+ import { DefaultRefreshIntervalInMs , MinimumRefreshIntervalInMs } from "./RefreshOptions" ;
15
+ import { LinkedList } from "./common/linkedList" ;
16
+ import { Disposable } from "./common/disposable" ;
14
17
15
18
export class AzureAppConfigurationImpl extends Map < string , unknown > implements AzureAppConfiguration {
16
19
private adapters : IKeyValueAdapter [ ] = [ ] ;
@@ -20,7 +23,10 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
20
23
*/
21
24
private sortedTrimKeyPrefixes : string [ ] | undefined ;
22
25
private readonly requestTracingEnabled : boolean ;
23
- private correlationContextHeader : string | undefined ;
26
+ // Refresh
27
+ private refreshIntervalInMs : number ;
28
+ private onRefreshListeners : LinkedList < ( ) => any > ;
29
+ private lastUpdateTimestamp : number ;
24
30
25
31
constructor (
26
32
private client : AppConfigurationClient ,
@@ -29,20 +35,32 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
29
35
super ( ) ;
30
36
// Enable request tracing if not opt-out
31
37
this . requestTracingEnabled = requestTracingEnabled ( ) ;
32
- if ( this . requestTracingEnabled ) {
33
- this . enableRequestTracing ( ) ;
34
- }
35
38
36
39
if ( options ?. trimKeyPrefixes ) {
37
40
this . sortedTrimKeyPrefixes = [ ...options . trimKeyPrefixes ] . sort ( ( a , b ) => b . localeCompare ( a ) ) ;
38
41
}
42
+
43
+ if ( options ?. refreshOptions ) {
44
+ this . onRefreshListeners = new LinkedList ( ) ;
45
+ this . refreshIntervalInMs = DefaultRefreshIntervalInMs ;
46
+
47
+ const refreshIntervalInMs = this . options ?. refreshOptions ?. refreshIntervalInMs ;
48
+ if ( refreshIntervalInMs !== undefined ) {
49
+ if ( refreshIntervalInMs < MinimumRefreshIntervalInMs ) {
50
+ throw new Error ( `The refresh interval time cannot be less than ${ MinimumRefreshIntervalInMs } milliseconds.` ) ;
51
+ } else {
52
+ this . refreshIntervalInMs = refreshIntervalInMs ;
53
+ }
54
+ }
55
+ }
56
+
39
57
// TODO: should add more adapters to process different type of values
40
58
// feature flag, others
41
59
this . adapters . push ( new AzureKeyVaultKeyValueAdapter ( options ?. keyVaultOptions ) ) ;
42
60
this . adapters . push ( new JsonKeyValueAdapter ( ) ) ;
43
61
}
44
62
45
- public async load ( ) {
63
+ public async load ( requestType : RequestType = RequestType . Startup ) {
46
64
const keyValues : [ key : string , value : unknown ] [ ] = [ ] ;
47
65
48
66
// validate selectors
@@ -55,23 +73,66 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
55
73
} ;
56
74
if ( this . requestTracingEnabled ) {
57
75
listOptions . requestOptions = {
58
- customHeaders : this . customHeaders ( )
76
+ customHeaders : this . customHeaders ( requestType )
59
77
}
60
78
}
61
79
62
80
const settings = this . client . listConfigurationSettings ( listOptions ) ;
63
81
64
82
for await ( const setting of settings ) {
65
83
if ( setting . key ) {
66
- const [ key , value ] = await this . processAdapters ( setting ) ;
67
- const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
68
- keyValues . push ( [ trimmedKey , value ] ) ;
84
+ const keyValuePair = await this . processKeyValues ( setting ) ;
85
+ keyValues . push ( keyValuePair ) ;
69
86
}
70
87
}
71
88
}
72
89
for ( const [ k , v ] of keyValues ) {
73
90
this . set ( k , v ) ;
74
91
}
92
+ this . lastUpdateTimestamp = Date . now ( ) ;
93
+ }
94
+
95
+ public async refresh ( ) : Promise < void > {
96
+ // if no refreshOptions set, return
97
+ if ( this . options ?. refreshOptions === undefined || this . options . refreshOptions . watchedSettings . length === 0 ) {
98
+ return Promise . resolve ( ) ;
99
+ }
100
+ // if still within refresh interval, return
101
+ const now = Date . now ( ) ;
102
+ if ( now < this . lastUpdateTimestamp + this . refreshIntervalInMs ) {
103
+ return Promise . resolve ( ) ;
104
+ }
105
+
106
+ // try refresh if any of watched settings is changed.
107
+ // TODO: watchedSettings as optional, etag based refresh if not specified.
108
+ 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 ) ) {
113
+ needRefresh = true ;
114
+ break ;
115
+ }
116
+ }
117
+ if ( needRefresh ) {
118
+ await this . load ( RequestType . Watch ) ;
119
+ // run callbacks in async
120
+ for ( const listener of this . onRefreshListeners ) {
121
+ listener ( ) ;
122
+ }
123
+ }
124
+ }
125
+
126
+ public onRefresh ( listener : ( ) => any , thisArg ?: any ) : Disposable {
127
+ const boundedListener = listener . bind ( thisArg ) ;
128
+ const remove = this . onRefreshListeners . push ( boundedListener ) ;
129
+ return new Disposable ( remove ) ;
130
+ }
131
+
132
+ private async processKeyValues ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
133
+ const [ key , value ] = await this . processAdapters ( setting ) ;
134
+ const trimmedKey = this . keyWithPrefixesTrimmed ( key ) ;
135
+ return [ trimmedKey , value ] ;
75
136
}
76
137
77
138
private async processAdapters ( setting : ConfigurationSetting < string > ) : Promise < [ string , unknown ] > {
@@ -94,17 +155,13 @@ export class AzureAppConfigurationImpl extends Map<string, unknown> implements A
94
155
return key ;
95
156
}
96
157
97
- private enableRequestTracing ( ) {
98
- this . correlationContextHeader = createCorrelationContextHeader ( this . options ) ;
99
- }
100
-
101
- private customHeaders ( ) {
158
+ private customHeaders ( requestType : RequestType ) {
102
159
if ( ! this . requestTracingEnabled ) {
103
160
return undefined ;
104
161
}
105
162
106
163
const headers = { } ;
107
- headers [ CorrelationContextHeaderName ] = this . correlationContextHeader ;
164
+ headers [ CorrelationContextHeaderName ] = createCorrelationContextHeader ( this . options , requestType ) ;
108
165
return headers ;
109
166
}
110
167
}
0 commit comments