From 90cc63ee4f066adb0b13d08ef0b098d8397268be Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sat, 1 Mar 2025 03:14:45 +0800 Subject: [PATCH] feat(adb): use sticky event emitter for `AdbServerClient.DeviceObserver.prototype.onListChange` event --- libraries/adb-daemon-webusb/src/observer.ts | 4 ++-- libraries/adb/src/server/observer.ts | 21 +++++++++++-------- libraries/event/src/event-emitter.ts | 2 +- libraries/event/src/index.ts | 3 ++- libraries/event/src/sticky-event-emitter.ts | 23 +++++++++++++++++++++ 5 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 libraries/event/src/sticky-event-emitter.ts diff --git a/libraries/adb-daemon-webusb/src/observer.ts b/libraries/adb-daemon-webusb/src/observer.ts index e662033c..5fa8ae88 100644 --- a/libraries/adb-daemon-webusb/src/observer.ts +++ b/libraries/adb-daemon-webusb/src/observer.ts @@ -1,5 +1,5 @@ import type { DeviceObserver } from "@yume-chan/adb"; -import { EventEmitter } from "@yume-chan/event"; +import { EventEmitter, StickyEventEmitter } from "@yume-chan/event"; import { AdbDaemonWebUsbDevice, @@ -34,7 +34,7 @@ export class AdbDaemonWebUsbDeviceObserver #onDeviceRemove = new EventEmitter(); onDeviceRemove = this.#onDeviceRemove.event; - #onListChange = new EventEmitter(); + #onListChange = new StickyEventEmitter(); onListChange = this.#onListChange.event; current: AdbDaemonWebUsbDevice[] = []; diff --git a/libraries/adb/src/server/observer.ts b/libraries/adb/src/server/observer.ts index 85dbc33b..b8ec2f60 100644 --- a/libraries/adb/src/server/observer.ts +++ b/libraries/adb/src/server/observer.ts @@ -1,4 +1,4 @@ -import { EventEmitter } from "@yume-chan/event"; +import { EventEmitter, StickyEventEmitter } from "@yume-chan/event"; import { Ref } from "../utils/index.js"; @@ -95,6 +95,17 @@ export class AdbServerDeviceObserverOwner { throw options.signal.reason; } + const onDeviceAdd = new EventEmitter(); + const onDeviceRemove = new EventEmitter(); + const onListChange = new StickyEventEmitter(); + const onError = new StickyEventEmitter(); + + const observer = { onDeviceAdd, onDeviceRemove, onListChange, onError }; + // Register `observer` before `#connect`. + // Because `#connect` might immediately receive some data + // and want to trigger observers + this.#observers.push(observer); + this.#stream ??= this.#connect(); const stream = await this.#stream; @@ -103,14 +114,6 @@ export class AdbServerDeviceObserverOwner { throw options.signal.reason; } - const onDeviceAdd = new EventEmitter(); - const onDeviceRemove = new EventEmitter(); - const onListChange = new EventEmitter(); - const onError = new EventEmitter(); - - const observer = { onDeviceAdd, onDeviceRemove, onListChange, onError }; - this.#observers.push(observer); - const ref = new Ref(options); const stop = async () => { diff --git a/libraries/event/src/event-emitter.ts b/libraries/event/src/event-emitter.ts index d7f417c0..adc25c73 100644 --- a/libraries/event/src/event-emitter.ts +++ b/libraries/event/src/event-emitter.ts @@ -62,7 +62,7 @@ export class EventEmitter implements Disposable { fire(e: TEvent) { for (const info of this.listeners.slice()) { - info.listener.apply(info.thisArg, [e, ...info.args]); + info.listener.call(info.thisArg, e, ...info.args); } } diff --git a/libraries/event/src/index.ts b/libraries/event/src/index.ts index 1d74c000..7e74f200 100644 --- a/libraries/event/src/index.ts +++ b/libraries/event/src/index.ts @@ -1,4 +1,5 @@ export * from "./disposable.js"; -export * from "./event.js"; export * from "./event-emitter.js"; +export * from "./event.js"; +export * from "./sticky-event-emitter.js"; export * from "./utils.js"; diff --git a/libraries/event/src/sticky-event-emitter.ts b/libraries/event/src/sticky-event-emitter.ts new file mode 100644 index 00000000..b1290dc4 --- /dev/null +++ b/libraries/event/src/sticky-event-emitter.ts @@ -0,0 +1,23 @@ +import { EventEmitter, type EventListenerInfo } from "./event-emitter.js"; +import type { RemoveEventListener } from "./event.js"; + +export class StickyEventEmitter extends EventEmitter< + TEvent, + TResult +> { + #value: TEvent | undefined; + + protected override addEventListener( + info: EventListenerInfo, + ): RemoveEventListener { + if (this.#value) { + info.listener.call(info.thisArg, this.#value, ...info.args); + } + return super.addEventListener(info); + } + + override fire(e: TEvent): void { + this.#value = e; + super.fire(e); + } +}