@@ -8,24 +8,20 @@ import { AzureAppConfigurationOptions, MaxRetries, MaxRetryDelayInMs } from "./A
88import { isFailoverableEnv } from "./requestTracing/utils" ;
99import * as RequestTracing from "./requestTracing/constants" ;
1010
11- const TCP_ORIGIN = "_origin._tcp" ;
12- const ALT = "_alt" ;
13- const TCP = "_tcp" ;
14- const Endpoint = "Endpoint" ;
15- const Id = "Id" ;
16- const Secret = "Secret" ;
11+ const TCP_ORIGIN_KEY_NAME = "_origin._tcp" ;
12+ const ALT_KEY_NAME = "_alt" ;
13+ const TCP_KEY_NAME = "_tcp" ;
14+ const Endpoint_KEY_NAME = "Endpoint" ;
15+ const Id_KEY_NAME = "Id" ;
16+ const Secret_KEY_NAME = "Secret" ;
17+ const ConnectionStringRegex = / E n d p o i n t = ( .* ) ; I d = ( .* ) ; S e c r e t = ( .* ) / ;
1718const AzConfigDomainLabel = ".azconfig." ;
1819const AppConfigDomainLabel = ".appconfig." ;
1920const FallbackClientRefreshExpireInterval = 60 * 60 * 1000 ; // 1 hour in milliseconds
2021const MinimalClientRefreshInterval = 30 * 1000 ; // 30 seconds in milliseconds
21- const SrvQueryTimeout = 5000 ; // 5 seconds
22+ const SrvQueryTimeout = 5 * 1000 ; // 5 seconds in milliseconds
2223
23- interface IConfigurationClientManager {
24- getClients ( ) : Promise < ConfigurationClientWrapper [ ] > ;
25- refreshClients ( ) : Promise < void > ;
26- }
27-
28- export class ConfigurationClientManager implements IConfigurationClientManager {
24+ export class ConfigurationClientManager {
2925 isFailoverable : boolean ;
3026 endpoint : string ;
3127 #secret : string ;
@@ -44,24 +40,41 @@ export class ConfigurationClientManager implements IConfigurationClientManager {
4440 appConfigOptions ?: AzureAppConfigurationOptions
4541 ) {
4642 let staticClient : AppConfigurationClient ;
47- let options : AzureAppConfigurationOptions ;
43+ let options : AzureAppConfigurationOptions | undefined ;
4844
49- if ( typeof connectionStringOrEndpoint === "string" ) {
45+ if ( typeof connectionStringOrEndpoint === "string" && ! instanceOfTokenCredential ( credentialOrOptions ) ) {
5046 const connectionString = connectionStringOrEndpoint ;
5147 options = credentialOrOptions as AzureAppConfigurationOptions ;
5248 this . #clientOptions = getClientOptions ( options ) ;
5349 staticClient = new AppConfigurationClient ( connectionString , this . #clientOptions) ;
54- this . #secret = parseConnectionString ( connectionString , Secret ) ;
55- this . #id = parseConnectionString ( connectionString , Id ) ;
56- // TODO: need to check if it's CDN or not
57- this . endpoint = parseConnectionString ( connectionString , Endpoint ) ;
50+ const regexMatch = connectionString . match ( ConnectionStringRegex ) ;
51+ if ( regexMatch ) {
52+ this . endpoint = regexMatch [ 1 ] ;
53+ this . #id = regexMatch [ 2 ] ;
54+ this . #secret = regexMatch [ 3 ] ;
55+ } else {
56+ throw new Error ( `Invalid connection string. Valid connection strings should match the regex '${ ConnectionStringRegex . source } '.` ) ;
57+ }
58+ } else if ( ( connectionStringOrEndpoint instanceof URL || typeof connectionStringOrEndpoint === "string" ) && instanceOfTokenCredential ( credentialOrOptions ) ) {
59+ let endpoint = connectionStringOrEndpoint ;
60+ // ensure string is a valid URL.
61+ 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+ }
71+ }
5872
59- } else if ( connectionStringOrEndpoint instanceof URL ) {
6073 const credential = credentialOrOptions as TokenCredential ;
6174 options = appConfigOptions as AzureAppConfigurationOptions ;
6275 this . #clientOptions = getClientOptions ( options ) ;
6376 staticClient = new AppConfigurationClient ( connectionStringOrEndpoint . toString ( ) , credential , this . #clientOptions) ;
64- this . endpoint = connectionStringOrEndpoint . toString ( ) ;
77+ this . endpoint = endpoint . toString ( ) ;
6578 this . #credential = credential ;
6679 } else {
6780 throw new Error ( "A connection string or an endpoint with credential must be specified to create a client." ) ;
@@ -84,7 +97,7 @@ export class ConfigurationClientManager implements IConfigurationClientManager {
8497 await this . #discoverFallbackClients( host ) ;
8598 }
8699
87- // Filter static clients where BackoffEndTime is less than or equal to now
100+ // Filter static clients whose backoff time has ended
88101 let availableClients = this . #staticClients. filter ( client => client . backoffEndTime <= currentTime ) ;
89102 // If there are dynamic clients, filter and concatenate them
90103 if ( this . #dynamicClients && this . #dynamicClients. length > 0 ) {
@@ -101,8 +114,8 @@ export class ConfigurationClientManager implements IConfigurationClientManager {
101114 if ( this . isFailoverable &&
102115 currentTime > new Date ( this . #lastFallbackClientRefreshAttempt + MinimalClientRefreshInterval ) . getTime ( ) ) {
103116 this . #lastFallbackClientRefreshAttempt = currentTime ;
104- const url = new URL ( this . endpoint ) ;
105- await this . #discoverFallbackClients( url . hostname ) ;
117+ const host = new URL ( this . endpoint ) . hostname ;
118+ await this . #discoverFallbackClients( host ) ;
106119 }
107120 }
108121
@@ -176,7 +189,7 @@ async function querySrvTargetHost(host: string): Promise<string[]> {
176189
177190 try {
178191 // Look up SRV records for the origin host
179- const originRecords = await dns . resolveSrv ( `${ TCP_ORIGIN } .${ host } ` ) ;
192+ const originRecords = await dns . resolveSrv ( `${ TCP_ORIGIN_KEY_NAME } .${ host } ` ) ;
180193 if ( originRecords . length === 0 ) {
181194 return results ;
182195 }
@@ -189,9 +202,9 @@ async function querySrvTargetHost(host: string): Promise<string[]> {
189202 let index = 0 ;
190203 let moreAltRecordsExist = true ;
191204 while ( moreAltRecordsExist ) {
192- const currentAlt = `${ ALT } ${ index } ` ;
205+ const currentAlt = `${ ALT_KEY_NAME } ${ index } ` ;
193206 try {
194- const altRecords = await dns . resolveSrv ( `${ currentAlt } .${ TCP } .${ originHost } ` ) ;
207+ const altRecords = await dns . resolveSrv ( `${ currentAlt } .${ TCP_KEY_NAME } .${ originHost } ` ) ;
195208 if ( altRecords . length === 0 ) {
196209 moreAltRecordsExist = false ;
197210 break ; // No more alternate records, exit loop
@@ -223,30 +236,6 @@ async function querySrvTargetHost(host: string): Promise<string[]> {
223236 return results ;
224237}
225238
226- /**
227- * Parses the connection string to extract the value associated with a specific token.
228- */
229- function parseConnectionString ( connectionString , token : string ) : string {
230- if ( ! connectionString ) {
231- throw new Error ( "connectionString is empty" ) ;
232- }
233-
234- // Token format is "token="
235- const searchToken = `${ token } =` ;
236- const startIndex = connectionString . indexOf ( searchToken ) ;
237- if ( startIndex === - 1 ) {
238- throw new Error ( `Token ${ token } not found in connectionString` ) ;
239- }
240-
241- // Move startIndex to the beginning of the token value
242- const valueStartIndex = startIndex + searchToken . length ;
243- const endIndex = connectionString . indexOf ( ";" , valueStartIndex ) ;
244- const valueEndIndex = endIndex === - 1 ? connectionString . length : endIndex ;
245-
246- // Extract and return the token value
247- return connectionString . substring ( valueStartIndex , valueEndIndex ) ;
248- }
249-
250239/**
251240 * Builds a connection string from the given endpoint, secret, and id.
252241 * Returns an empty string if either secret or id is empty.
@@ -256,7 +245,7 @@ function buildConnectionString(endpoint, secret, id: string): string {
256245 return "" ;
257246 }
258247
259- return `${ Endpoint } =${ endpoint } ;${ Id } =${ id } ;${ Secret } =${ secret } ` ;
248+ return `${ Endpoint_KEY_NAME } =${ endpoint } ;${ Id_KEY_NAME } =${ id } ;${ Secret_KEY_NAME } =${ secret } ` ;
260249}
261250
262251/**
@@ -315,3 +304,7 @@ export function getClientOptions(options?: AzureAppConfigurationOptions): AppCon
315304 } ) ;
316305}
317306
307+ export function instanceOfTokenCredential ( obj : unknown ) {
308+ return obj && typeof obj === "object" && "getToken" in obj && typeof obj . getToken === "function" ;
309+ }
310+
0 commit comments