feat(core): expose custom protocol handler APIs (#1553)

This commit is contained in:
Lucas Fernandes Nogueira 2021-04-21 15:43:11 -03:00 committed by GitHub
parent be65f04db7
commit 938fb624f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 62 deletions

View File

@ -0,0 +1,5 @@
---
"tauri": patch
---
Adds APIs to determine global and window-specific custom protocol handlers.

View File

@ -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

View File

@ -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)
}),
}]
}

View File

@ -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);
}

View File

@ -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.

View File

@ -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,
}
}