Skip to content

Commit fea91be

Browse files
committed
update
1 parent fa9b2c6 commit fea91be

File tree

3 files changed

+81
-101
lines changed

3 files changed

+81
-101
lines changed

src/ConfigurationClientManager.ts

Lines changed: 78 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AppConfigurationClient, AppConfigurationClientOptions } from "@azure/ap
55
import { ConfigurationClientWrapper } from "./ConfigurationClientWrapper.js";
66
import { TokenCredential } from "@azure/identity";
77
import { AzureAppConfigurationOptions, MaxRetries, MaxRetryDelayInMs } from "./AzureAppConfigurationOptions.js";
8-
import { isFailoverableEnv } from "./requestTracing/utils.js";
8+
import { isBrowser, isWebWorker } from "./requestTracing/utils.js";
99
import * as RequestTracing from "./requestTracing/constants.js";
1010

1111
const TCP_ORIGIN_KEY_NAME = "_origin._tcp";
@@ -14,19 +14,20 @@ const TCP_KEY_NAME = "_tcp";
1414
const ENDPOINT_KEY_NAME = "Endpoint";
1515
const ID_KEY_NAME = "Id";
1616
const SECRET_KEY_NAME = "Secret";
17-
const AZCONFIG_DOMAIN_LABEL = ".azconfig.";
18-
const APPCONFIG_DOMAIN_LABEL = ".appconfig.";
17+
const TRUSTED_DOMAIN_LABELS = [".azconfig.", ".appconfig."];
1918
const FALLBACK_CLIENT_REFRESH_EXPIRE_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
2019
const 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

2322
export 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 = /Endpoint=(.*);Id=(.*);Secret=(.*)/;
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 {
254241
export 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);

src/load.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export async function load(
3232
const startTimestamp = Date.now();
3333
let options: AzureAppConfigurationOptions | undefined;
3434
const clientManager = new ConfigurationClientManager(connectionStringOrEndpoint, credentialOrOptions, appConfigOptions);
35+
await clientManager.init();
3536

3637
if (!instanceOfTokenCredential(credentialOrOptions)) {
3738
options = credentialOrOptions as AzureAppConfigurationOptions;

src/requestTracing/utils.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function isDevEnvironment(): boolean {
153153
return false;
154154
}
155155

156-
function isBrowser() {
156+
export function isBrowser() {
157157
// https://developer.mozilla.org/en-US/docs/Web/API/Window
158158
const isWindowDefinedAsExpected = typeof window === "object" && typeof Window === "function" && window instanceof Window;
159159
// https://developer.mozilla.org/en-US/docs/Web/API/Document
@@ -162,7 +162,7 @@ function isBrowser() {
162162
return isWindowDefinedAsExpected && isDocumentDefinedAsExpected;
163163
}
164164

165-
function isWebWorker() {
165+
export function isWebWorker() {
166166
// https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope
167167
const workerGlobalScopeDefined = typeof WorkerGlobalScope !== "undefined";
168168
// https://developer.mozilla.org/en-US/docs/Web/API/WorkerNavigator
@@ -173,10 +173,3 @@ function isWebWorker() {
173173
return workerGlobalScopeDefined && importScriptsAsGlobalFunction && isNavigatorDefinedAsExpected;
174174
}
175175

176-
export function isFailoverableEnv() {
177-
if (isBrowser() || isWebWorker()) {
178-
return false;
179-
}
180-
181-
return true;
182-
}

0 commit comments

Comments
 (0)