mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-11 10:43:31 +02:00
Compare commits
3 Commits
dev
...
feat/event
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1259ca56b2 | ||
|
|
2ece5d7adf | ||
|
|
95f6c48dc0 |
5
.changes/emit-raw-api.md
Normal file
5
.changes/emit-raw-api.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@tauri-apps/api": minor:feat
|
||||
---
|
||||
|
||||
Optimize raw Uint8Array/ArrayBuffer/number[] payloads on the event system via the emit2 and emitTo2 functions.
|
||||
5
.changes/event-emit-raw.md
Normal file
5
.changes/event-emit-raw.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
---
|
||||
|
||||
Allow sending raw binary payloads using the event system through `Emitter::emit_raw` and `Emitter::emit_raw_to`.
|
||||
5
.changes/listen-raw-binary-payload.md
Normal file
5
.changes/listen-raw-binary-payload.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": minor:feat
|
||||
---
|
||||
|
||||
Allow reading raw binary payloads using the event system through [`Event::payload_raw`].
|
||||
File diff suppressed because one or more lines are too long
@@ -8,7 +8,7 @@ use crate::{
|
||||
channel::ChannelDataIpcQueue, CallbackFn, CommandArg, CommandItem, Invoke, InvokeError,
|
||||
InvokeHandler, InvokeResponseBody,
|
||||
},
|
||||
manager::{webview::UriSchemeProtocol, AppManager, Asset},
|
||||
manager::{webview::UriSchemeProtocol, AppManager, Asset, EventPayloadStore},
|
||||
plugin::{Plugin, PluginStore},
|
||||
resources::ResourceTable,
|
||||
runtime::{
|
||||
@@ -2260,6 +2260,7 @@ tauri::Builder::default()
|
||||
});
|
||||
|
||||
app.manage(ChannelDataIpcQueue::default());
|
||||
app.manage(EventPayloadStore::default());
|
||||
app.handle.plugin(crate::ipc::channel::plugin())?;
|
||||
|
||||
let handle = app.handle();
|
||||
|
||||
@@ -201,7 +201,11 @@ impl Listeners {
|
||||
let handlers = handlers.filter(|(_, h)| match_any_or_filter(&h.target, &filter));
|
||||
for (&id, Handler { callback, .. }) in handlers {
|
||||
maybe_pending = true;
|
||||
(callback)(Event::new(id, emit_args.payload.clone()))
|
||||
(callback)(Event::new(
|
||||
id,
|
||||
emit_args.payload.clone(),
|
||||
emit_args.raw.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,6 +270,37 @@ impl Listeners {
|
||||
})
|
||||
}
|
||||
|
||||
/// Filters the webviews that are matched by the filter has a JS listener for the given event
|
||||
pub(crate) fn webiews_matching_event_filter<'a, R, I, F>(
|
||||
&self,
|
||||
webviews: I,
|
||||
emit_args: &EmitArgs,
|
||||
filter: Option<F>,
|
||||
) -> Vec<&'a Webview<R>>
|
||||
where
|
||||
R: Runtime,
|
||||
I: Iterator<Item = &'a Webview<R>>,
|
||||
F: Fn(&EventTarget) -> bool,
|
||||
{
|
||||
let event = &emit_args.event;
|
||||
let js_listeners = self.inner.js_event_listeners.lock().unwrap();
|
||||
webviews
|
||||
.filter(|webview| {
|
||||
if let Some(handlers) = js_listeners.get(webview.label()).and_then(|s| s.get(event)) {
|
||||
let ids = handlers
|
||||
.iter()
|
||||
.filter(|handler| match_any_or_filter(&handler.target, &filter))
|
||||
.map(|handler| handler.id)
|
||||
.collect::<Vec<_>>();
|
||||
if !ids.is_empty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn emit_js_filter<'a, R, I, F>(
|
||||
&self,
|
||||
mut webviews: I,
|
||||
|
||||
@@ -13,7 +13,11 @@ mod event_name;
|
||||
|
||||
pub(crate) use event_name::EventName;
|
||||
|
||||
use crate::ipc::CallbackFn;
|
||||
use crate::{
|
||||
event::plugin::{FETCH_EVENT_PAYLOAD_COMMAND, PAYLOAD_ID_HEADER_NAME},
|
||||
ipc::CallbackFn,
|
||||
manager::BINARY_EVENT_PAYLOAD_PREFIX,
|
||||
};
|
||||
|
||||
/// Unique id of an event.
|
||||
pub type EventId = u32;
|
||||
@@ -119,6 +123,8 @@ pub struct EmitArgs {
|
||||
event: EventName,
|
||||
/// Serialized payload.
|
||||
payload: String,
|
||||
/// Raw payload bytes.
|
||||
raw: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl EmitArgs {
|
||||
@@ -128,17 +134,33 @@ impl EmitArgs {
|
||||
Ok(EmitArgs {
|
||||
event: event.into_owned(),
|
||||
payload: serde_json::to_string(payload)?,
|
||||
raw: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_str(event: EventName<&str>, payload: String) -> crate::Result<Self> {
|
||||
pub fn new_raw<S: Serialize>(
|
||||
event: EventName<&str>,
|
||||
payload: &S,
|
||||
raw_data: Vec<u8>,
|
||||
) -> crate::Result<Self> {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("window::emit::json").entered();
|
||||
let _span = tracing::debug_span!("window::emit::serialize").entered();
|
||||
Ok(EmitArgs {
|
||||
event: event.into_owned(),
|
||||
payload,
|
||||
payload: serde_json::to_string(payload)?,
|
||||
raw: Some(raw_data),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_str(event: EventName<&str>, payload: String) -> Self {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("window::emit::json").entered();
|
||||
EmitArgs {
|
||||
event: event.into_owned(),
|
||||
payload,
|
||||
raw: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that was emitted.
|
||||
@@ -146,11 +168,12 @@ impl EmitArgs {
|
||||
pub struct Event {
|
||||
id: EventId,
|
||||
data: String,
|
||||
raw: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn new(id: EventId, data: String) -> Self {
|
||||
Self { id, data }
|
||||
fn new(id: EventId, data: String, raw: Option<Vec<u8>>) -> Self {
|
||||
Self { id, data, raw }
|
||||
}
|
||||
|
||||
/// The [`EventId`] of the handler that was triggered.
|
||||
@@ -159,9 +182,28 @@ impl Event {
|
||||
}
|
||||
|
||||
/// The event payload.
|
||||
///
|
||||
/// Note that you must use [`Self::payload_raw`] for events emitted with a binary payload.
|
||||
pub fn payload(&self) -> &str {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Consumes the event and return its payload.
|
||||
///
|
||||
/// Note that you must use [`Self::into_payload_raw`] for events emitted with a binary payload.
|
||||
pub fn into_payload(self) -> String {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// The event payload bytes.
|
||||
pub fn payload_raw(&self) -> &[u8] {
|
||||
self.raw.as_deref().unwrap_or(self.data.as_bytes())
|
||||
}
|
||||
|
||||
/// Consumes the event and return its payload bytes.
|
||||
pub fn into_payload_raw(self) -> Vec<u8> {
|
||||
self.raw.unwrap_or_else(|| self.data.into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn listen_js_script(
|
||||
@@ -224,8 +266,15 @@ pub(crate) fn unlisten_js_script(
|
||||
pub(crate) fn event_initialization_script(function_name: &str, listeners: &str) -> String {
|
||||
format!(
|
||||
"Object.defineProperty(window, '{function_name}', {{
|
||||
value: function (eventData, ids) {{
|
||||
value: async function (eventData, ids) {{
|
||||
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
|
||||
|
||||
if (typeof eventData.payload === 'string' && eventData.payload.startsWith('{BINARY_EVENT_PAYLOAD_PREFIX}')) {{
|
||||
const payloadId = eventData.payload.slice('{BINARY_EVENT_PAYLOAD_PREFIX}'.length)
|
||||
const data = await window.__TAURI_INTERNALS__.invoke('{FETCH_EVENT_PAYLOAD_COMMAND}', null, {{ headers: {{ '{PAYLOAD_ID_HEADER_NAME}': payloadId }} }})
|
||||
eventData.payload = data
|
||||
}}
|
||||
|
||||
for (const id of ids) {{
|
||||
const listener = listeners[id]
|
||||
if (listener) {{
|
||||
@@ -235,7 +284,7 @@ pub(crate) fn event_initialization_script(function_name: &str, listeners: &str)
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
"
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
use serde::Deserialize;
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
use serde_json::Value as JsonValue;
|
||||
use serialize_to_javascript::{default_template, DefaultTemplate, Template};
|
||||
|
||||
use crate::ipc::{InvokeBody, Request, Response};
|
||||
use crate::manager::EventPayloadStore;
|
||||
use crate::plugin::{Builder, TauriPlugin};
|
||||
use crate::{command, ipc::CallbackFn, EventId, Result, Runtime};
|
||||
use crate::{AppHandle, Emitter, Manager, Webview};
|
||||
use crate::{AppHandle, Emitter, Manager, State, Webview};
|
||||
|
||||
use super::EventName;
|
||||
use super::EventTarget;
|
||||
|
||||
const EVENT_NAME_HEADER_NAME: &str = "Tauri-Event-Name";
|
||||
const EVENT_TARGET_HEADER_NAME: &str = "Tauri-Event-Target";
|
||||
pub const FETCH_EVENT_PAYLOAD_COMMAND: &str = "plugin:event|fetch_payload";
|
||||
pub const PAYLOAD_ID_HEADER_NAME: &str = "Tauri-Payload-Id";
|
||||
|
||||
#[command(root = "crate")]
|
||||
async fn listen<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
@@ -31,22 +38,106 @@ async fn unlisten<R: Runtime>(
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
async fn emit<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
async fn emit<R: Runtime>(app: AppHandle<R>, request: Request<'_>) -> Result<()> {
|
||||
if let Some(event_header) = request
|
||||
.headers()
|
||||
.get(EVENT_NAME_HEADER_NAME)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
let event = EventName::new(event_header)?;
|
||||
match request.body() {
|
||||
InvokeBody::Json(payload) => app.emit(event.as_str(), payload),
|
||||
InvokeBody::Raw(payload) => app.emit_raw(event.as_str(), payload.clone()),
|
||||
}
|
||||
} else if let InvokeBody::Json(payload) = request.body() {
|
||||
let args: EmitArgs = serde_json::from_value(payload.clone())?;
|
||||
app.emit(args.event.as_str(), args.payload)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("unexpected emit request format").into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EmitArgs {
|
||||
event: EventName,
|
||||
payload: Option<JsonValue>,
|
||||
) -> Result<()> {
|
||||
app.emit(event.as_str(), payload)
|
||||
payload: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
async fn emit_to<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
target: EventTarget,
|
||||
async fn emit_to<R: Runtime>(app: AppHandle<R>, request: Request<'_>) -> Result<()> {
|
||||
if let Some(event_header) = request
|
||||
.headers()
|
||||
.get(EVENT_NAME_HEADER_NAME)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
let event = EventName::new(event_header)?;
|
||||
let target = if let Some(target_header) = request
|
||||
.headers()
|
||||
.get(EVENT_TARGET_HEADER_NAME)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
{
|
||||
serde_json::from_str::<EventTarget>(target_header)?
|
||||
} else {
|
||||
return Err(anyhow::anyhow!("missing event target header").into());
|
||||
};
|
||||
|
||||
match request.body() {
|
||||
InvokeBody::Json(payload) => app.emit_to(target, event.as_str(), payload),
|
||||
InvokeBody::Raw(payload) => app.emit_raw_to(target, event.as_str(), payload.clone()),
|
||||
}
|
||||
} else if let InvokeBody::Json(payload) = request.body() {
|
||||
let args: EmitToArgs = serde_json::from_value(payload.clone())?;
|
||||
app.emit_to(args.target, args.event.as_str(), args.payload)
|
||||
} else {
|
||||
Err(anyhow::anyhow!("unexpected emit request format").into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EmitToArgs {
|
||||
event: EventName,
|
||||
payload: Option<JsonValue>,
|
||||
) -> Result<()> {
|
||||
app.emit_to(target, event.as_str(), payload)
|
||||
payload: Option<serde_json::Value>,
|
||||
target: EventTarget,
|
||||
}
|
||||
|
||||
#[command(root = "crate")]
|
||||
fn fetch_payload(
|
||||
request: Request<'_>,
|
||||
store: State<'_, EventPayloadStore>,
|
||||
) -> std::result::Result<Response, &'static str> {
|
||||
if let Some(id) = request
|
||||
.headers()
|
||||
.get(PAYLOAD_ID_HEADER_NAME)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|id| id.parse().ok())
|
||||
{
|
||||
let r = if let Some(data) = store.0.lock().unwrap().get(&id) {
|
||||
// fetch_add returns the previous value so we must +1
|
||||
let read_count = data
|
||||
.read_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||
+ 1;
|
||||
|
||||
Ok((
|
||||
Response::new(data.payload.clone()),
|
||||
read_count == data.target_count,
|
||||
))
|
||||
} else {
|
||||
Err("data not found")
|
||||
};
|
||||
|
||||
match r {
|
||||
Ok((response, done)) => {
|
||||
if done {
|
||||
store.0.lock().unwrap().remove(&id);
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else {
|
||||
Err("missing payload id header")
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the event plugin.
|
||||
@@ -70,7 +161,7 @@ pub(crate) fn init<R: Runtime, M: Manager<R>>(manager: &M) -> TauriPlugin<R> {
|
||||
Builder::new("event")
|
||||
.invoke_handler(crate::generate_handler![
|
||||
#![plugin(event)]
|
||||
listen, unlisten, emit, emit_to
|
||||
listen, unlisten, emit, emit_to, fetch_payload
|
||||
])
|
||||
.js_init_script(
|
||||
init_script
|
||||
|
||||
@@ -990,6 +990,16 @@ pub trait Emitter<R: Runtime>: sealed::ManagerBase<R> {
|
||||
self.manager().emit(event, payload)
|
||||
}
|
||||
|
||||
/// Similar to [`Emitter::emit`] but the payload is raw binary data.
|
||||
///
|
||||
/// This function is optimized to emit event with large binary payloads,
|
||||
/// avoiding serialization costs.
|
||||
fn emit_raw(&self, event: &str, payload: Vec<u8>) -> Result<()> {
|
||||
let event = EventName::new(event)?;
|
||||
let payload = EmitPayload::<()>::Binary(payload);
|
||||
self.manager().emit(event, payload)
|
||||
}
|
||||
|
||||
/// Emits an event to all [targets](EventTarget) matching the given target.
|
||||
///
|
||||
/// # Examples
|
||||
@@ -1032,6 +1042,16 @@ pub trait Emitter<R: Runtime>: sealed::ManagerBase<R> {
|
||||
self.manager().emit_to(target.into(), event, payload)
|
||||
}
|
||||
|
||||
/// Similar to [`Emitter::emit_to`] but the payload is json serialized.
|
||||
fn emit_raw_to<I>(&self, target: I, event: &str, payload: Vec<u8>) -> Result<()>
|
||||
where
|
||||
I: Into<EventTarget>,
|
||||
{
|
||||
let event = EventName::new(event)?;
|
||||
let payload = EmitPayload::<()>::Binary(payload);
|
||||
self.manager().emit_to(target.into(), event, payload)
|
||||
}
|
||||
|
||||
/// Emits an event to all [targets](EventTarget) based on the given filter.
|
||||
///
|
||||
/// # Examples
|
||||
|
||||
@@ -6,7 +6,10 @@ use std::{
|
||||
borrow::Cow,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
sync::{atomic::AtomicBool, Arc, Mutex, MutexGuard},
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
|
||||
Arc, Mutex, MutexGuard,
|
||||
},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
@@ -38,6 +41,9 @@ mod tray;
|
||||
pub mod webview;
|
||||
pub mod window;
|
||||
|
||||
static EVENT_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
pub const BINARY_EVENT_PAYLOAD_PREFIX: &str = "__tauri_event_payload_id__:";
|
||||
|
||||
#[derive(Default)]
|
||||
/// Spaced and quoted Content-Security-Policy hash values.
|
||||
struct CspHashStrings {
|
||||
@@ -249,6 +255,7 @@ impl<R: Runtime> fmt::Debug for AppManager<R> {
|
||||
pub(crate) enum EmitPayload<'a, S: Serialize> {
|
||||
Serialize(&'a S),
|
||||
Str(String),
|
||||
Binary(Vec<u8>),
|
||||
}
|
||||
|
||||
impl<R: Runtime> AppManager<R> {
|
||||
@@ -546,16 +553,48 @@ impl<R: Runtime> AppManager<R> {
|
||||
event: EventName<&str>,
|
||||
payload: EmitPayload<'_, S>,
|
||||
) -> crate::Result<()> {
|
||||
let webviews = self
|
||||
.webview
|
||||
.webviews_lock()
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("emit::run").entered();
|
||||
let emit_args = match payload {
|
||||
EmitPayload::Serialize(payload) => EmitArgs::new(event, payload)?,
|
||||
EmitPayload::Str(payload) => EmitArgs::new_str(event, payload)?,
|
||||
EmitPayload::Str(payload) => EmitArgs::new_str(event, payload),
|
||||
EmitPayload::Binary(payload) => {
|
||||
let id = EVENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
if !webviews.is_empty() {
|
||||
self
|
||||
.state()
|
||||
.get::<EventPayloadStore>()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(
|
||||
id,
|
||||
PendingEventPayload {
|
||||
target_count: webviews.len(),
|
||||
read_count: AtomicUsize::new(0),
|
||||
payload: payload.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
EmitArgs::new_raw(
|
||||
event,
|
||||
&format!("{BINARY_EVENT_PAYLOAD_PREFIX}{id}"),
|
||||
payload,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
let listeners = self.listeners();
|
||||
|
||||
listeners.emit_js(self.webview.webviews_lock().values(), &emit_args)?;
|
||||
listeners.emit_js(webviews.iter(), &emit_args)?;
|
||||
listeners.emit(emit_args)?;
|
||||
|
||||
Ok(())
|
||||
@@ -577,18 +616,50 @@ impl<R: Runtime> AppManager<R> {
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _span = tracing::debug_span!("emit::run").entered();
|
||||
let emit_args = match payload {
|
||||
EmitPayload::Serialize(payload) => EmitArgs::new(event, payload)?,
|
||||
EmitPayload::Str(payload) => EmitArgs::new_str(event, payload)?,
|
||||
};
|
||||
|
||||
let listeners = self.listeners();
|
||||
let webviews = self
|
||||
.webview
|
||||
.webviews_lock()
|
||||
.values()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
listeners.emit_js_filter(
|
||||
self.webview.webviews_lock().values(),
|
||||
&emit_args,
|
||||
Some(&filter),
|
||||
)?;
|
||||
let emit_args = match payload {
|
||||
EmitPayload::Serialize(payload) => EmitArgs::new(event, payload)?,
|
||||
EmitPayload::Str(payload) => EmitArgs::new_str(event, payload),
|
||||
EmitPayload::Binary(payload) => {
|
||||
let id = EVENT_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let emit_args = EmitArgs::new_raw(
|
||||
event,
|
||||
&format!("{BINARY_EVENT_PAYLOAD_PREFIX}{id}"),
|
||||
payload.clone(),
|
||||
)?;
|
||||
|
||||
if !webviews.is_empty() {
|
||||
self
|
||||
.state()
|
||||
.get::<EventPayloadStore>()
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(
|
||||
id,
|
||||
PendingEventPayload {
|
||||
target_count: listeners
|
||||
.webiews_matching_event_filter(webviews.iter(), &emit_args, Some(&filter))
|
||||
.len(),
|
||||
read_count: AtomicUsize::new(0),
|
||||
payload,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
emit_args
|
||||
}
|
||||
};
|
||||
|
||||
listeners.emit_js_filter(webviews.iter(), &emit_args, Some(&filter))?;
|
||||
|
||||
listeners.emit_filter(emit_args, Some(filter))?;
|
||||
|
||||
@@ -714,6 +785,16 @@ impl<R: Runtime> AppManager<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a binary event payload id to a pending payload that must be send to the JavaScript side via the IPC.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct EventPayloadStore(pub Arc<Mutex<HashMap<u32, PendingEventPayload>>>);
|
||||
|
||||
pub struct PendingEventPayload {
|
||||
pub target_count: usize,
|
||||
pub read_count: AtomicUsize,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::replace_with_callback;
|
||||
|
||||
@@ -1802,6 +1802,7 @@ tauri::Builder::default()
|
||||
if (plugin_command.is_some() || has_app_acl_manifest)
|
||||
// TODO: Remove this special check in v3
|
||||
&& request.cmd != crate::ipc::channel::FETCH_CHANNEL_DATA_COMMAND
|
||||
&& request.cmd != crate::event::plugin::FETCH_EVENT_PAYLOAD_COMMAND
|
||||
&& invoke.acl.is_none()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -164,6 +164,15 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.emit("rust-event", Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
|
||||
let webview_ = webview.clone();
|
||||
webview.listen("raw-js-event", move |event| {
|
||||
println!("got raw-js-event with message '{:?}'", event.payload_raw());
|
||||
|
||||
webview_
|
||||
.emit_raw("raw-rust-event", event.into_payload_raw())
|
||||
.expect("failed to emit");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,16 +4,25 @@
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
|
||||
let { onMessage } = $props()
|
||||
let unlisten
|
||||
const unlisten = []
|
||||
|
||||
const webviewWindow = getCurrentWebviewWindow()
|
||||
|
||||
onMount(async () => {
|
||||
unlisten = await webviewWindow.listen('rust-event', onMessage)
|
||||
const unlistenFn1 = await webviewWindow.listen('rust-event', onMessage)
|
||||
unlisten.push(unlistenFn1)
|
||||
const unlistenFn2 = await webviewWindow.listen('raw-rust-event', (event) => {
|
||||
onMessage({
|
||||
event: event.event,
|
||||
id: event.id,
|
||||
payload: Array.from(new Uint8Array(event.payload)),
|
||||
})
|
||||
})
|
||||
unlisten.push(unlistenFn2)
|
||||
})
|
||||
onDestroy(() => {
|
||||
if (unlisten) {
|
||||
unlisten()
|
||||
for (const unlistenFn of unlisten) {
|
||||
unlistenFn()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -55,6 +64,10 @@
|
||||
function emitEvent() {
|
||||
webviewWindow.emit('js-event', 'this is the payload string')
|
||||
}
|
||||
|
||||
function emitRawEvent() {
|
||||
webviewWindow.emit2('raw-js-event', new Uint8Array([1, 2, 3]))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -65,6 +78,9 @@
|
||||
<button class="btn" id="event" onclick={emitEvent}>
|
||||
Send event to Rust
|
||||
</button>
|
||||
<button class="btn" id="event" onclick={emitRawEvent}>
|
||||
Send raw event to Rust
|
||||
</button>
|
||||
<button class="btn" id="request" onclick={echo}> Echo </button>
|
||||
<button class="btn" id="request" onclick={spam}> Spam </button>
|
||||
</div>
|
||||
|
||||
@@ -172,6 +172,8 @@ async function once<T>(
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* Prefer {@link emit2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emit } from '@tauri-apps/api/event';
|
||||
@@ -184,15 +186,36 @@ async function once<T>(
|
||||
* @since 1.0.0
|
||||
*/
|
||||
async function emit<T>(event: string, payload?: T): Promise<void> {
|
||||
await invoke('plugin:event|emit', {
|
||||
event,
|
||||
payload
|
||||
await invoke('plugin:event|emit', { event, payload })
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* This function is similar to {@link emit} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emit2 } from '@tauri-apps/api/event';
|
||||
* await emit2('frontend-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*
|
||||
* @since 2.10.0
|
||||
*/
|
||||
async function emit2<T>(event: string, payload?: T): Promise<void> {
|
||||
await invoke('plugin:event|emit', payload as any, {
|
||||
headers: { 'Tauri-Event-Name': event }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* Prefer {@link emitTo2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emitTo } from '@tauri-apps/api/event';
|
||||
@@ -212,10 +235,38 @@ async function emitTo<T>(
|
||||
): Promise<void> {
|
||||
const eventTarget: EventTarget =
|
||||
typeof target === 'string' ? { kind: 'AnyLabel', label: target } : target
|
||||
await invoke('plugin:event|emit_to', {
|
||||
target: eventTarget,
|
||||
event,
|
||||
payload
|
||||
await invoke('plugin:event|emit_to', { event, payload, target: eventTarget })
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* This function is similar to {@link emitTo} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { emitTo2 } from '@tauri-apps/api/event';
|
||||
* await emitTo2('main', '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 2.10.0
|
||||
*/
|
||||
async function emitTo2<T>(
|
||||
target: EventTarget | string,
|
||||
event: string,
|
||||
payload?: T
|
||||
): Promise<void> {
|
||||
const eventTarget: EventTarget =
|
||||
typeof target === 'string' ? { kind: 'AnyLabel', label: target } : target
|
||||
await invoke('plugin:event|emit_to', payload as any, {
|
||||
headers: {
|
||||
'Tauri-Event-Name': event,
|
||||
'Tauri-Event-Target': JSON.stringify(eventTarget)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -228,4 +279,4 @@ export type {
|
||||
Options
|
||||
}
|
||||
|
||||
export { listen, once, emit, emitTo, TauriEvent }
|
||||
export { listen, once, emit, emitTo, emit2, emitTo2, TauriEvent }
|
||||
|
||||
@@ -25,7 +25,9 @@ import {
|
||||
// imported for documentation purposes
|
||||
type EventTarget,
|
||||
emit,
|
||||
emit2,
|
||||
emitTo,
|
||||
emitTo2,
|
||||
listen,
|
||||
once
|
||||
} from './event'
|
||||
@@ -313,6 +315,8 @@ class Webview {
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* Prefer {@link Webview.emit2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
@@ -337,9 +341,40 @@ class Webview {
|
||||
return emit<T>(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* This function is similar to {@link Webview.emit} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
* await getCurrentWebview().emit('webview-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*/
|
||||
async emit2<T>(event: string, payload?: T): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
return emit2<T>(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* Prefer {@link Webview.emitTo2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
@@ -369,6 +404,40 @@ class Webview {
|
||||
return emitTo<T>(target, event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* This function is similar to {@link Webview.emitTo} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
* await getCurrentWebview().emitTo('main', '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 emitTo2<T>(
|
||||
target: string | EventTarget,
|
||||
event: string,
|
||||
payload?: T
|
||||
): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
return emitTo2<T>(target, event, payload)
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
_handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
// imported for documentation purposes
|
||||
type EventTarget,
|
||||
emit,
|
||||
emit2,
|
||||
emitTo,
|
||||
emitTo2,
|
||||
listen,
|
||||
once
|
||||
} from './event'
|
||||
@@ -437,6 +439,9 @@ class Window {
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* Prefer {@link Window.emit2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
@@ -461,9 +466,40 @@ class Window {
|
||||
return emit<T>(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets}.
|
||||
*
|
||||
* This function is similar to {@link Window.emit} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
* await getCurrentWindow().emit('window-loaded', { loggedIn: true, token: 'authToken' });
|
||||
* ```
|
||||
*
|
||||
* @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
|
||||
* @param payload Event payload.
|
||||
*/
|
||||
async emit2<T>(event: string, payload?: T): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
return emit2<T>(event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* Prefer {@link Window.emitTo2} to send binary data.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
@@ -492,6 +528,39 @@ class Window {
|
||||
return emitTo<T>(target, event, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event to all {@link EventTarget|targets} matching the given target.
|
||||
*
|
||||
* This function is similar to {@link Window.emitTo} but it is optimized for binary payloads.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
* await getCurrentWindow().emit('main', '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 emitTo2<T>(
|
||||
target: string | EventTarget,
|
||||
event: string,
|
||||
payload?: T
|
||||
): Promise<void> {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
for (const handler of this.listeners[event] || []) {
|
||||
handler({
|
||||
event,
|
||||
id: -1,
|
||||
payload
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
return emitTo2<T>(target, event, payload)
|
||||
}
|
||||
|
||||
/** @ignore */
|
||||
_handleTauriEvent<T>(event: string, handler: EventCallback<T>): boolean {
|
||||
if (localTauriEvents.includes(event)) {
|
||||
|
||||
Reference in New Issue
Block a user