Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved EventEmitter for tauri api shell #4697

Merged
merged 3 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/improve-event-emitter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"api": minor
---

Improve shell's `Command`, `Command.stdout` and `Command.stderr` events with new `once`, `off`, `listenerCount`, `prependListener`, `prependOnceListener` and `removeAllListeners` functions.
2 changes: 1 addition & 1 deletion core/tauri/scripts/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/api/dist/assets/index.css

Large diffs are not rendered by default.

54 changes: 27 additions & 27 deletions examples/api/dist/assets/index.js

Large diffs are not rendered by default.

9 changes: 1 addition & 8 deletions examples/api/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@
svelte-hmr "^0.14.12"

"@tauri-apps/api@../../tooling/api/dist":
version "1.0.1"
dependencies:
type-fest "2.14.0"
version "1.0.2"

"@unocss/cli@0.39.3":
version "0.39.3"
Expand Down Expand Up @@ -888,11 +886,6 @@ totalist@^3.0.0:
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==

type-fest@2.14.0:
version "2.14.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.14.0.tgz#f990e19169517d689c98e16d128b231022b27e12"
integrity sha512-hQnTQkFjL5ik6HF2fTAM8ycbr94UbQXK364wF930VHb0dfBJ5JBP8qwrR8TaK9zwUEk7meruo2JAUDMwvuxd/w==

ufo@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.8.4.tgz#23e9ed82398d2116dcb378e8fba5ced8eca2ee40"
Expand Down
167 changes: 140 additions & 27 deletions tooling/api/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,46 +134,159 @@ async function execute(
}

class EventEmitter<E extends string> {
/** @ignore */
/** @ignore */
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
private eventListeners: {
[key: string]: Array<(arg: any) => void>
} = Object.create(null)
private eventListeners: Record<E, Array<(...args: any[]) => void>> =
Object.create(null)

/** @ignore */
private addEventListener(event: string, handler: (arg: any) => void): void {
if (event in this.eventListeners) {
/**
* Alias for `emitter.on(eventName, listener)`.
*/
addListener(eventName: E, listener: (...args: any[]) => void): this {
return this.on(eventName, listener)
}

/**
* Alias for `emitter.off(eventName, listener)`.
*/
removeListener(eventName: E, listener: (...args: any[]) => void): this {
return this.off(eventName, listener)
}

/**
* Adds the `listener` function to the end of the listeners array for the
* event named `eventName`. No checks are made to see if the `listener` has
* already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple
* times.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
on(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event].push(handler)
this.eventListeners[eventName].push(listener)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event] = [handler]
this.eventListeners[eventName] = [listener]
}
return this
}

/** @ignore */
_emit(event: E, payload: any): void {
if (event in this.eventListeners) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const listeners = this.eventListeners[event as any]
for (const listener of listeners) {
listener(payload)
}
/**
* Adds a **one-time**`listener` function for the event named `eventName`. The
* next time `eventName` is triggered, this listener is removed and then invoked.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*
* @param eventName The name of the event.
* @param listener The callback function
*/
once(eventName: E, listener: (...args: any[]) => void): this {
const wrapper = (...args: any[]): void => {
this.removeListener(eventName, wrapper)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listener(...args)
}
return this.addListener(eventName, wrapper)
}

/**
* Removes the all specified listener from the listener array for the event eventName
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*/
off(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName] = this.eventListeners[eventName].filter(
(l) => l !== listener
)
}
return this
}

/**
* Removes all listeners, or those of the specified eventName.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
*/
removeAllListeners(event?: E): this {
if (event) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete,security/detect-object-injection
delete this.eventListeners[event]
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
this.eventListeners = Object.create(null)
}
return this
}

/**
* Listen to an event from the child process.
* @ignore
* Synchronously calls each of the listeners registered for the event named`eventName`, in the order they were registered, passing the supplied arguments
* to each.
*
* @param event The event name.
* @param handler The event handler.
* Returns `true` if the event had listeners, `false` otherwise.
*/
emit(eventName: E, ...args: any[]): boolean {
if (eventName in this.eventListeners) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,security/detect-object-injection
const listeners = this.eventListeners[eventName]
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
for (const listener of listeners) listener(...args)
return true
}
return false
}

/**
* Returns the number of listeners listening to the event named `eventName`.
*/
listenerCount(eventName: E): number {
if (eventName in this.eventListeners)
// eslint-disable-next-line security/detect-object-injection
return this.eventListeners[eventName].length
return 0
}

/**
* Adds the `listener` function to the _beginning_ of the listeners array for the
* event named `eventName`. No checks are made to see if the `listener` has
* already been added. Multiple calls passing the same combination of `eventName`and `listener` will result in the `listener` being added, and called, multiple
* times.
*
* @return The `this` instance for chained calls.
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
on(event: E, handler: (arg: any) => void): EventEmitter<E> {
this.addEventListener(event, handler)
prependListener(eventName: E, listener: (...args: any[]) => void): this {
if (eventName in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName].unshift(listener)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[eventName] = [listener]
}
return this
}

/**
* Adds a **one-time**`listener` function for the event named `eventName` to the_beginning_ of the listeners array. The next time `eventName` is triggered, this
* listener is removed, and then invoked.
*
* Returns a reference to the `EventEmitter`, so that calls can be chained.
* @param eventName The name of the event.
* @param listener The callback function
*/
prependOnceListener(eventName: E, listener: (...args: any[]) => void): this {
const wrapper = (...args: any[]): void => {
this.removeListener(eventName, wrapper)
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listener(...args)
}
return this.prependListener(eventName, wrapper)
}
}

class Child {
Expand Down Expand Up @@ -311,16 +424,16 @@ class Command extends EventEmitter<'close' | 'error'> {
(event) => {
switch (event.event) {
case 'Error':
this._emit('error', event.payload)
this.emit('error', event.payload)
break
case 'Terminated':
this._emit('close', event.payload)
this.emit('close', event.payload)
break
case 'Stdout':
this.stdout._emit('data', event.payload)
this.stdout.emit('data', event.payload)
break
case 'Stderr':
this.stderr._emit('data', event.payload)
this.stderr.emit('data', event.payload)
break
}
},
Expand Down