Skip to content

Commit 369a438

Browse files
feat: add optional event emission to current event target and fix up … (#286)
* feat: add optional event emission to current event target and fix up memory leak * chore: changeset * chore: fix up potential evvent buildup on failed connections * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 946bfb2 commit 369a438

File tree

4 files changed

+84
-12
lines changed

4 files changed

+84
-12
lines changed

.changeset/real-weeks-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/devtools-event-client': minor
3+
---
4+
5+
fix memory leak and add internal event emission

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
},
5555
{
5656
"path": "packages/event-bus-client/dist/esm/plugin.js",
57-
"limit": "1.1 KB"
57+
"limit": "1.2 KB"
5858
}
5959
],
6060
"devDependencies": {

packages/event-bus-client/src/plugin.ts

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export class EventClient<
3030
#retryCount = 0
3131
#maxRetries = 5
3232
#connecting = false
33+
#failedToConnect = false
34+
#internalEventTarget: EventTarget | null = null
3335

3436
#onConnected = () => {
3537
this.debugLog('Connected to event bus')
@@ -56,7 +58,7 @@ export class EventClient<
5658
'tanstack-connect',
5759
this.#retryConnection,
5860
)
59-
61+
this.#failedToConnect = true
6062
this.debugLog('Max retries reached, giving up on connection')
6163
this.stopConnectLoop()
6264
}
@@ -90,6 +92,7 @@ export class EventClient<
9092
this.debugLog(' Initializing event subscription for plugin', this.#pluginId)
9193
this.#queuedEvents = []
9294
this.#connected = false
95+
this.#failedToConnect = false
9396
this.#connectIntervalId = null
9497
this.#connectEveryMs = reconnectEveryMs
9598
}
@@ -107,11 +110,13 @@ export class EventClient<
107110

108111
private stopConnectLoop() {
109112
this.#connecting = false
113+
110114
if (this.#connectIntervalId === null) {
111115
return
112116
}
113117
clearInterval(this.#connectIntervalId)
114118
this.#connectIntervalId = null
119+
this.#queuedEvents = []
115120
this.debugLog('Stopped connect loop')
116121
}
117122

@@ -189,6 +194,23 @@ export class EventClient<
189194
this.dispatchCustomEvent('tanstack-dispatch-event', event)
190195
}
191196

197+
createEventPayload<
198+
TSuffix extends Extract<
199+
keyof TEventMap,
200+
`${TPluginId & string}:${string}`
201+
> extends `${TPluginId & string}:${infer S}`
202+
? S
203+
: never,
204+
>(
205+
eventSuffix: TSuffix,
206+
payload: TEventMap[`${TPluginId & string}:${TSuffix}`],
207+
) {
208+
return {
209+
type: `${this.#pluginId}:${eventSuffix}`,
210+
payload,
211+
pluginId: this.#pluginId,
212+
}
213+
}
192214
emit<
193215
TSuffix extends Extract<
194216
keyof TEventMap,
@@ -208,14 +230,27 @@ export class EventClient<
208230
)
209231
return
210232
}
233+
if (this.#internalEventTarget) {
234+
this.debugLog(
235+
'Emitting event to internal event target',
236+
eventSuffix,
237+
payload,
238+
)
239+
this.#internalEventTarget.dispatchEvent(
240+
new CustomEvent(`${this.#pluginId}:${eventSuffix}`, {
241+
detail: this.createEventPayload(eventSuffix, payload),
242+
}),
243+
)
244+
}
245+
246+
if (this.#failedToConnect) {
247+
this.debugLog('Previously failed to connect, not emitting to bus')
248+
return
249+
}
211250
// wait to connect to the bus
212251
if (!this.#connected) {
213252
this.debugLog('Bus not available, will be pushed as soon as connected')
214-
this.#queuedEvents.push({
215-
type: `${this.#pluginId}:${eventSuffix}`,
216-
payload,
217-
pluginId: this.#pluginId,
218-
})
253+
this.#queuedEvents.push(this.createEventPayload(eventSuffix, payload))
219254
// start connection to event bus
220255
if (typeof CustomEvent !== 'undefined' && !this.#connecting) {
221256
this.#connectFunction()
@@ -224,11 +259,7 @@ export class EventClient<
224259
return
225260
}
226261
// emit right now
227-
return this.emitEventToBus({
228-
type: `${this.#pluginId}:${eventSuffix}`,
229-
payload,
230-
pluginId: this.#pluginId,
231-
})
262+
return this.emitEventToBus(this.createEventPayload(eventSuffix, payload))
232263
}
233264

234265
on<
@@ -246,8 +277,20 @@ export class EventClient<
246277
TEventMap[`${TPluginId & string}:${TSuffix}`]
247278
>,
248279
) => void,
280+
options?: {
281+
withEventTarget?: boolean
282+
},
249283
) {
284+
const withEventTarget = options?.withEventTarget ?? false
250285
const eventName = `${this.#pluginId}:${eventSuffix}` as const
286+
if (withEventTarget) {
287+
if (!this.#internalEventTarget) {
288+
this.#internalEventTarget = new EventTarget()
289+
}
290+
this.#internalEventTarget.addEventListener(eventName, (e) => {
291+
cb((e as CustomEvent).detail)
292+
})
293+
}
251294
if (!this.#enabled) {
252295
this.debugLog(
253296
'Event bus client is disabled, not registering event',
@@ -262,6 +305,9 @@ export class EventClient<
262305
this.#eventTarget().addEventListener(eventName, handler)
263306
this.debugLog('Registered event to bus', eventName)
264307
return () => {
308+
if (withEventTarget) {
309+
this.#internalEventTarget?.removeEventListener(eventName, handler)
310+
}
265311
this.#eventTarget().removeEventListener(eventName, handler)
266312
}
267313
}

packages/event-bus-client/tests/index.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,27 @@ describe('EventClient', () => {
284284
})
285285
})
286286

287+
describe('emitting to internal event target', () => {
288+
it('should initialize and dispatch events to the internal event target', () => {
289+
const client = new EventClient({
290+
debug: false,
291+
pluginId: 'test-internal',
292+
})
293+
const internalEventHandler = vi.fn()
294+
client.on('event', internalEventHandler, {
295+
withEventTarget: true,
296+
})
297+
client.emit('event', { foo: 'bar' })
298+
expect(internalEventHandler).toHaveBeenCalledWith(
299+
expect.objectContaining({
300+
type: 'test-internal:event',
301+
payload: { foo: 'bar' },
302+
pluginId: 'test-internal',
303+
}),
304+
)
305+
})
306+
})
307+
287308
describe('connecting behavior', () => {
288309
it('should only attempt connection once when #connecting flag is set', async () => {
289310
bus.stop()

0 commit comments

Comments
 (0)