Skip to content

Commit 2c43bcc

Browse files
authored
fix: #417 ensure BrowserEventEmitter off removes listeners (#419)
1 parent b4d315b commit 2c43bcc

File tree

3 files changed

+91
-10
lines changed

3 files changed

+91
-10
lines changed

.changeset/tender-crews-promise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@openai/agents-core": patch
3+
---
4+
5+
fix: #417 ensure BrowserEventEmitter off removes listeners

packages/agents-core/src/shims/shims-browser.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,49 @@ export class BrowserEventEmitter<
1616
> implements EventEmitter<EventTypes>
1717
{
1818
#target = new EventTarget();
19+
#listenerWrappers = new Map<
20+
string,
21+
Map<(...args: EventTypes[any]) => void, Set<EventListener>>
22+
>();
1923

2024
on<K extends keyof EventTypes>(
2125
type: K,
2226
listener: (...args: EventTypes[K]) => void,
2327
) {
24-
this.#target.addEventListener(
25-
type as string,
26-
((event: CustomEvent) =>
27-
listener(...(event.detail ?? []))) as EventListener,
28-
);
28+
const eventType = type as string;
29+
let listenersForType = this.#listenerWrappers.get(eventType);
30+
if (!listenersForType) {
31+
listenersForType = new Map();
32+
this.#listenerWrappers.set(eventType, listenersForType);
33+
}
34+
let wrappers = listenersForType.get(listener);
35+
if (!wrappers) {
36+
wrappers = new Set();
37+
listenersForType.set(listener, wrappers);
38+
}
39+
const wrapper = ((event: CustomEvent) =>
40+
listener(...(event.detail ?? []))) as EventListener;
41+
wrappers.add(wrapper);
42+
this.#target.addEventListener(eventType, wrapper);
2943
return this;
3044
}
3145

3246
off<K extends keyof EventTypes>(
3347
type: K,
3448
listener: (...args: EventTypes[K]) => void,
3549
) {
36-
this.#target.removeEventListener(
37-
type as string,
38-
((event: CustomEvent) =>
39-
listener(...(event.detail ?? []))) as EventListener,
40-
);
50+
const eventType = type as string;
51+
const listenersForType = this.#listenerWrappers.get(eventType);
52+
const wrappers = listenersForType?.get(listener);
53+
if (wrappers?.size) {
54+
for (const wrapper of wrappers) {
55+
this.#target.removeEventListener(eventType, wrapper);
56+
}
57+
listenersForType?.delete(listener);
58+
if (listenersForType?.size === 0) {
59+
this.#listenerWrappers.delete(eventType);
60+
}
61+
}
4162
return this;
4263
}
4364

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, test } from 'vitest';
2+
3+
import { BrowserEventEmitter } from '../../src/shims/shims-browser';
4+
5+
describe('BrowserEventEmitter', () => {
6+
test('off removes previously registered listener', () => {
7+
const emitter = new BrowserEventEmitter<{ foo: [string] }>();
8+
const calls: string[] = [];
9+
10+
const handler = (value: string) => {
11+
calls.push(value);
12+
};
13+
14+
emitter.on('foo', handler);
15+
emitter.emit('foo', 'first');
16+
emitter.off('foo', handler);
17+
emitter.emit('foo', 'second');
18+
19+
expect(calls).toEqual(['first']);
20+
});
21+
22+
test('once triggers listener only once', () => {
23+
const emitter = new BrowserEventEmitter<{ foo: [string] }>();
24+
let callCount = 0;
25+
26+
emitter.once('foo', () => {
27+
callCount += 1;
28+
});
29+
30+
emitter.emit('foo', 'first');
31+
emitter.emit('foo', 'second');
32+
33+
expect(callCount).toBe(1);
34+
});
35+
36+
test('multiple identical listeners fire for each registration and are removed by off', () => {
37+
const emitter = new BrowserEventEmitter<{ foo: [string] }>();
38+
const calls: string[] = [];
39+
40+
const handler = (value: string) => {
41+
calls.push(value);
42+
};
43+
44+
emitter.on('foo', handler);
45+
emitter.on('foo', handler);
46+
47+
emitter.emit('foo', 'first');
48+
expect(calls).toEqual(['first', 'first']);
49+
50+
emitter.off('foo', handler);
51+
emitter.emit('foo', 'second');
52+
53+
expect(calls).toEqual(['first', 'first']);
54+
});
55+
});

0 commit comments

Comments
 (0)