@@ -9,7 +9,7 @@ import { IKeyValueAdapter } from "./IKeyValueAdapter";
9
9
import { JsonKeyValueAdapter } from "./JsonKeyValueAdapter" ;
10
10
import { DEFAULT_REFRESH_INTERVAL_IN_MS , MIN_REFRESH_INTERVAL_IN_MS } from "./RefreshOptions" ;
11
11
import { 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" ;
13
13
import { AzureKeyVaultKeyValueAdapter } from "./keyvault/AzureKeyVaultKeyValueAdapter" ;
14
14
import { RefreshTimer } from "./refresh/RefreshTimer" ;
15
15
import { getConfigurationSettingWithTrace , listConfigurationSettingsWithTrace , requestTracingEnabled } from "./requestTracing/utils" ;
@@ -36,6 +36,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
36
36
#sortedTrimKeyPrefixes: string [ ] | undefined ;
37
37
readonly #requestTracingEnabled: boolean ;
38
38
#client: AppConfigurationClient ;
39
+ #clientEndpoint: string | undefined ;
39
40
#options: AzureAppConfigurationOptions | undefined ;
40
41
#isInitialLoadCompleted: boolean = false ;
41
42
@@ -57,9 +58,11 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
57
58
58
59
constructor (
59
60
client : AppConfigurationClient ,
61
+ clientEndpoint : string | undefined ,
60
62
options : AzureAppConfigurationOptions | undefined
61
63
) {
62
64
this . #client = client ;
65
+ this . #clientEndpoint = clientEndpoint ;
63
66
this . #options = options ;
64
67
65
68
// Enable request tracing if not opt-out
@@ -255,8 +258,7 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
255
258
}
256
259
257
260
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 [ ] = [ ] ;
260
262
for ( const selector of this . #featureFlagSelectors) {
261
263
const listOptions : ListConfigurationSettingsOptions = {
262
264
keyFilter : `${ featureFlagPrefix } ${ selector . keyFilter } ` ,
@@ -273,15 +275,17 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
273
275
pageEtags . push ( page . etag ?? "" ) ;
274
276
for ( const setting of page . items ) {
275
277
if ( isFeatureFlag ( setting ) ) {
276
- featureFlagsMap . set ( setting . key , setting . value ) ;
278
+ featureFlagSettings . push ( setting ) ;
277
279
}
278
280
}
279
281
}
280
282
selector . pageEtags = pageEtags ;
281
283
}
282
284
283
285
// 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
+ ) ;
285
289
286
290
// feature_management is a reserved key, and feature_flags is an array of feature flags
287
291
this . #configMap. set ( FEATURE_MANAGEMENT_KEY_NAME , { [ FEATURE_FLAGS_KEY_NAME ] : featureFlags } ) ;
@@ -532,6 +536,83 @@ export class AzureAppConfigurationImpl implements AzureAppConfiguration {
532
536
}
533
537
return response ;
534
538
}
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
+ }
535
616
}
536
617
537
618
function getValidSelectors ( selectors : SettingSelector [ ] ) : SettingSelector [ ] {
0 commit comments