@@ -9,7 +9,7 @@ import { IKeyValueAdapter } from "./IKeyValueAdapter";
99import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter" ;
1010import { DEFAULT_REFRESH_INTERVAL_IN_MS , MIN_REFRESH_INTERVAL_IN_MS } from "./RefreshOptions" ;
1111import { Disposable } from "./common/disposable" ;
12- import { FEATURE_FLAGS_KEY_NAME , FEATURE_MANAGEMENT_KEY_NAME } from "./featureManagement/constants" ;
12+ import { FEATURE_FLAGS_KEY_NAME , FEATURE_MANAGEMENT_KEY_NAME , TELEMETRY_KEY_NAME , ENABLED_KEY_NAME , METADATA_KEY_NAME , ETAG_KEY_NAME , FEATURE_FLAG_ID_KEY_NAME , FEATURE_FLAG_REFERENCE_KEY_NAME } from "./featureManagement/constants" ;
1313import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter" ;
1414import { RefreshTimer } from "./refresh/RefreshTimer" ;
1515import { getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils" ;
@@ -36,6 +36,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
3636 #sortedTrimKeyPrefixes: string [ ] | undefined ;
3737 readonly #requestTracingEnabled: boolean ;
3838 #client: AppConfigurationClient ;
39+ #clientEndpoint: string | undefined ;
3940 #options: AzureAppConfigurationOptions | undefined ;
4041 #isInitialLoadCompleted: boolean = false ;
4142
@@ -57,9 +58,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
5758
5859 constructor (
5960 client : AppConfigurationClient ,
61+ clientEndpoint : string | undefined ,
6062 options : AzureAppConfigurationOptions | undefined
6163 ) {
6264 this . #client = client ;
65+ this . #clientEndpoint = clientEndpoint ;
6366 this . #options = options ;
6467
6568 // Enable request tracing if not opt-out
@@ -255,8 +258,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
255258 }
256259
257260 async #loadFeatureFlags( ) {
258- // Temporary map to store feature flags, key is the key of the setting, value is the raw value of the setting
259- const featureFlagsMap = new Map < string , any > ( ) ;
261+ const featureFlagSettings : ConfigurationSetting [ ] = [ ] ;
260262 for ( const selector of this . #featureFlagSelectors) {
261263 const listOptions : ListConfigurationSettingsOptions = {
262264 keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
@@ -273,15 +275,17 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
273275 pageEtags . push ( page . etag ?? "" ) ;
274276 for ( const setting of page . items ) {
275277 if ( isFeatureFlag ( setting ) ) {
276- featureFlagsMap . set ( setting . key , setting . value ) ;
278+ featureFlagSettings . push ( setting ) ;
277279 }
278280 }
279281 }
280282 selector . pageEtags = pageEtags ;
281283 }
282284
283285 // parse feature flags
284- const featureFlags = Array . from ( featureFlagsMap . values ( ) ) . map ( rawFlag => JSON . parse ( rawFlag ) ) ;
286+ const featureFlags = await Promise . all (
287+ featureFlagSettings . map ( setting => this . #parseFeatureFlag( setting ) )
288+ ) ;
285289
286290 // feature_management is a reserved key, and feature_flags is an array of feature flags
287291 this . #configMap. set ( FEATURE_MANAGEMENT_KEY_NAME , { [ FEATURE_FLAGS_KEY_NAME ] : featureFlags } ) ;
@@ -532,6 +536,83 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
532536 }
533537 return response ;
534538 }
539+
540+ async #parseFeatureFlag( setting : ConfigurationSetting < string > ) : Promise < any > {
541+ const rawFlag = setting . value ;
542+ if ( rawFlag === undefined ) {
543+ throw new Error ( "The value of configuration setting cannot be undefined." ) ;
544+ }
545+ const featureFlag = JSON . parse ( rawFlag ) ;
546+
547+ if ( featureFlag [ TELEMETRY_KEY_NAME ] && featureFlag [ TELEMETRY_KEY_NAME ] [ ENABLED_KEY_NAME ] === true ) {
548+ const metadata = featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] ;
549+ featureFlag [ TELEMETRY_KEY_NAME ] [ METADATA_KEY_NAME ] = {
550+ [ ETAG_KEY_NAME ] : setting . etag ,
551+ [ FEATURE_FLAG_ID_KEY_NAME ] : await this . #calculateFeatureFlagId( setting ) ,
552+ [ FEATURE_FLAG_REFERENCE_KEY_NAME ] : this . #createFeatureFlagReference( setting ) ,
553+ ...( metadata || { } )
554+ } ;
555+ }
556+
557+ return featureFlag ;
558+ }
559+
560+ async #calculateFeatureFlagId( setting : ConfigurationSetting < string > ) : Promise < string > {
561+ let crypto ;
562+
563+ // Check for browser environment
564+ if ( typeof window !== "undefined" && window . crypto && window . crypto . subtle ) {
565+ crypto = window . crypto ;
566+ }
567+ // Check for Node.js environment
568+ else if ( typeof global !== "undefined" && global . crypto ) {
569+ crypto = global . crypto ;
570+ }
571+ // Fallback to native Node.js crypto module
572+ else {
573+ try {
574+ if ( typeof module !== "undefined" && module . exports ) {
575+ crypto = require ( "crypto" ) ;
576+ }
577+ else {
578+ crypto = await import ( "crypto" ) ;
579+ }
580+ } catch ( error ) {
581+ console . error ( "Failed to load the crypto module:" , error . message ) ;
582+ throw error ;
583+ }
584+ }
585+
586+ let baseString = `${ setting . key } \n` ;
587+ if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
588+ baseString += `${ setting . label } ` ;
589+ }
590+
591+ // Convert to UTF-8 encoded bytes
592+ const data = new TextEncoder ( ) . encode ( baseString ) ;
593+
594+ // In the browser, use crypto.subtle.digest
595+ if ( crypto . subtle ) {
596+ const hashBuffer = await crypto . subtle . digest ( "SHA-256" , data ) ;
597+ const hashArray = new Uint8Array ( hashBuffer ) ;
598+ const base64String = btoa ( String . fromCharCode ( ...hashArray ) ) ;
599+ const base64urlString = base64String . replace ( / \+ / g, "-" ) . replace ( / \/ / g, "_" ) . replace ( / = + $ / , "" ) ;
600+ return base64urlString ;
601+ }
602+ // In Node.js, use the crypto module's hash function
603+ else {
604+ const hash = crypto . createHash ( "sha256" ) . update ( data ) . digest ( ) ;
605+ return hash . toString ( "base64url" ) ;
606+ }
607+ }
608+
609+ #createFeatureFlagReference( setting : ConfigurationSetting < string > ) : string {
610+ let featureFlagReference = `${ this . #clientEndpoint} kv/${ setting . key } ` ;
611+ if ( setting . label && setting . label . trim ( ) . length !== 0 ) {
612+ featureFlagReference += `?label=${ setting . label } ` ;
613+ }
614+ return featureFlagReference ;
615+ }
535616}
536617
537618function getValidSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
0 commit comments