mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-01 10:01:07 +02:00
refactor(core): refactor and fix event system following multiwebview support (#8621)
* clippy * refactor(core): refactor and fix event system following multiwebview support * update documentation * update js docs * lint * clippy * update multiwindow example [skip ci] * enhance event tests * fix example * Update .changes/tauri-event-after-multiwebview.md Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com> * fix tests * add diagram * Add `App/AppHandle` even target * Discard changes to examples/api/src-tauri/tauri-plugin-sample/permissions/schemas/schema.json * revert accidental changes * regenerate schemas * fix doctests * add helper methods * update docs * update api * update docs [skip ci] * update docs [skip ci] --------- Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Lucas Nogueira <118899497+lucasfernog-crabnebula@users.noreply.github.com>
This commit is contained in:
@@ -11,24 +11,17 @@
|
||||
|
||||
import { invoke, transformCallback } from './core'
|
||||
|
||||
type EventSource =
|
||||
| {
|
||||
kind: 'global'
|
||||
}
|
||||
| {
|
||||
kind: 'window'
|
||||
label: string
|
||||
}
|
||||
| {
|
||||
kind: 'webview'
|
||||
label: string
|
||||
}
|
||||
type EventTarget =
|
||||
| { kind: 'Any' }
|
||||
| { kind: 'AnyLabel'; label: string }
|
||||
| { kind: 'App' }
|
||||
| { kind: 'Window'; label: string }
|
||||
| { kind: 'Webview'; label: string }
|
||||
| { kind: 'WebviewWindow'; label: string }
|
||||
|
||||
interface Event<T> {
|
||||
/** Event name */
|
||||
event: EventName
|
||||
/** The source of the event. Can be a global event, an event from a window or an event from another webview. */
|
||||
source: EventSource
|
||||
/** Event identifier used to unlisten */
|
||||
id: number
|
||||
/** Event payload */
|
||||
@@ -43,16 +36,11 @@ type EventName = `${TauriEvent}` | (string & Record<never, never>)
|
||||
|
||||
interface Options {
|
||||
/**
|
||||
* Window or webview the function targets.
|
||||
* The event target to listen to, defaults to `{ kind: 'Any' }`, see {@link EventTarget}.
|
||||
*
|
||||
* When listening to events and using this value,
|
||||
* only events triggered by the window with the given label are received.
|
||||
*
|
||||
* When emitting events, only the window with the given label will receive it.
|
||||
* If a string is provided, {@link EventTarget.AnyLabel} is used.
|
||||
*/
|
||||
target?:
|
||||
| { kind: 'window'; label: string }
|
||||
| { kind: 'webview'; label: string }
|
||||
target?: string | EventTarget
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,14 +77,13 @@ async function _unlisten(event: string, eventId: number): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an event. The event can be either global or window-specific.
|
||||
* See {@link Event.source} to check the event source.
|
||||
* Listen to an emitted event to any {@link EventTarget|target}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { listen } from '@tauri-apps/api/event';
|
||||
* const unlisten = await listen<string>('error', (event) => {
|
||||
* console.log(`Got error in window ${event.source}, payload: ${event.payload}`);
|
||||
* console.log(`Got error, payload: ${event.payload}`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
@@ -105,6 +92,7 @@ async function _unlisten(event: string, eventId: number): Promise<void> {
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler callback.
|
||||
* @param options Event listening options.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*
|
||||
@@ -115,9 +103,13 @@ async function listen<T>(
|
||||
handler: EventCallback<T>,
|
||||
options?: Options
|
||||
): Promise<UnlistenFn> {
|
||||
const target: EventTarget =
|
||||
typeof options?.target === 'string'
|
||||
? { kind: 'AnyLabel', label: options.target }
|
||||
: options?.target ?? { kind: 'Any' }
|
||||
return invoke<number>('plugin:event|listen', {
|
||||
event,
|
||||
target: options?.target,
|
||||
target,
|
||||
handler: transformCallback(handler)
|
||||
}).then((eventId) => {
|
||||
return async () => _unlisten(event, eventId)
|
||||
@@ -125,7 +117,7 @@ async function listen<T>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an one-off event. See {@link listen} for more information.
|
||||
* Listens once to an emitted event to any {@link EventTarget|target}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -143,6 +135,8 @@ async function listen<T>(
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler callback.
|
||||
* @param options Event listening options.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*
|
||||
@@ -164,7 +158,8 @@ async function once<T>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the backend and all Tauri windows.
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emit } from '@tauri-apps/api/event';
|
||||
@@ -172,28 +167,53 @@ async function once<T>(
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
async function emit(
|
||||
event: string,
|
||||
payload?: unknown,
|
||||
options?: Options
|
||||
): Promise<void> {
|
||||
async function emit(event: string, payload?: unknown): Promise<void> {
|
||||
await invoke('plugin:event|emit', {
|
||||
event,
|
||||
target: options?.target,
|
||||
payload
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emit } from '@tauri-apps/api/event';
|
||||
* await emit('frontend-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
async function emitTo(
|
||||
target: EventTarget | string,
|
||||
event: string,
|
||||
payload?: unknown
|
||||
): Promise<void> {
|
||||
const eventTarget: EventTarget =
|
||||
typeof target === 'string' ? { kind: 'AnyLabel', label: target } : target
|
||||
await invoke('plugin:event|emit_to', {
|
||||
target: eventTarget,
|
||||
event,
|
||||
payload
|
||||
})
|
||||
}
|
||||
|
||||
export type {
|
||||
EventSource,
|
||||
Event,
|
||||
EventTarget,
|
||||
EventCallback,
|
||||
UnlistenFn,
|
||||
EventName,
|
||||
Options
|
||||
}
|
||||
|
||||
export { listen, once, emit, TauriEvent }
|
||||
export { listen, once, emit, emitTo, TauriEvent }
|
||||
|
||||
@@ -19,7 +19,16 @@
|
||||
import { PhysicalPosition, PhysicalSize } from './dpi'
|
||||
import type { LogicalPosition, LogicalSize } from './dpi'
|
||||
import type { EventName, EventCallback, UnlistenFn } from './event'
|
||||
import { TauriEvent, emit, listen, once } from './event'
|
||||
import {
|
||||
TauriEvent,
|
||||
// imported for documentation purposes
|
||||
// eslint-disable-next-line
|
||||
type EventTarget,
|
||||
emit,
|
||||
emitTo,
|
||||
listen,
|
||||
once
|
||||
} from './event'
|
||||
import { invoke } from './core'
|
||||
import { Window, getCurrent as getCurrentWindow } from './window'
|
||||
import type { WindowOptions } from './window'
|
||||
@@ -190,7 +199,7 @@ class Webview {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an event emitted by the backend that is tied to the webview.
|
||||
* Listen to an emitted event on this webview.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -220,12 +229,12 @@ class Webview {
|
||||
})
|
||||
}
|
||||
return listen(event, handler, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
target: { kind: 'Webview', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an one-off event emitted by the backend that is tied to the webview.
|
||||
* Listen to an emitted event on this webview only once.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -252,12 +261,13 @@ class Webview {
|
||||
})
|
||||
}
|
||||
return once(event, handler, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
target: { kind: 'Webview', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the backend, tied to the webview.
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
@@ -274,15 +284,44 @@ class Webview {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
source: { kind: 'webview', label: this.label },
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emit(event, payload, {
|
||||
target: { kind: 'webview', label: this.label }
|
||||
})
|
||||
return emit(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/webview';
|
||||
* await getCurrent().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*/
|
||||
async emitTo(
|
||||
target: string,
|
||||
event: string,
|
||||
payload?: unknown
|
||||
): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emitTo(target, event, payload)
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
@@ -604,12 +643,79 @@ class WebviewWindow {
|
||||
// @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor
|
||||
return getAll().map((w) => new WebviewWindow(w.label, { skip: true }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an emitted event on this webivew window.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { WebviewWindow } from '@tauri-apps/api/webview';
|
||||
* const unlisten = await WebviewWindow.getCurrent().listen<string>('state-changed', (event) => {
|
||||
* console.log(`Got error: ${payload}`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async listen<T>(
|
||||
event: EventName,
|
||||
handler: EventCallback<T>
|
||||
): Promise<UnlistenFn> {
|
||||
if (this._handleTauriEvent(event, handler)) {
|
||||
return Promise.resolve(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection
|
||||
const listeners = this.listeners[event]
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return listen(event, handler, {
|
||||
target: { kind: 'WebviewWindow', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an emitted event on this webivew window only once.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { WebviewWindow } from '@tauri-apps/api/webview';
|
||||
* const unlisten = await WebviewWindow.getCurrent().once<null>('initialized', (event) => {
|
||||
* console.log(`Webview initialized!`);
|
||||
* });
|
||||
*
|
||||
* // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
* unlisten();
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param handler Event handler.
|
||||
* @returns A promise resolving to a function to unlisten to the event.
|
||||
* Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted.
|
||||
*/
|
||||
async once<T>(event: string, handler: EventCallback<T>): Promise<UnlistenFn> {
|
||||
if (this._handleTauriEvent(event, handler)) {
|
||||
return Promise.resolve(() => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const listeners = this.listeners[event]
|
||||
listeners.splice(listeners.indexOf(handler), 1)
|
||||
})
|
||||
}
|
||||
return once(event, handler, {
|
||||
target: { kind: 'WebviewWindow', label: this.label }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// order matters, we use window APIs by default
|
||||
applyMixins(WebviewWindow, [Webview, Window])
|
||||
// Order matters, we use window APIs by default
|
||||
applyMixins(WebviewWindow, [Window, Webview])
|
||||
|
||||
/** Extends a base class by other specifed classes */
|
||||
/** Extends a base class by other specifed classes, wihtout overriding existing properties */
|
||||
function applyMixins(
|
||||
baseClass: { prototype: unknown },
|
||||
extendedClasses: unknown
|
||||
@@ -619,6 +725,12 @@ function applyMixins(
|
||||
: [extendedClasses]
|
||||
).forEach((extendedClass: { prototype: unknown }) => {
|
||||
Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => {
|
||||
if (
|
||||
typeof baseClass.prototype === 'object' &&
|
||||
baseClass.prototype &&
|
||||
name in baseClass.prototype
|
||||
)
|
||||
return
|
||||
Object.defineProperty(
|
||||
baseClass.prototype,
|
||||
name,
|
||||
|
||||
@@ -22,14 +22,17 @@ import {
|
||||
PhysicalPosition,
|
||||
PhysicalSize
|
||||
} from './dpi'
|
||||
import type {
|
||||
Event,
|
||||
EventName,
|
||||
EventCallback,
|
||||
UnlistenFn,
|
||||
EventSource
|
||||
import type { Event, EventName, EventCallback, UnlistenFn } from './event'
|
||||
import {
|
||||
TauriEvent,
|
||||
// imported for documentation purposes
|
||||
// eslint-disable-next-line
|
||||
type EventTarget,
|
||||
emit,
|
||||
emitTo,
|
||||
listen,
|
||||
once
|
||||
} from './event'
|
||||
import { TauriEvent, emit, listen, once } from './event'
|
||||
import { invoke } from './core'
|
||||
import { WebviewWindow } from './webview'
|
||||
|
||||
@@ -97,15 +100,12 @@ enum UserAttentionType {
|
||||
class CloseRequestedEvent {
|
||||
/** Event name */
|
||||
event: EventName
|
||||
/** The source of the event. */
|
||||
source: EventSource
|
||||
/** Event identifier used to unlisten */
|
||||
id: number
|
||||
private _preventDefault = false
|
||||
|
||||
constructor(event: Event<null>) {
|
||||
this.event = event.event
|
||||
this.source = event.source
|
||||
this.id = event.id
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ class Window {
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an event emitted by the backend that is tied to the window.
|
||||
* Listen to an emitted event on this window.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -390,12 +390,12 @@ class Window {
|
||||
})
|
||||
}
|
||||
return listen(event, handler, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
target: { kind: 'Window', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to an one-off event emitted by the backend that is tied to the window.
|
||||
* Listen to an emitted event on this window only once.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -422,12 +422,12 @@ class Window {
|
||||
})
|
||||
}
|
||||
return once(event, handler, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
target: { kind: 'Window', label: this.label }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to the backend, tied to the window.
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/window';
|
||||
@@ -444,15 +444,43 @@ class Window {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
source: { kind: 'window', label: this.label },
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emit(event, payload, {
|
||||
target: { kind: 'window', label: this.label }
|
||||
})
|
||||
return emit(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrent } from '@tauri-apps/api/window';
|
||||
* await getCurrent().emit('window-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
* @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object.
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*/
|
||||
async emitTo(
|
||||
target: string | EventTarget,
|
||||
event: string,
|
||||
payload?: unknown
|
||||
): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
return emitTo(target, event, payload)
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
|
||||
Reference in New Issue
Block a user