Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a0a551e
wip
zhiyuanliang-ms Sep 7, 2025
d0ecd09
load from azure front door
zhiyuanliang-ms Sep 7, 2025
8d06357
fix bug
zhiyuanliang-ms Sep 8, 2025
3310171
add more test
zhiyuanliang-ms Sep 8, 2025
6878b64
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Sep 8, 2025
9af1749
add browser test
zhiyuanliang-ms Sep 8, 2025
f97ae3d
update
zhiyuanliang-ms Sep 8, 2025
264113d
update
zhiyuanliang-ms Sep 8, 2025
58d1f2e
fix test
zhiyuanliang-ms Sep 8, 2025
725dd63
remove sync-token header
zhiyuanliang-ms Sep 9, 2025
665af4c
wip
zhiyuanliang-ms Sep 9, 2025
9d63b7c
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Sep 10, 2025
5087206
update
zhiyuanliang-ms Sep 10, 2025
339b3fa
fix lint
zhiyuanliang-ms Sep 10, 2025
b2b76ed
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Sep 10, 2025
1577bef
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Sep 26, 2025
87f952b
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Oct 2, 2025
4480562
update CDN tag
zhiyuanliang-ms Oct 14, 2025
d3c5f96
Merge branch 'zhiyuanliang/afd-support' of https://github.com/Azure/A…
zhiyuanliang-ms Nov 4, 2025
414a8c8
disallow sentinel key refresh for AFD
zhiyuanliang-ms Nov 5, 2025
bebf549
update
zhiyuanliang-ms Nov 5, 2025
564f16a
update
zhiyuanliang-ms Nov 6, 2025
28d37c2
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Nov 7, 2025
ca056b9
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Nov 7, 2025
b5f26c4
update
zhiyuanliang-ms Nov 7, 2025
5b09aee
update
zhiyuanliang-ms Nov 7, 2025
dcf5814
resolve merge conflict
zhiyuanliang-ms Nov 8, 2025
5d9477b
update error message
zhiyuanliang-ms Nov 8, 2025
2599b6a
Merge branch 'preview' of https://github.com/Azure/AppConfiguration-J…
zhiyuanliang-ms Nov 9, 2025
29f7958
update
zhiyuanliang-ms Nov 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 123 additions & 34 deletions src/appConfigurationImpl.ts

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions src/cdn/cdnRequestPipelinePolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { PipelinePolicy } from "@azure/core-rest-pipeline";

/**
* The pipeline policy that remove the authorization header from the request to allow anonymous access to the Azure Front Door.
* @remarks
* The policy position should be perRetry, since it should be executed after the "Sign" phase: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/src/serviceClient.ts
*/
export class AnonymousRequestPipelinePolicy implements PipelinePolicy {
name: string = "AppConfigurationAnonymousRequestPolicy";

async sendRequest(request, next) {
if (request.headers.has("authorization")) {
request.headers.delete("authorization");
}
return next(request);
}
}

/**
* The pipeline policy that remove the "sync-token" header from the request.
* The policy position should be perRetry. It should be executed after the SyncTokenPolicy in @azure/app-configuration, which is executed after retry phase: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/appconfiguration/app-configuration/src/appConfigurationClient.ts#L198
*/
export class RemoveSyncTokenPipelinePolicy implements PipelinePolicy {
name: string = "AppConfigurationRemoveSyncTokenPolicy";

async sendRequest(request, next) {
if (request.headers.has("sync-token")) {
request.headers.delete("sync-token");
}
return next(request);
}
}
4 changes: 4 additions & 0 deletions src/cdn/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

export const TIMESTAMP_HEADER = "x-ms-date";
2 changes: 2 additions & 0 deletions src/common/errorMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export const enum ErrorMessages {
INVALID_LABEL_FILTER = "The characters '*' and ',' are not supported in label filters.",
INVALID_TAG_FILTER = "Tag filter must follow the format 'tagName=tagValue'",
CONNECTION_STRING_OR_ENDPOINT_MISSED = "A connection string or an endpoint with credential must be specified to create a client.",
REPLICA_DISCOVERY_NOT_SUPPORTED = "Replica discovery is not supported when loading from Azure Front Door.",
LOAD_BALANCING_NOT_SUPPORTED = "Load balancing is not supported when loading from Azure Front Door."
}

