mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-01 11:13:40 +03:00
feat(core): expose custom protocol handler APIs (#1553)
This commit is contained in:
parent
be65f04db7
commit
938fb624f5
5
.changes/multi-custom-protocols.md
Normal file
5
.changes/multi-custom-protocols.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Adds APIs to determine global and window-specific custom protocol handlers.
|
@ -7,14 +7,19 @@ use crate::{
|
||||
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
|
||||
plugin::{Plugin, PluginStore},
|
||||
runtime::{
|
||||
flavors::wry::Wry, manager::WindowManager, tag::Tag, webview::Attributes,
|
||||
window::PendingWindow, Dispatch, Runtime,
|
||||
flavors::wry::Wry,
|
||||
manager::{Args, WindowManager},
|
||||
tag::Tag,
|
||||
webview::{Attributes, CustomProtocol},
|
||||
window::PendingWindow,
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
Context, Manager, Params, Window,
|
||||
};
|
||||
|
||||
use crate::runtime::manager::Args;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
use crate::updater;
|
||||
|
||||
@ -118,6 +123,9 @@ where
|
||||
|
||||
/// All passed plugins
|
||||
plugins: PluginStore<Args<E, L, A, R>>,
|
||||
|
||||
/// The custom protocols available to all windows.
|
||||
custom_protocols: HashMap<String, Arc<CustomProtocol>>,
|
||||
}
|
||||
|
||||
impl<E, L, A, R> Builder<E, L, A, R>
|
||||
@ -135,6 +143,7 @@ where
|
||||
on_page_load: Box::new(|_, _| ()),
|
||||
pending_windows: Default::default(),
|
||||
plugins: PluginStore::default(),
|
||||
custom_protocols: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,6 +192,24 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a custom protocol available to all windows.
|
||||
pub fn custom_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
|
||||
>(
|
||||
mut self,
|
||||
name: N,
|
||||
handler: H,
|
||||
) -> Self {
|
||||
self.custom_protocols.insert(
|
||||
name.into(),
|
||||
Arc::new(CustomProtocol {
|
||||
handler: Box::new(handler),
|
||||
}),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
/// Runs the configured Tauri application.
|
||||
pub fn run(mut self, context: Context<A>) -> crate::Result<()> {
|
||||
let manager = WindowManager::with_handlers(
|
||||
@ -190,6 +217,7 @@ where
|
||||
self.plugins,
|
||||
self.invoke_handler,
|
||||
self.on_page_load,
|
||||
self.custom_protocols,
|
||||
);
|
||||
|
||||
// set up all the windows defined in the config
|
||||
|
@ -8,15 +8,19 @@ use crate::{
|
||||
api::config::WindowConfig,
|
||||
runtime::{
|
||||
webview::{
|
||||
Attributes, AttributesBase, CustomProtocol, FileDropEvent, FileDropHandler, RpcRequest,
|
||||
WebviewRpcHandler,
|
||||
Attributes, AttributesBase, FileDropEvent, FileDropHandler, RpcRequest, WebviewRpcHandler,
|
||||
},
|
||||
window::{DetachedWindow, PendingWindow},
|
||||
Dispatch, Params, Runtime,
|
||||
},
|
||||
Icon,
|
||||
};
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::TryFrom,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::api::path::{resolve_path, BaseDirectory};
|
||||
@ -41,8 +45,15 @@ impl TryFrom<Icon> for WryIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributesBase for wry::Attributes {}
|
||||
impl Attributes for wry::Attributes {
|
||||
/// Wry attributes.
|
||||
#[derive(Default, Clone)]
|
||||
pub struct WryAttributes {
|
||||
attributes: wry::Attributes,
|
||||
custom_protocols: Arc<Mutex<HashMap<String, wry::CustomProtocol>>>,
|
||||
}
|
||||
|
||||
impl AttributesBase for WryAttributes {}
|
||||
impl Attributes for WryAttributes {
|
||||
type Icon = WryIcon;
|
||||
|
||||
fn new() -> Self {
|
||||
@ -50,7 +61,7 @@ impl Attributes for wry::Attributes {
|
||||
}
|
||||
|
||||
fn with_config(config: WindowConfig) -> Self {
|
||||
let mut webview = wry::Attributes::default()
|
||||
let mut webview = WryAttributes::default()
|
||||
.title(config.title.to_string())
|
||||
.width(config.width)
|
||||
.height(config.height)
|
||||
@ -108,106 +119,132 @@ impl Attributes for wry::Attributes {
|
||||
}
|
||||
|
||||
fn initialization_script(mut self, init: &str) -> Self {
|
||||
self.initialization_scripts.push(init.to_string());
|
||||
self
|
||||
.attributes
|
||||
.initialization_scripts
|
||||
.push(init.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
fn x(mut self, x: f64) -> Self {
|
||||
self.x = Some(x);
|
||||
self.attributes.x = Some(x);
|
||||
self
|
||||
}
|
||||
|
||||
fn y(mut self, y: f64) -> Self {
|
||||
self.y = Some(y);
|
||||
self.attributes.y = Some(y);
|
||||
self
|
||||
}
|
||||
|
||||
fn width(mut self, width: f64) -> Self {
|
||||
self.width = width;
|
||||
self.attributes.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
fn height(mut self, height: f64) -> Self {
|
||||
self.height = height;
|
||||
self.attributes.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
fn min_width(mut self, min_width: f64) -> Self {
|
||||
self.min_width = Some(min_width);
|
||||
self.attributes.min_width = Some(min_width);
|
||||
self
|
||||
}
|
||||
|
||||
fn min_height(mut self, min_height: f64) -> Self {
|
||||
self.min_height = Some(min_height);
|
||||
self.attributes.min_height = Some(min_height);
|
||||
self
|
||||
}
|
||||
|
||||
fn max_width(mut self, max_width: f64) -> Self {
|
||||
self.max_width = Some(max_width);
|
||||
self.attributes.max_width = Some(max_width);
|
||||
self
|
||||
}
|
||||
|
||||
fn max_height(mut self, max_height: f64) -> Self {
|
||||
self.max_height = Some(max_height);
|
||||
self.attributes.max_height = Some(max_height);
|
||||
self
|
||||
}
|
||||
|
||||
fn resizable(mut self, resizable: bool) -> Self {
|
||||
self.resizable = resizable;
|
||||
self.attributes.resizable = resizable;
|
||||
self
|
||||
}
|
||||
|
||||
fn title<S: Into<String>>(mut self, title: S) -> Self {
|
||||
self.title = title.into();
|
||||
self.attributes.title = title.into();
|
||||
self
|
||||
}
|
||||
|
||||
fn fullscreen(mut self, fullscreen: bool) -> Self {
|
||||
self.fullscreen = fullscreen;
|
||||
self.attributes.fullscreen = fullscreen;
|
||||
self
|
||||
}
|
||||
|
||||
fn maximized(mut self, maximized: bool) -> Self {
|
||||
self.maximized = maximized;
|
||||
self.attributes.maximized = maximized;
|
||||
self
|
||||
}
|
||||
|
||||
fn visible(mut self, visible: bool) -> Self {
|
||||
self.visible = visible;
|
||||
self.attributes.visible = visible;
|
||||
self
|
||||
}
|
||||
|
||||
fn transparent(mut self, transparent: bool) -> Self {
|
||||
self.transparent = transparent;
|
||||
self.attributes.transparent = transparent;
|
||||
self
|
||||
}
|
||||
|
||||
fn decorations(mut self, decorations: bool) -> Self {
|
||||
self.decorations = decorations;
|
||||
self.attributes.decorations = decorations;
|
||||
self
|
||||
}
|
||||
|
||||
fn always_on_top(mut self, always_on_top: bool) -> Self {
|
||||
self.always_on_top = always_on_top;
|
||||
self.attributes.always_on_top = always_on_top;
|
||||
self
|
||||
}
|
||||
|
||||
fn icon(mut self, icon: Self::Icon) -> Self {
|
||||
self.icon = Some(icon.0);
|
||||
self.attributes.icon = Some(icon.0);
|
||||
self
|
||||
}
|
||||
|
||||
fn has_icon(&self) -> bool {
|
||||
self.icon.is_some()
|
||||
self.attributes.icon.is_some()
|
||||
}
|
||||
|
||||
fn user_data_path(mut self, user_data_path: Option<PathBuf>) -> Self {
|
||||
self.user_data_path = user_data_path;
|
||||
self.attributes.user_data_path = user_data_path;
|
||||
self
|
||||
}
|
||||
|
||||
fn url(mut self, url: String) -> Self {
|
||||
self.url.replace(url);
|
||||
self.attributes.url.replace(url);
|
||||
self
|
||||
}
|
||||
|
||||
fn has_custom_protocol(&self, name: &str) -> bool {
|
||||
self.custom_protocols.lock().unwrap().contains_key(name)
|
||||
}
|
||||
|
||||
fn custom_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
|
||||
>(
|
||||
self,
|
||||
name: N,
|
||||
handler: H,
|
||||
) -> Self {
|
||||
let name = name.into();
|
||||
self.custom_protocols.lock().unwrap().insert(
|
||||
name.clone(),
|
||||
wry::CustomProtocol {
|
||||
name,
|
||||
handler: Box::new(move |data| (handler)(data).map_err(|_| wry::Error::InitScriptError)),
|
||||
},
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@ -245,7 +282,7 @@ pub struct WryDispatcher {
|
||||
impl Dispatch for WryDispatcher {
|
||||
type Runtime = Wry;
|
||||
type Icon = WryIcon;
|
||||
type Attributes = wry::Attributes;
|
||||
type Attributes = WryAttributes;
|
||||
|
||||
fn create_window<M: Params<Runtime = Self::Runtime>>(
|
||||
&mut self,
|
||||
@ -254,7 +291,6 @@ impl Dispatch for WryDispatcher {
|
||||
let PendingWindow {
|
||||
attributes,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
label,
|
||||
..
|
||||
@ -268,14 +304,20 @@ impl Dispatch for WryDispatcher {
|
||||
let file_drop_handler = file_drop_handler
|
||||
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let custom_protocols = {
|
||||
let mut lock = attributes
|
||||
.custom_protocols
|
||||
.lock()
|
||||
.expect("poisoned custom protocols");
|
||||
std::mem::take(&mut *lock)
|
||||
};
|
||||
|
||||
let window = self
|
||||
.application
|
||||
.add_window_with_configs(
|
||||
attributes,
|
||||
attributes.attributes,
|
||||
rpc_handler,
|
||||
custom_protocol
|
||||
.map(create_custom_protocol)
|
||||
.unwrap_or_default(),
|
||||
custom_protocols.into_iter().map(|(_, p)| p).collect(),
|
||||
file_drop_handler,
|
||||
)
|
||||
.map_err(|_| crate::Error::CreateWebview)?;
|
||||
@ -463,7 +505,6 @@ impl Runtime for Wry {
|
||||
let PendingWindow {
|
||||
attributes,
|
||||
rpc_handler,
|
||||
custom_protocol,
|
||||
file_drop_handler,
|
||||
label,
|
||||
..
|
||||
@ -477,14 +518,20 @@ impl Runtime for Wry {
|
||||
let file_drop_handler = file_drop_handler
|
||||
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
|
||||
|
||||
let custom_protocols = {
|
||||
let mut lock = attributes
|
||||
.custom_protocols
|
||||
.lock()
|
||||
.expect("poisoned custom protocols");
|
||||
std::mem::take(&mut *lock)
|
||||
};
|
||||
|
||||
let window = self
|
||||
.inner
|
||||
.add_window_with_configs(
|
||||
attributes,
|
||||
attributes.attributes,
|
||||
rpc_handler,
|
||||
custom_protocol
|
||||
.map(create_custom_protocol)
|
||||
.unwrap_or_default(),
|
||||
custom_protocols.into_iter().map(|(_, p)| p).collect(),
|
||||
file_drop_handler,
|
||||
)
|
||||
.map_err(|_| crate::Error::CreateWebview)?;
|
||||
@ -542,13 +589,3 @@ fn create_file_drop_handler<M: Params<Runtime = Wry>>(
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a wry custom protocol from a tauri custom protocol.
|
||||
fn create_custom_protocol(custom_protocol: CustomProtocol) -> Vec<wry::CustomProtocol> {
|
||||
vec![wry::CustomProtocol {
|
||||
name: custom_protocol.name.clone(),
|
||||
handler: Box::new(move |data| {
|
||||
(custom_protocol.handler)(data).map_err(|_| wry::Error::InitScriptError)
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ pub struct InnerWindowManager<P: Params> {
|
||||
/// A list of salts that are valid for the current application.
|
||||
salts: Mutex<HashSet<Uuid>>,
|
||||
package_info: PackageInfo,
|
||||
/// The custom protocols available to all windows.
|
||||
custom_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
|
||||
}
|
||||
|
||||
/// A [Zero Sized Type] marker representing a full [`Params`].
|
||||
@ -100,6 +102,7 @@ impl<P: Params> WindowManager<P> {
|
||||
plugins: PluginStore<P>,
|
||||
invoke_handler: Box<InvokeHandler<P>>,
|
||||
on_page_load: Box<OnPageLoad<P>>,
|
||||
custom_protocols: HashMap<String, std::sync::Arc<CustomProtocol>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(InnerWindowManager {
|
||||
@ -113,6 +116,7 @@ impl<P: Params> WindowManager<P> {
|
||||
default_window_icon: context.default_window_icon,
|
||||
salts: Mutex::default(),
|
||||
package_info: context.package_info,
|
||||
custom_protocols,
|
||||
}),
|
||||
_marker: Args::default(),
|
||||
}
|
||||
@ -173,6 +177,17 @@ impl<P: Params> WindowManager<P> {
|
||||
}
|
||||
}
|
||||
|
||||
for (name, protocol) in &self.inner.custom_protocols {
|
||||
if !attributes.has_custom_protocol(name) {
|
||||
let protocol = protocol.clone();
|
||||
attributes = attributes.custom_protocol(name.clone(), move |p| (protocol.handler)(p));
|
||||
}
|
||||
}
|
||||
|
||||
if !attributes.has_custom_protocol("tauri") {
|
||||
attributes = attributes.custom_protocol("tauri", self.prepare_custom_protocol().handler);
|
||||
}
|
||||
|
||||
// If we are on windows use App Data Local as webview temp dir
|
||||
// to prevent any bundled application to failed.
|
||||
// Fix: https://github.com/tauri-apps/tauri/issues/1365
|
||||
@ -227,7 +242,6 @@ impl<P: Params> WindowManager<P> {
|
||||
let assets = self.inner.assets.clone();
|
||||
let bundle_identifier = self.inner.config.tauri.bundle.identifier.clone();
|
||||
CustomProtocol {
|
||||
name: "tauri".into(),
|
||||
handler: Box::new(move |path| {
|
||||
let mut path = path
|
||||
.split('?')
|
||||
@ -367,6 +381,7 @@ mod test {
|
||||
PluginStore::default(),
|
||||
Box::new(|_| ()),
|
||||
Box::new(|_, _| ()),
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
@ -433,7 +448,6 @@ impl<P: Params> WindowManager<P> {
|
||||
let label = pending.label.clone();
|
||||
pending.attributes = self.prepare_attributes(attributes, url, label, pending_labels)?;
|
||||
pending.rpc_handler = Some(self.prepare_rpc_handler());
|
||||
pending.custom_protocol = Some(self.prepare_custom_protocol());
|
||||
} else {
|
||||
pending.attributes = attributes.url(url);
|
||||
}
|
||||
|
@ -92,6 +92,19 @@ pub trait Attributes: AttributesBase {
|
||||
/// Sets the webview url.
|
||||
fn url(self, url: String) -> Self;
|
||||
|
||||
/// Whether the custom protocol handler is defined or not.
|
||||
fn has_custom_protocol(&self, name: &str) -> bool;
|
||||
|
||||
/// Adds a custom protocol handler.
|
||||
fn custom_protocol<
|
||||
N: Into<String>,
|
||||
H: Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync + 'static,
|
||||
>(
|
||||
self,
|
||||
name: N,
|
||||
handler: H,
|
||||
) -> Self;
|
||||
|
||||
/// The full attributes.
|
||||
fn build(self) -> Self;
|
||||
}
|
||||
@ -106,10 +119,8 @@ pub struct RpcRequest {
|
||||
|
||||
/// Uses a custom handler to resolve file requests
|
||||
pub struct CustomProtocol {
|
||||
/// Name of the protocol
|
||||
pub name: String,
|
||||
/// Handler for protocol
|
||||
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
|
||||
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send + Sync>,
|
||||
}
|
||||
|
||||
/// The file drop event payload.
|
||||
|
@ -11,7 +11,7 @@ use crate::{
|
||||
hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
|
||||
runtime::{
|
||||
tag::ToJavascript,
|
||||
webview::{CustomProtocol, FileDropHandler, WebviewRpcHandler},
|
||||
webview::{FileDropHandler, WebviewRpcHandler},
|
||||
Dispatch, Runtime,
|
||||
},
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
@ -38,9 +38,6 @@ pub struct PendingWindow<M: Params> {
|
||||
/// How to handle RPC calls on the webview window.
|
||||
pub rpc_handler: Option<WebviewRpcHandler<M>>,
|
||||
|
||||
/// How to handle custom protocols for the webview window.
|
||||
pub custom_protocol: Option<CustomProtocol>,
|
||||
|
||||
/// How to handle a file dropping onto the webview window.
|
||||
pub file_drop_handler: Option<FileDropHandler<M>>,
|
||||
}
|
||||
@ -57,7 +54,6 @@ impl<M: Params> PendingWindow<M> {
|
||||
label,
|
||||
url,
|
||||
rpc_handler: None,
|
||||
custom_protocol: None,
|
||||
file_drop_handler: None,
|
||||
}
|
||||
}
|
||||
@ -71,7 +67,6 @@ impl<M: Params> PendingWindow<M> {
|
||||
label,
|
||||
url,
|
||||
rpc_handler: None,
|
||||
custom_protocol: None,
|
||||
file_drop_handler: None,
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user