@@ -5,7 +5,7 @@ import { AppConfigurationClient, AppConfigurationClientOptions } from "@azure/ap
55import { ConfigurationClientWrapper } from "./ConfigurationClientWrapper.js" ;
66import { TokenCredential } from "@azure/identity" ;
77import { AzureAppConfigurationOptions , MaxRetries , MaxRetryDelayInMs } from "./AzureAppConfigurationOptions.js" ;
8- import { isFailoverableEnv } from "./requestTracing/utils.js" ;
8+ import { isBrowser , isWebWorker } from "./requestTracing/utils.js" ;
99import * as RequestTracing from "./requestTracing/constants.js" ;
1010
1111const TCP_ORIGIN_KEY_NAME = "_origin._tcp" ;
@@ -14,19 +14,20 @@ const TCP_KEY_NAME = "_tcp";
1414const ENDPOINT_KEY_NAME = "Endpoint" ;
1515const ID_KEY_NAME = "Id" ;
1616const SECRET_KEY_NAME = "Secret" ;
17- const AZCONFIG_DOMAIN_LABEL = ".azconfig." ;
18- const APPCONFIG_DOMAIN_LABEL = ".appconfig." ;
17+ const TRUSTED_DOMAIN_LABELS = [ ".azconfig." , ".appconfig." ] ;
1918const FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL = 60 * 60 * 1000 ; // 1 hour in milliseconds
2019const MINIMAL_CLIENT_REFRESH_INTERVAL = 30 * 1000 ; // 30 seconds in milliseconds
21- const SRV_QUERY_TIMEOUT = 5 * 1000 ; // 5 seconds in milliseconds
20+ const SRV_QUERY_TIMEOUT = 30 * 1000 ; // 30 seconds in milliseconds
2221
2322export class ConfigurationClientManager {
2423 isFailoverable : boolean ;
24+ dns : any ;
2525 endpoint : string ;
2626 #secret : string ;
2727 #id : string ;
2828 #credential: TokenCredential ;
2929 #clientOptions: AppConfigurationClientOptions | undefined ;
30+ #appConfigOptions: AzureAppConfigurationOptions | undefined ;
3031 #validDomain: string ;
3132 #staticClients: ConfigurationClientWrapper [ ] ;
3233 #dynamicClients: ConfigurationClientWrapper [ ] ;
@@ -39,12 +40,11 @@ export class ConfigurationClientManager {
3940 appConfigOptions ?: AzureAppConfigurationOptions
4041 ) {
4142 let staticClient : AppConfigurationClient ;
42- let options : AzureAppConfigurationOptions | undefined ;
4343
4444 if ( typeof connectionStringOrEndpoint === "string" && ! instanceOfTokenCredential ( credentialOrOptions ) ) {
4545 const connectionString = connectionStringOrEndpoint ;
46- options = credentialOrOptions as AzureAppConfigurationOptions ;
47- this . #clientOptions = getClientOptions ( options ) ;
46+ this . #appConfigOptions = credentialOrOptions as AzureAppConfigurationOptions ;
47+ this . #clientOptions = getClientOptions ( this . #appConfigOptions ) ;
4848 staticClient = new AppConfigurationClient ( connectionString , this . #clientOptions) ;
4949 const ConnectionStringRegex = / E n d p o i n t = ( .* ) ; I d = ( .* ) ; S e c r e t = ( .* ) / ;
5050 const regexMatch = connectionString . match ( ConnectionStringRegex ) ;
@@ -71,8 +71,8 @@ export class ConfigurationClientManager {
7171 }
7272
7373 const credential = credentialOrOptions as TokenCredential ;
74- options = appConfigOptions as AzureAppConfigurationOptions ;
75- this . #clientOptions = getClientOptions ( options ) ;
74+ this . #appConfigOptions = appConfigOptions as AzureAppConfigurationOptions ;
75+ this . #clientOptions = getClientOptions ( this . #appConfigOptions ) ;
7676 staticClient = new AppConfigurationClient ( connectionStringOrEndpoint . toString ( ) , credential , this . #clientOptions) ;
7777 this . endpoint = endpoint . toString ( ) ;
7878 this . #credential = credential ;
@@ -82,7 +82,23 @@ export class ConfigurationClientManager {
8282
8383 this . #staticClients = [ new ConfigurationClientWrapper ( this . endpoint , staticClient ) ] ;
8484 this . #validDomain = getValidDomain ( this . endpoint ) ;
85- this . isFailoverable = ( options ?. replicaDiscoveryEnabled ?? true ) && isFailoverableEnv ( ) ;
85+ }
86+
87+ async init ( ) {
88+ if ( this . #appConfigOptions?. replicaDiscoveryEnabled === false || isBrowser ( ) || isWebWorker ( ) ) {
89+ this . isFailoverable = false ;
90+ return ;
91+ }
92+
93+ try {
94+ this . dns = await import ( "dns/promises" ) ;
95+ } catch ( error ) {
96+ this . isFailoverable = false ;
97+ console . warn ( "Failed to load the dns module:" , error . message ) ;
98+ return ;
99+ }
100+
101+ this . isFailoverable = true ;
86102 }
87103
88104 async getClients ( ) : Promise < ConfigurationClientWrapper [ ] > {
@@ -120,50 +136,37 @@ export class ConfigurationClientManager {
120136 }
121137
122138 async #discoverFallbackClients( host ) {
123- const timeout = setTimeout ( ( ) => {
124- } , SRV_QUERY_TIMEOUT ) ;
125- const srvResults = await querySrvTargetHost ( host ) ;
126-
139+ let result ;
127140 try {
128- const result = await Promise . race ( [ srvResults , timeout ] ) ;
129-
130- if ( result === timeout ) {
131- throw new Error ( "SRV record query timed out." ) ;
132- }
141+ result = await Promise . race ( [
142+ new Promise ( ( _ , reject ) => setTimeout ( ( ) => reject ( new Error ( "SRV record query timed out." ) ) , SRV_QUERY_TIMEOUT ) ) ,
143+ this . #querySrvTargetHost( host )
144+ ] ) ;
145+ } catch ( error ) {
146+ throw new Error ( `Fail to build fallback clients, ${ error . message } ` ) ;
147+ }
133148
134- const srvTargetHosts = result as string [ ] ;
135- // Shuffle the list of SRV target hosts
136- for ( let i = srvTargetHosts . length - 1 ; i > 0 ; i -- ) {
137- const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
138- [ srvTargetHosts [ i ] , srvTargetHosts [ j ] ] = [ srvTargetHosts [ j ] , srvTargetHosts [ i ] ] ;
139- }
149+ const srvTargetHosts = result as string [ ] ;
150+ // Shuffle the list of SRV target hosts
151+ for ( let i = srvTargetHosts . length - 1 ; i > 0 ; i -- ) {
152+ const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
153+ [ srvTargetHosts [ i ] , srvTargetHosts [ j ] ] = [ srvTargetHosts [ j ] , srvTargetHosts [ i ] ] ;
154+ }
140155
141- const newDynamicClients : ConfigurationClientWrapper [ ] = [ ] ;
142- for ( const host of srvTargetHosts ) {
143- if ( isValidEndpoint ( host , this . #validDomain) ) {
144- const targetEndpoint = `https://${ host } ` ;
145- if ( targetEndpoint . toLowerCase ( ) === this . endpoint . toLowerCase ( ) ) {
146- continue ;
147- }
148- const client = this . #newConfigurationClient( targetEndpoint ) ;
149- newDynamicClients . push ( new ConfigurationClientWrapper ( targetEndpoint , client ) ) ;
156+ const newDynamicClients : ConfigurationClientWrapper [ ] = [ ] ;
157+ for ( const host of srvTargetHosts ) {
158+ if ( isValidEndpoint ( host , this . #validDomain) ) {
159+ const targetEndpoint = `https://${ host } ` ;
160+ if ( targetEndpoint . toLowerCase ( ) === this . endpoint . toLowerCase ( ) ) {
161+ continue ;
150162 }
163+ const client = this . #credential ? new AppConfigurationClient ( targetEndpoint , this . #credential, this . #clientOptions) : new AppConfigurationClient ( buildConnectionString ( targetEndpoint , this . #secret, this . #id) , this . #clientOptions) ;
164+ newDynamicClients . push ( new ConfigurationClientWrapper ( targetEndpoint , client ) ) ;
151165 }
152-
153- this . #dynamicClients = newDynamicClients ;
154- this . #lastFallbackClientRefreshTime = Date . now ( ) ;
155- } catch ( err ) {
156- console . warn ( `Fail to build fallback clients, ${ err . message } ` ) ;
157- }
158- }
159-
160- #newConfigurationClient( endpoint ) {
161- if ( this . #credential) {
162- return new AppConfigurationClient ( endpoint , this . #credential, this . #clientOptions) ;
163166 }
164167
165- const connectionStr = buildConnectionString ( endpoint , this . #secret , this . #id ) ;
166- return new AppConfigurationClient ( connectionStr , this . #clientOptions ) ;
168+ this . #dynamicClients = newDynamicClients ;
169+ this . #lastFallbackClientRefreshTime = Date . now ( ) ;
167170 }
168171
169172 #isFallbackClientDiscoveryDue( dateTime ) {
@@ -172,41 +175,31 @@ export class ConfigurationClientManager {
172175 || this . #dynamicClients. every ( client => dateTime < client . backoffEndTime )
173176 || dateTime >= this . #lastFallbackClientRefreshTime + FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL ) ;
174177 }
175- }
176178
177- /**
179+ /**
178180 * Query SRV records and return target hosts.
179181 */
180- async function querySrvTargetHost ( host : string ) : Promise < string [ ] > {
181- const results : string [ ] = [ ] ;
182- let dns ;
183-
184- if ( typeof global !== "undefined" && global . dns ) {
185- dns = global . dns ;
186- } else {
187- throw new Error ( "Failover is not supported in the current environment." ) ;
188- }
182+ async #querySrvTargetHost( host : string ) : Promise < string [ ] > {
183+ const results : string [ ] = [ ] ;
189184
190- try {
191- // Look up SRV records for the origin host
192- const originRecords = await dns . resolveSrv ( `${ TCP_ORIGIN_KEY_NAME } .${ host } ` ) ;
193- if ( originRecords . length === 0 ) {
194- return results ;
195- }
185+ try {
186+ // Look up SRV records for the origin host
187+ const originRecords = await this . dns . resolveSrv ( `${ TCP_ORIGIN_KEY_NAME } .${ host } ` ) ;
188+ if ( originRecords . length === 0 ) {
189+ return results ;
190+ }
191+
192+ // Add the first origin record to results
193+ const originHost = originRecords [ 0 ] . name ;
194+ results . push ( originHost ) ;
196195
197- // Add the first origin record to results
198- const originHost = originRecords [ 0 ] . name ;
199- results . push ( originHost ) ;
200-
201- // Look up SRV records for alternate hosts
202- let index = 0 ;
203- let moreAltRecordsExist = true ;
204- while ( moreAltRecordsExist ) {
205- const currentAlt = `${ ALT_KEY_NAME } ${ index } ` ;
206- try {
207- const altRecords = await dns . resolveSrv ( `${ currentAlt } .${ TCP_KEY_NAME } .${ originHost } ` ) ;
196+ // Look up SRV records for alternate hosts
197+ let index = 0 ;
198+ // eslint-disable-next-line no-constant-condition
199+ while ( true ) {
200+ const currentAlt = `${ ALT_KEY_NAME } ${ index } ` ;
201+ const altRecords = await this . dns . resolveSrv ( `${ currentAlt } .${ TCP_KEY_NAME } .${ originHost } ` ) ;
208202 if ( altRecords . length === 0 ) {
209- moreAltRecordsExist = false ;
210203 break ; // No more alternate records, exit loop
211204 }
212205
@@ -217,23 +210,17 @@ async function querySrvTargetHost(host: string): Promise<string[]> {
217210 }
218211 } ) ;
219212 index ++ ;
220- } catch ( err ) {
221- if ( err . code === "ENOTFOUND" ) {
222- break ; // No more alternate records, exit loop
223- } else {
224- throw new Error ( `Failed to lookup alternate SRV records: ${ err . message } ` ) ;
225- }
213+ }
214+ } catch ( err ) {
215+ if ( err . code === "ENOTFOUND" ) {
216+ return results ; // No more SRV records found, return results
217+ } else {
218+ throw new Error ( `Failed to lookup SRV records: ${ err . message } ` ) ;
226219 }
227220 }
228- } catch ( err ) {
229- if ( err . code === "ENOTFOUND" ) {
230- return results ; // No SRV records found, return empty array
231- } else {
232- throw new Error ( `Failed to lookup SRV records: ${ err . message } ` ) ;
233- }
234- }
235221
236- return results ;
222+ return results ;
223+ }
237224}
238225
239226/**
@@ -254,10 +241,9 @@ function buildConnectionString(endpoint, secret, id: string): string {
254241export function getValidDomain ( endpoint : string ) : string {
255242 try {
256243 const url = new URL ( endpoint ) ;
257- const trustedDomainLabels = [ AZCONFIG_DOMAIN_LABEL , APPCONFIG_DOMAIN_LABEL ] ;
258244 const host = url . hostname . toLowerCase ( ) ;
259245
260- for ( const label of trustedDomainLabels ) {
246+ for ( const label of TRUSTED_DOMAIN_LABELS ) {
261247 const index = host . lastIndexOf ( label ) ;
262248 if ( index !== - 1 ) {
263249 return host . substring ( index ) ;
0 commit comments