export const enum KeyVaultReferenceErrorMessages {
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@

export { AzureAppConfiguration } from "./appConfiguration.js";
export { Disposable } from "./common/disposable.js";
export { load } from "./load.js";
export { load, loadFromAzureFrontDoor } from "./load.js";
export { KeyFilter, LabelFilter } from "./types.js";
export { VERSION } from "./version.js";
42 changes: 41 additions & 1 deletion src/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ import { AzureAppConfiguration } from "./appConfiguration.js";
import { AzureAppConfigurationImpl } from "./appConfigurationImpl.js";
import { AzureAppConfigurationOptions } from "./appConfigurationOptions.js";
import { ConfigurationClientManager } from "./configurationClientManager.js";
import { AnonymousRequestPipelinePolicy, RemoveSyncTokenPipelinePolicy } from "./cdn/cdnRequestPipelinePolicy.js";
import { instanceOfTokenCredential } from "./common/utils.js";
import { ArgumentError } from "./common/errors.js";
import { ErrorMessages } from "./common/errorMessages.js";

const MIN_DELAY_FOR_UNHANDLED_ERROR_IN_MS: number = 5_000;

// Empty token credential to be used when loading from Azure Front Door
const emptyTokenCredential: TokenCredential = {
getToken: async () => ({ token: "", expiresOnTimestamp: Number.MAX_SAFE_INTEGER })
};

/**
* Loads the data from Azure App Configuration service and returns an instance of AzureAppConfiguration.
* @param connectionString The connection string for the App Configuration store.
Expand Down Expand Up @@ -42,7 +50,8 @@ export async function load(
}

try {
const appConfiguration = new AzureAppConfigurationImpl(clientManager, options);
const isCdnUsed: boolean = credentialOrOptions === emptyTokenCredential;
const appConfiguration = new AzureAppConfigurationImpl(clientManager, options, isCdnUsed);
await appConfiguration.load();
return appConfiguration;
} catch (error) {
Expand All @@ -56,3 +65,34 @@ export async function load(
throw error;
}
}

/**
* Loads the data from Azure Front Door (CDN) and returns an instance of AzureAppConfiguration.
* @param endpoint The URL to the Azure Front Door.
* @param appConfigOptions Optional parameters.
*/
export async function loadFromAzureFrontDoor(endpoint: URL | string, options?: AzureAppConfigurationOptions): Promise<AzureAppConfiguration>;

export async function loadFromAzureFrontDoor(
endpoint: string | URL,
appConfigOptions: AzureAppConfigurationOptions = {}
): Promise<AzureAppConfiguration> {
if (appConfigOptions.replicaDiscoveryEnabled) {
throw new ArgumentError(ErrorMessages.REPLICA_DISCOVERY_NOT_SUPPORTED);
}
if (appConfigOptions.loadBalancingEnabled) {
throw new ArgumentError(ErrorMessages.LOAD_BALANCING_NOT_SUPPORTED);
}
appConfigOptions.replicaDiscoveryEnabled = false; // Disable replica discovery when loading from Azure Front Door

appConfigOptions.clientOptions = {
...appConfigOptions.clientOptions,
additionalPolicies: [
...(appConfigOptions.clientOptions?.additionalPolicies || []),
{ policy: new AnonymousRequestPipelinePolicy(), position: "perRetry" },
{ policy: new RemoveSyncTokenPipelinePolicy(), position: "perRetry" }
]
};

return await load(endpoint, emptyTokenCredential, appConfigOptions);
}
1 change: 1 addition & 0 deletions src/requestTracing/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const REPLICA_COUNT_KEY = "ReplicaCount";
export const KEY_VAULT_CONFIGURED_TAG = "UsesKeyVault";
export const KEY_VAULT_REFRESH_CONFIGURED_TAG = "RefreshesKeyVault";
export const FAILOVER_REQUEST_TAG = "Failover";
export const AFD_USED_TAG = "AFD";

// Compact feature tags
export const FEATURES_KEY = "Features";
Expand Down
8 changes: 8 additions & 0 deletions src/requestTracing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
HostType,
KEY_VAULT_CONFIGURED_TAG,
KEY_VAULT_REFRESH_CONFIGURED_TAG,
AFD_USED_TAG,
KUBERNETES_ENV_VAR,
NODEJS_DEV_ENV_VAL,
NODEJS_ENV_VAR,
Expand All @@ -50,6 +51,7 @@ export interface RequestTracingOptions {
initialLoadCompleted: boolean;
replicaCount: number;
isFailoverRequest: boolean;
isAfdUsed: boolean;
featureFlagTracing: FeatureFlagTracingOptions | undefined;
fmVersion: string | undefined;
aiConfigurationTracing: AIConfigurationTracingOptions | undefined;
Expand Down Expand Up @@ -99,7 +101,9 @@ function applyRequestTracing<T extends OperationOptions>(requestTracingOptions:
const actualOptions = { ...operationOptions };
if (requestTracingOptions.enabled) {
actualOptions.requestOptions = {
...actualOptions.requestOptions,
customHeaders: {
...actualOptions.requestOptions?.customHeaders,
[CORRELATION_CONTEXT_HEADER_NAME]: createCorrelationContextHeader(requestTracingOptions)
}
};
Expand All @@ -119,6 +123,7 @@ function createCorrelationContextHeader(requestTracingOptions: RequestTracingOpt
FFFeatures: Seed+Telemetry
UsersKeyVault
Failover
AFD
*/
const keyValues = new Map<string, string | undefined>();
const tags: string[] = [];
Expand Down Expand Up @@ -150,6 +155,9 @@ function createCorrelationContextHeader(requestTracingOptions: RequestTracingOpt
if (requestTracingOptions.isFailoverRequest) {
tags.push(FAILOVER_REQUEST_TAG);
}
if (requestTracingOptions.isAfdUsed) {
tags.push(AFD_USED_TAG);
}
if (requestTracingOptions.replicaCount > 0) {
keyValues.set(REPLICA_COUNT_KEY, requestTracingOptions.replicaCount.toString());
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export type WatchedSetting = {

export type SettingWatcher = {
etag?: string;
timestamp: Date;
}

export type PagedSettingsWatcher = SettingSelector & {
Expand Down
Loading