Browse Source

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

Lucas Fernandes Nogueira 4 years ago
parent
commit
938fb624f5

+ 5 - 0
.changes/multi-custom-protocols.md

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

+ 31 - 3
core/tauri/src/runtime/app.rs

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

+ 85 - 48
core/tauri/src/runtime/flavors/wry.rs

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

+ 16 - 2
core/tauri/src/runtime/manager.rs

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

+ 14 - 3
core/tauri/src/runtime/webview.rs

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

+ 1 - 6
core/tauri/src/runtime/window.rs

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