-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathload.ts
More file actions
165 lines (147 loc) · 7.59 KB
/
load.ts
File metadata and controls
165 lines (147 loc) · 7.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { AppConfigurationClient, AppConfigurationClientOptions } from "@azure/app-configuration";
import { PipelinePolicy, PipelineRequest, SendRequest } from "@azure/core-rest-pipeline";
import { TokenCredential } from "@azure/identity";
import { AzureAppConfiguration } from "./AzureAppConfiguration.js";
import { AzureAppConfigurationImpl } from "./AzureAppConfigurationImpl.js";
import { AzureAppConfigurationOptions, MaxRetries, MaxRetryDelayInMs } from "./AzureAppConfigurationOptions.js";
import * as RequestTracing from "./requestTracing/constants.js";
const MIN_DELAY_FOR_UNHANDLED_ERROR: number = 5000; // 5 seconds
/**
* Loads the data from Azure App Configuration service and returns an instance of AzureAppConfiguration.
* @param connectionString The connection string for the App Configuration store.
* @param options Optional parameters.
*/
export async function load(connectionString: string, options?: AzureAppConfigurationOptions): Promise<AzureAppConfiguration>;
/**
* Loads the data from Azure App Configuration service and returns an instance of AzureAppConfiguration.
* @param endpoint The URL to the App Configuration store.
* @param credential The credential to use to connect to the App Configuration store.
* @param options Optional parameters.
*/
export async function load(endpoint: URL | string, credential: TokenCredential, options?: AzureAppConfigurationOptions): Promise<AzureAppConfiguration>;
export async function load(
connectionStringOrEndpoint: string | URL,
credentialOrOptions?: TokenCredential | AzureAppConfigurationOptions,
appConfigOptions?: AzureAppConfigurationOptions
): Promise<AzureAppConfiguration> {
const startTimestamp = Date.now();
let client: AppConfigurationClient;
let clientEndpoint: string | undefined;
let options: AzureAppConfigurationOptions | undefined;
// input validation
if (typeof connectionStringOrEndpoint === "string" && !instanceOfTokenCredential(credentialOrOptions)) {
const connectionString = connectionStringOrEndpoint;
options = credentialOrOptions as AzureAppConfigurationOptions;
const clientOptions = getClientOptions(options);
client = new AppConfigurationClient(connectionString, clientOptions);
clientEndpoint = getEndpoint(connectionStringOrEndpoint);
} else if ((connectionStringOrEndpoint instanceof URL || typeof connectionStringOrEndpoint === "string") && instanceOfTokenCredential(credentialOrOptions)) {
// ensure string is a valid URL.
if (typeof connectionStringOrEndpoint === "string") {
try {
const endpointUrl = new URL(connectionStringOrEndpoint);
clientEndpoint = endpointUrl.toString();
} catch (error) {
if (error.code === "ERR_INVALID_URL") {
throw new Error("Invalid endpoint URL.", { cause: error });
} else {
throw error;
}
}
} else {
clientEndpoint = connectionStringOrEndpoint.toString();
}
const credential = credentialOrOptions as TokenCredential;
options = appConfigOptions;
const clientOptions = getClientOptions(options);
client = new AppConfigurationClient(clientEndpoint, credential, clientOptions);
} else {
throw new Error("A connection string or an endpoint with credential must be specified to create a client.");
}
try {
const appConfiguration = new AzureAppConfigurationImpl(client, clientEndpoint, options);
await appConfiguration.load();
return appConfiguration;
} catch (error) {
// load() method is called in the application's startup code path.
// Unhandled exceptions cause application crash which can result in crash loops as orchestrators attempt to restart the application.
// Knowing the intended usage of the provider in startup code path, we mitigate back-to-back crash loops from overloading the server with requests by waiting a minimum time to propagate fatal errors.
const delay = MIN_DELAY_FOR_UNHANDLED_ERROR - (Date.now() - startTimestamp);
if (delay > 0) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw error;
}
}
/**
* Loads the data from a CDN and returns an instance of AzureAppConfiguration.
* @param cdnEndpoint The URL to the CDN.
* @param appConfigOptions Optional parameters.
*/
export async function loadFromCdn(cdnEndpoint: URL | string, options?: AzureAppConfigurationOptions): Promise<AzureAppConfiguration>;
export async function loadFromCdn(
cdnEndpoint: string | URL,
appConfigOptions?: AzureAppConfigurationOptions
): Promise<AzureAppConfiguration> {
const emptyTokenCredential: TokenCredential = {
getToken: async () => ({ token: "", expiresOnTimestamp: 0 })
};
// the api version supports sas token authentication
const apiVersion = "2024-09-01-preview";
const policyName = "CdnRequestApiVersionPolicy";
const apiVersionPolicy: PipelinePolicy = {
name: policyName,
sendRequest: async (request: PipelineRequest, next: SendRequest) => {
const url = new URL(request.url);
url.searchParams.set("api-version", apiVersion);
request.url = url.toString();
return next(request);
},
};
if (appConfigOptions === undefined) {
appConfigOptions = { clientOptions: {}};
}
const policies = appConfigOptions.clientOptions?.additionalPolicies || [];
// The policy position should be perRetry so that the policy will be processed after the api version policy added by the SDK.
// https://learn.microsoft.com/en-us/dotnet/api/azure.core.httppipelineposition?view=azure-dotnet#fields
policies.push({policy: apiVersionPolicy, position: "perRetry"});
appConfigOptions.clientOptions = { ...appConfigOptions.clientOptions, additionalPolicies: policies};
return await load(cdnEndpoint, emptyTokenCredential, appConfigOptions);
}
function instanceOfTokenCredential(obj: unknown) {
return obj && typeof obj === "object" && "getToken" in obj && typeof obj.getToken === "function";
}
function getClientOptions(options?: AzureAppConfigurationOptions): AppConfigurationClientOptions | undefined {
// user-agent
let userAgentPrefix = RequestTracing.USER_AGENT_PREFIX; // Default UA for JavaScript Provider
const userAgentOptions = options?.clientOptions?.userAgentOptions;
if (userAgentOptions?.userAgentPrefix) {
userAgentPrefix = `${userAgentOptions.userAgentPrefix} ${userAgentPrefix}`; // Prepend if UA prefix specified by user
}
// retry options
const defaultRetryOptions = {
maxRetries: MaxRetries,
maxRetryDelayInMs: MaxRetryDelayInMs,
};
const retryOptions = Object.assign({}, defaultRetryOptions, options?.clientOptions?.retryOptions);
return Object.assign({}, options?.clientOptions, {
retryOptions,
userAgentOptions: {
userAgentPrefix
}
});
}
function getEndpoint(connectionString: string): string | undefined {
const parts = connectionString.split(";");
const endpointPart = parts.find(part => part.startsWith("Endpoint="));
if (endpointPart) {
let endpoint = endpointPart.split("=")[1];
if (!endpoint.endsWith("/")) {
endpoint += "/";
}
return endpoint;
}
return undefined;
}