@@ -140,6 +140,21 @@ interface CommandParams {
140140 newEndpoint : string ;
141141 oldEndpoint : string ;
142142 } ;
143+ registerFlagChangeListener ?: {
144+ listenerId : string ;
145+ flagKey : string ;
146+ callbackUri : string ;
147+ } ;
148+ registerFlagValueChangeListener ?: {
149+ listenerId : string ;
150+ flagKey : string ;
151+ context : LDContext ;
152+ defaultValue : LDFlagValue ;
153+ callbackUri : string ;
154+ } ;
155+ unregisterListener ?: {
156+ listenerId : string ;
157+ } ;
143158}
144159
145160export function makeSdkConfig ( options : SdkConfigOptions , tag : string ) : LDOptions {
@@ -316,9 +331,15 @@ export interface SdkClientEntity {
316331 doCommand : ( params : CommandParams ) => Promise < any > ;
317332}
318333
334+ interface ListenerEntry {
335+ eventName : string ;
336+ handler : ( ...args : any [ ] ) => void ;
337+ }
338+
319339export async function newSdkClientEntity ( options : any ) : Promise < SdkClientEntity > {
320340 const c : any = { } ;
321341 const log = Log ( options . tag ) ;
342+ const listeners = new Map < string , ListenerEntry > ( ) ;
322343
323344 log . info ( `Creating client with configuration: ${ JSON . stringify ( options . configuration ) } ` ) ;
324345 const timeout =
@@ -341,6 +362,11 @@ export async function newSdkClientEntity(options: any): Promise<SdkClientEntity>
341362 }
342363
343364 c . close = ( ) => {
365+ // Unregister all listeners before closing to avoid firing callbacks after shutdown.
366+ listeners . forEach ( ( entry ) => {
367+ client . off ( entry . eventName , entry . handler ) ;
368+ } ) ;
369+ listeners . clear ( ) ;
344370 client . close ( ) ;
345371 log . info ( 'Test ended' ) ;
346372 } ;
@@ -514,6 +540,69 @@ export async function newSdkClientEntity(options: any): Promise<SdkClientEntity>
514540 }
515541 }
516542
543+ case 'registerFlagChangeListener' : {
544+ const p = params . registerFlagChangeListener ! ;
545+ // 'update:key' fires for a specific flag; 'update' (no key) fires for any flag change.
546+ const eventName = p . flagKey ? `update:${ p . flagKey } ` : 'update' ;
547+
548+ const handler = ( eventParams : { key : string } ) => {
549+ got
550+ . post ( p . callbackUri , {
551+ json : {
552+ listenerId : p . listenerId ,
553+ flagKey : eventParams . key ,
554+ } ,
555+ } )
556+ . catch ( ( ) => { } ) ;
557+ } ;
558+
559+ listeners . set ( p . listenerId , { eventName, handler } ) ;
560+ client . on ( eventName , handler ) ;
561+ return undefined ;
562+ }
563+
564+ case 'registerFlagValueChangeListener' : {
565+ const p = params . registerFlagValueChangeListener ! ;
566+ const eventName = `update:${ p . flagKey } ` ;
567+
568+ // Snapshot the current evaluated value so we can detect actual value changes.
569+ // On each SDK update event, re-evaluate and only notify the harness if the
570+ // evaluated value differs (using JSON comparison for deep equality).
571+ let oldValue = await client . variation ( p . flagKey , p . context , p . defaultValue ) ;
572+
573+ const handler = async ( ) => {
574+ const newValue = await client . variation ( p . flagKey , p . context , p . defaultValue ) ;
575+ if ( JSON . stringify ( newValue ) !== JSON . stringify ( oldValue ) ) {
576+ const previousValue = oldValue ;
577+ oldValue = newValue ;
578+ got
579+ . post ( p . callbackUri , {
580+ json : {
581+ listenerId : p . listenerId ,
582+ flagKey : p . flagKey ,
583+ oldValue : previousValue ,
584+ newValue,
585+ } ,
586+ } )
587+ . catch ( ( ) => { } ) ;
588+ }
589+ } ;
590+
591+ listeners . set ( p . listenerId , { eventName, handler } ) ;
592+ client . on ( eventName , handler ) ;
593+ return undefined ;
594+ }
595+
596+ case 'unregisterListener' : {
597+ const p = params . unregisterListener ! ;
598+ const entry = listeners . get ( p . listenerId ) ;
599+ if ( entry ) {
600+ client . off ( entry . eventName , entry . handler ) ;
601+ listeners . delete ( p . listenerId ) ;
602+ }
603+ return undefined ;
604+ }
605+
517606 default :
518607 throw badCommandError ;
519608 }
0 commit comments