@@ -20,9 +20,9 @@ const MINIMAL_CLIENT_REFRESH_INTERVAL = 30 * 1000; // 30 seconds in milliseconds
2020const SRV_QUERY_TIMEOUT = 30 * 1000 ; // 30 seconds in milliseconds
2121
2222export class ConfigurationClientManager {
23- isFailoverable : boolean ;
24- dns : any ;
25- endpoint : string ;
23+ # isFailoverable: boolean ;
24+ # dns: any ;
25+ endpoint : URL ;
2626 #secret : string ;
2727 #id : string ;
2828 #credential: TokenCredential ;
@@ -40,81 +40,79 @@ export class ConfigurationClientManager {
4040 appConfigOptions ?: AzureAppConfigurationOptions
4141 ) {
4242 let staticClient : AppConfigurationClient ;
43+ const credentialPassed = instanceOfTokenCredential ( credentialOrOptions ) ;
4344
44- if ( typeof connectionStringOrEndpoint === "string" && ! instanceOfTokenCredential ( credentialOrOptions ) ) {
45+ if ( typeof connectionStringOrEndpoint === "string" && ! credentialPassed ) {
4546 const connectionString = connectionStringOrEndpoint ;
4647 this . #appConfigOptions = credentialOrOptions as AzureAppConfigurationOptions ;
4748 this . #clientOptions = getClientOptions ( this . #appConfigOptions) ;
48- 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 ) ;
5151 if ( regexMatch ) {
52- this . endpoint = regexMatch [ 1 ] ;
52+ const endpointFromConnectionStr = regexMatch [ 1 ] ;
53+ this . endpoint = getValidUrl ( endpointFromConnectionStr ) ;
5354 this . #id = regexMatch [ 2 ] ;
5455 this . #secret = regexMatch [ 3 ] ;
5556 } else {
5657 throw new Error ( `Invalid connection string. Valid connection strings should match the regex '${ ConnectionStringRegex . source } '.` ) ;
5758 }
58- } else if ( ( connectionStringOrEndpoint instanceof URL || typeof connectionStringOrEndpoint === "string" ) && instanceOfTokenCredential ( credentialOrOptions ) ) {
59+ staticClient = new AppConfigurationClient ( connectionString , this . #clientOptions) ;
60+ } else if ( ( connectionStringOrEndpoint instanceof URL || typeof connectionStringOrEndpoint === "string" ) && credentialPassed ) {
5961 let endpoint = connectionStringOrEndpoint ;
6062 // ensure string is a valid URL.
6163 if ( typeof endpoint === "string" ) {
62- try {
63- endpoint = new URL ( endpoint ) ;
64- } catch ( error ) {
65- if ( error . code === "ERR_INVALID_URL" ) {
66- throw new Error ( "Invalid endpoint URL." , { cause : error } ) ;
67- } else {
68- throw error ;
69- }
70- }
64+ endpoint = getValidUrl ( endpoint ) ;
7165 }
7266
7367 const credential = credentialOrOptions as TokenCredential ;
7468 this . #appConfigOptions = appConfigOptions as AzureAppConfigurationOptions ;
7569 this . #clientOptions = getClientOptions ( this . #appConfigOptions) ;
76- staticClient = new AppConfigurationClient ( connectionStringOrEndpoint . toString ( ) , credential , this . #clientOptions) ;
77- this . endpoint = endpoint . toString ( ) ;
70+ this . endpoint = endpoint ;
7871 this . #credential = credential ;
72+ staticClient = new AppConfigurationClient ( this . endpoint . origin , this . #credential, this . #clientOptions) ;
7973 } else {
8074 throw new Error ( "A connection string or an endpoint with credential must be specified to create a client." ) ;
8175 }
8276
83- this . #staticClients = [ new ConfigurationClientWrapper ( this . endpoint , staticClient ) ] ;
84- this . #validDomain = getValidDomain ( this . endpoint ) ;
77+ this . #staticClients = [ new ConfigurationClientWrapper ( this . endpoint . origin , staticClient ) ] ;
78+ this . #validDomain = getValidDomain ( this . endpoint . hostname . toLowerCase ( ) ) ;
8579 }
8680
8781 async init ( ) {
8882 if ( this . #appConfigOptions?. replicaDiscoveryEnabled === false || isBrowser ( ) || isWebWorker ( ) ) {
89- this . isFailoverable = false ;
83+ this . # isFailoverable = false ;
9084 return ;
9185 }
9286
9387 try {
94- this . dns = await import ( "dns/promises" ) ;
88+ this . # dns = await import ( "dns/promises" ) ;
9589 } catch ( error ) {
96- this . isFailoverable = false ;
90+ this . # isFailoverable = false ;
9791 console . warn ( "Failed to load the dns module:" , error . message ) ;
9892 return ;
9993 }
10094
101- this . isFailoverable = true ;
95+ this . # isFailoverable = true ;
10296 }
10397
10498 async getClients ( ) : Promise < ConfigurationClientWrapper [ ] > {
105- if ( ! this . isFailoverable ) {
99+ if ( ! this . # isFailoverable) {
106100 return this . #staticClients;
107101 }
108102
109103 const currentTime = Date . now ( ) ;
110- if ( this . #isFallbackClientDiscoveryDue( currentTime ) ) {
104+ // Filter static clients whose backoff time has ended
105+ let availableClients = this . #staticClients. filter ( client => client . backoffEndTime <= currentTime ) ;
106+ if ( currentTime >= this . #lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL &&
107+ ( ! this . #dynamicClients ||
108+ // All dynamic clients are in backoff means no client is available
109+ this . #dynamicClients. every ( client => currentTime < client . backoffEndTime ) ||
110+ currentTime >= this . #lastFallbackClientRefreshTime + FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL ) ) {
111111 this . #lastFallbackClientRefreshAttempt = currentTime ;
112- const host = new URL ( this . endpoint ) . hostname ;
113- await this . #discoverFallbackClients ( host ) ;
112+ await this . #discoverFallbackClients ( this . endpoint . hostname ) ;
113+ return availableClients . concat ( this . #dynamicClients ) ;
114114 }
115115
116- // Filter static clients whose backoff time has ended
117- let availableClients = this . #staticClients. filter ( client => client . backoffEndTime <= currentTime ) ;
118116 // If there are dynamic clients, filter and concatenate them
119117 if ( this . #dynamicClients && this . #dynamicClients. length > 0 ) {
120118 availableClients = availableClients . concat (
@@ -127,23 +125,22 @@ export class ConfigurationClientManager {
127125
128126 async refreshClients ( ) {
129127 const currentTime = Date . now ( ) ;
130- if ( this . isFailoverable &&
131- currentTime > new Date ( this . #lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL ) . getTime ( ) ) {
128+ if ( this . # isFailoverable &&
129+ currentTime >= new Date ( this . #lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL ) . getTime ( ) ) {
132130 this . #lastFallbackClientRefreshAttempt = currentTime ;
133- const host = new URL ( this . endpoint ) . hostname ;
134- await this . #discoverFallbackClients( host ) ;
131+ await this . #discoverFallbackClients( this . endpoint . hostname ) ;
135132 }
136133 }
137134
138- async #discoverFallbackClients( host ) {
135+ async #discoverFallbackClients( host : string ) {
139136 let result ;
140137 try {
141138 result = await Promise . race ( [
142139 new Promise ( ( _ , reject ) => setTimeout ( ( ) => reject ( new Error ( "SRV record query timed out." ) ) , SRV_QUERY_TIMEOUT ) ) ,
143140 this . #querySrvTargetHost( host )
144141 ] ) ;
145142 } catch ( error ) {
146- throw new Error ( `Fail to build fallback clients, ${ error . message } ` ) ;
143+ throw new Error ( `Failed to build fallback clients, ${ error . message } ` ) ;
147144 }
148145
149146 const srvTargetHosts = result as string [ ] ;
@@ -157,10 +154,12 @@ export class ConfigurationClientManager {
157154 for ( const host of srvTargetHosts ) {
158155 if ( isValidEndpoint ( host , this . #validDomain) ) {
159156 const targetEndpoint = `https://${ host } ` ;
160- if ( targetEndpoint . toLowerCase ( ) === this . endpoint . toLowerCase ( ) ) {
157+ if ( host . toLowerCase ( ) === this . endpoint . hostname . toLowerCase ( ) ) {
161158 continue ;
162159 }
163- const client = this . #credential ? new AppConfigurationClient ( targetEndpoint , this . #credential, this . #clientOptions) : new AppConfigurationClient ( buildConnectionString ( targetEndpoint , this . #secret, this . #id) , this . #clientOptions) ;
160+ const client = this . #credential ?
161+ new AppConfigurationClient ( targetEndpoint , this . #credential, this . #clientOptions) :
162+ new AppConfigurationClient ( buildConnectionString ( targetEndpoint , this . #secret, this . #id) , this . #clientOptions) ;
164163 newDynamicClients . push ( new ConfigurationClientWrapper ( targetEndpoint , client ) ) ;
165164 }
166165 }
@@ -169,13 +168,6 @@ export class ConfigurationClientManager {
169168 this . #lastFallbackClientRefreshTime = Date . now ( ) ;
170169 }
171170
172- #isFallbackClientDiscoveryDue( dateTime ) {
173- return dateTime >= this . #lastFallbackClientRefreshAttempt + MINIMAL_CLIENT_REFRESH_INTERVAL
174- && ( ! this . #dynamicClients
175- || this . #dynamicClients. every ( client => dateTime < client . backoffEndTime )
176- || dateTime >= this . #lastFallbackClientRefreshTime + FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL ) ;
177- }
178-
179171 /**
180172 * Query SRV records and return target hosts.
181173 */
@@ -184,7 +176,7 @@ export class ConfigurationClientManager {
184176
185177 try {
186178 // Look up SRV records for the origin host
187- const originRecords = await this . dns . resolveSrv ( `${ TCP_ORIGIN_KEY_NAME } .${ host } ` ) ;
179+ const originRecords = await this . # dns. resolveSrv ( `${ TCP_ORIGIN_KEY_NAME } .${ host } ` ) ;
188180 if ( originRecords . length === 0 ) {
189181 return results ;
190182 }
@@ -198,7 +190,7 @@ export class ConfigurationClientManager {
198190 // eslint-disable-next-line no-constant-condition
199191 while ( true ) {
200192 const currentAlt = `${ ALT_KEY_NAME } ${ index } ` ;
201- const altRecords = await this . dns . resolveSrv ( `${ currentAlt } .${ TCP_KEY_NAME } .${ originHost } ` ) ;
193+ const altRecords = await this . # dns. resolveSrv ( `${ currentAlt } .${ TCP_KEY_NAME } .${ originHost } ` ) ;
202194 if ( altRecords . length === 0 ) {
203195 break ; // No more alternate records, exit loop
204196 }
@@ -238,19 +230,12 @@ function buildConnectionString(endpoint, secret, id: string): string {
238230/**
239231 * Extracts a valid domain from the given endpoint URL based on trusted domain labels.
240232 */
241- export function getValidDomain ( endpoint : string ) : string {
242- try {
243- const url = new URL ( endpoint ) ;
244- const host = url . hostname . toLowerCase ( ) ;
245-
246- for ( const label of TRUSTED_DOMAIN_LABELS ) {
247- const index = host . lastIndexOf ( label ) ;
248- if ( index !== - 1 ) {
249- return host . substring ( index ) ;
250- }
233+ export function getValidDomain ( host : string ) : string {
234+ for ( const label of TRUSTED_DOMAIN_LABELS ) {
235+ const index = host . lastIndexOf ( label ) ;
236+ if ( index !== - 1 ) {
237+ return host . substring ( index ) ;
251238 }
252- } catch ( error ) {
253- console . error ( "Error parsing URL:" , error . message ) ;
254239 }
255240
256241 return "" ;
@@ -267,7 +252,7 @@ export function isValidEndpoint(host: string, validDomain: string): boolean {
267252 return host . toLowerCase ( ) . endsWith ( validDomain . toLowerCase ( ) ) ;
268253}
269254
270- export function getClientOptions ( options ?: AzureAppConfigurationOptions ) : AppConfigurationClientOptions | undefined {
255+ function getClientOptions ( options ?: AzureAppConfigurationOptions ) : AppConfigurationClientOptions | undefined {
271256 // user-agent
272257 let userAgentPrefix = RequestTracing . USER_AGENT_PREFIX ; // Default UA for JavaScript Provider
273258 const userAgentOptions = options ?. clientOptions ?. userAgentOptions ;
@@ -290,6 +275,18 @@ export function getClientOptions(options?: AzureAppConfigurationOptions): AppCon
290275 } ) ;
291276}
292277
278+ function getValidUrl ( endpoint : string ) : URL {
279+ try {
280+ return new URL ( endpoint ) ;
281+ } catch ( error ) {
282+ if ( error . code === "ERR_INVALID_URL" ) {
283+ throw new Error ( "Invalid endpoint URL." , { cause : error } ) ;
284+ } else {
285+ throw error ;
286+ }
287+ }
288+ }
289+
293290export function instanceOfTokenCredential ( obj : unknown ) {
294291 return obj && typeof obj === "object" && "getToken" in obj && typeof obj . getToken === "function" ;
295292}
0 commit comments