Skip to content

Commit caa112c

Browse files
rochdevclaude
andcommitted
fix(openfeature): implement full ProviderEventEmitter contract in the deferred bridge emitter
@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...) on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the app registered any handlers itself. The deferred emitter only implemented addHandler/emit, so replacing the Datadog OpenFeature provider with another provider at runtime threw "oldProvider.events.removeHandler is not a function", crashing the customer's app. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent e2db427 commit caa112c

2 files changed

Lines changed: 67 additions & 0 deletions

File tree

packages/dd-trace/src/openfeature/server-sdk-bridge.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,38 @@ class DeferredOpenFeatureEventEmitter {
5353
emit (eventType, details) {
5454
this.#target()?.emit(eventType, details)
5555
}
56+
57+
/**
58+
* @param {string} eventType
59+
* @param {(details?: unknown) => void} handler
60+
*/
61+
removeHandler (eventType, handler) {
62+
this.#target()?.removeHandler(eventType, handler)
63+
}
64+
65+
/**
66+
* @param {string} [eventType]
67+
*/
68+
removeAllHandlers (eventType) {
69+
this.#target()?.removeAllHandlers(eventType)
70+
}
71+
72+
/**
73+
* @param {string} eventType
74+
* @returns {((details?: unknown) => void)[]}
75+
*/
76+
getHandlers (eventType) {
77+
return this.#target()?.getHandlers(eventType) ?? []
78+
}
79+
80+
/**
81+
* @param {import('@openfeature/server-sdk').Logger} logger
82+
* @returns {this}
83+
*/
84+
setLogger (logger) {
85+
this.#target()?.setLogger(logger)
86+
return this
87+
}
5688
}
5789

5890
module.exports = {

packages/dd-trace/test/openfeature/server-sdk-bridge.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ describe('OpenFeature server-sdk bridge', () => {
2929

3030
emitter.addHandler(bridge.ProviderEvents.Ready, sinon.spy())
3131
emitter.emit(bridge.ProviderEvents.Ready)
32+
emitter.removeHandler(bridge.ProviderEvents.Ready, sinon.spy())
33+
emitter.removeAllHandlers(bridge.ProviderEvents.Ready)
34+
assert.deepStrictEqual(emitter.getHandlers(bridge.ProviderEvents.Ready), [])
35+
assert.strictEqual(emitter.setLogger({}), emitter)
3236
})
3337

3438
it('forwards addHandler and emit to the real emitter once one is registered', () => {
@@ -52,6 +56,37 @@ describe('OpenFeature server-sdk bridge', () => {
5256
sinon.assert.calledOnceWithExactly(handler, { some: 'details' })
5357
})
5458

59+
it('forwards removeHandler, removeAllHandlers, getHandlers, and setLogger to the real emitter', () => {
60+
const handlers = [sinon.spy()]
61+
const realEmitterInstance = {
62+
addHandler: sinon.spy(),
63+
emit: sinon.spy(),
64+
removeHandler: sinon.spy(),
65+
removeAllHandlers: sinon.spy(),
66+
getHandlers: sinon.stub().returns(handlers),
67+
setLogger: sinon.spy(),
68+
}
69+
const RealEventEmitter = sinon.stub().returns(realEmitterInstance)
70+
71+
bridge.setEventEmitter(RealEventEmitter)
72+
73+
const emitter = new bridge.OpenFeatureEventEmitter()
74+
const handler = sinon.spy()
75+
const logger = {}
76+
77+
emitter.removeHandler('PROVIDER_READY', handler)
78+
emitter.removeAllHandlers('PROVIDER_READY')
79+
const result = emitter.getHandlers('PROVIDER_READY')
80+
const returned = emitter.setLogger(logger)
81+
82+
sinon.assert.calledOnceWithExactly(realEmitterInstance.removeHandler, 'PROVIDER_READY', handler)
83+
sinon.assert.calledOnceWithExactly(realEmitterInstance.removeAllHandlers, 'PROVIDER_READY')
84+
sinon.assert.calledOnceWithExactly(realEmitterInstance.getHandlers, 'PROVIDER_READY')
85+
assert.strictEqual(result, handlers)
86+
sinon.assert.calledOnceWithExactly(realEmitterInstance.setLogger, logger)
87+
assert.strictEqual(returned, emitter)
88+
})
89+
5590
it('reuses the same real emitter instance across calls on the same deferred emitter', () => {
5691
const realEmitterInstance = {
5792
addHandler: sinon.spy(),

0 commit comments

Comments
 (0)