mirror of https://github.com/tauri-apps/tauri
feat(core): add WebviewWindow::resolve_command_scope (#11439)
* feat(core): add WebviewWindow::resolve_command_scope This new functionality exposes the `CommandScope` resolution as a function (currently only commands can resolve them as a dependency injection via CommandItem) This function is useful to validate the configuration at runtime (do some asserts at setup phase to ensure capabilities are properly configured) and to resolve scopes in a separate thread or context * adjust return type
This commit is contained in:
parent
a5bf48eab0
commit
f0da0bde87
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"tauri": patch:feat
|
||||
---
|
||||
|
||||
Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime.
|
|
@ -204,7 +204,7 @@ fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<
|
|||
.map(|val| {
|
||||
val.as_str().map(
|
||||
#[allow(clippy::redundant_closure)]
|
||||
|s| formatter(s),
|
||||
&formatter,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
|
|
|
@ -23,7 +23,7 @@ use tauri_utils::platform::Target;
|
|||
use url::Url;
|
||||
|
||||
use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
|
||||
use crate::{AppHandle, Manager, StateManager};
|
||||
use crate::{AppHandle, Manager, StateManager, Webview};
|
||||
|
||||
use super::{CommandArg, CommandItem};
|
||||
|
||||
|
@ -614,6 +614,33 @@ pub struct CommandScope<T: ScopeObject> {
|
|||
}
|
||||
|
||||
impl<T: ScopeObject> CommandScope<T> {
|
||||
pub(crate) fn resolve<R: Runtime>(
|
||||
webview: &Webview<R>,
|
||||
scope_ids: Vec<u64>,
|
||||
) -> crate::Result<Self> {
|
||||
let mut allow = Vec::new();
|
||||
let mut deny = Vec::new();
|
||||
|
||||
for scope_id in scope_ids {
|
||||
let scope = webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scope_manager
|
||||
.get_command_scope_typed::<R, T>(webview.app_handle(), &scope_id)?;
|
||||
|
||||
for s in scope.allows() {
|
||||
allow.push(s.clone());
|
||||
}
|
||||
for s in scope.denies() {
|
||||
deny.push(s.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CommandScope { allow, deny })
|
||||
}
|
||||
|
||||
/// What this access scope allows.
|
||||
pub fn allows(&self) -> &Vec<Arc<T>> {
|
||||
&self.allow
|
||||
|
@ -698,29 +725,7 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
|
|||
.collect::<Vec<_>>()
|
||||
});
|
||||
if let Some(scope_ids) = scope_ids {
|
||||
let mut allow = Vec::new();
|
||||
let mut deny = Vec::new();
|
||||
|
||||
for scope_id in scope_ids {
|
||||
let scope = command
|
||||
.message
|
||||
.webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scope_manager
|
||||
.get_command_scope_typed::<R, T>(command.message.webview.app_handle(), &scope_id)?;
|
||||
|
||||
for s in scope.allows() {
|
||||
allow.push(s.clone());
|
||||
}
|
||||
for s in scope.denies() {
|
||||
deny.push(s.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CommandScope { allow, deny })
|
||||
CommandScope::resolve(&command.message.webview, scope_ids).map_err(Into::into)
|
||||
} else {
|
||||
Ok(CommandScope {
|
||||
allow: Default::default(),
|
||||
|
@ -735,6 +740,17 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
|
|||
pub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);
|
||||
|
||||
impl<T: ScopeObject> GlobalScope<T> {
|
||||
pub(crate) fn resolve<R: Runtime>(webview: &Webview<R>, plugin: &str) -> crate::Result<Self> {
|
||||
webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scope_manager
|
||||
.get_global_scope_typed(webview.app_handle(), plugin)
|
||||
.map(Self)
|
||||
}
|
||||
|
||||
/// What this access scope allows.
|
||||
pub fn allows(&self) -> &Vec<Arc<T>> {
|
||||
&self.0.allow
|
||||
|
@ -749,20 +765,11 @@ impl<T: ScopeObject> GlobalScope<T> {
|
|||
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<T> {
|
||||
/// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
|
||||
fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
|
||||
command
|
||||
.message
|
||||
.webview
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.lock()
|
||||
.unwrap()
|
||||
.scope_manager
|
||||
.get_global_scope_typed(
|
||||
command.message.webview.app_handle(),
|
||||
command.plugin.unwrap_or(APP_ACL_KEY),
|
||||
)
|
||||
.map_err(InvokeError::from_error)
|
||||
.map(GlobalScope)
|
||||
GlobalScope::resolve(
|
||||
&command.message.webview,
|
||||
command.plugin.unwrap_or(APP_ACL_KEY),
|
||||
)
|
||||
.map_err(InvokeError::from_error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ use crate::{
|
|||
app::{UriSchemeResponder, WebviewEvent},
|
||||
event::{EmitArgs, EventTarget},
|
||||
ipc::{
|
||||
CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage,
|
||||
InvokeResolver, Origin, OwnedInvokeResponder,
|
||||
CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
|
||||
InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
|
||||
},
|
||||
manager::AppManager,
|
||||
sealed::{ManagerBase, RuntimeOrDispatch},
|
||||
|
@ -880,6 +880,83 @@ impl<R: Runtime> Webview<R> {
|
|||
.dispatcher
|
||||
.on_webview_event(move |event| f(&event.clone().into()));
|
||||
}
|
||||
|
||||
/// Resolves the given command scope for this webview on the currently loaded URL.
|
||||
///
|
||||
/// If the command is not allowed, returns None.
|
||||
///
|
||||
/// If the scope cannot be deserialized to the given type, an error is returned.
|
||||
///
|
||||
/// In a command context this can be directly resolved from the command arguments via [CommandScope]:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::ipc::CommandScope;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct ScopeType {
|
||||
/// some_value: String,
|
||||
/// }
|
||||
/// #[tauri::command]
|
||||
/// fn my_command(scope: CommandScope<ScopeType>) {
|
||||
/// // check scope
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::Manager;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct ScopeType {
|
||||
/// some_value: String,
|
||||
/// }
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let webview = app.get_webview_window("main").unwrap();
|
||||
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn resolve_command_scope<T: ScopeObject>(
|
||||
&self,
|
||||
plugin: &str,
|
||||
command: &str,
|
||||
) -> crate::Result<Option<ResolvedScope<T>>> {
|
||||
let current_url = self.url()?;
|
||||
let is_local = self.is_local_url(¤t_url);
|
||||
let origin = if is_local {
|
||||
Origin::Local
|
||||
} else {
|
||||
Origin::Remote { url: current_url }
|
||||
};
|
||||
|
||||
let cmd_name = format!("plugin:{plugin}|{command}");
|
||||
let resolved_access = self
|
||||
.manager()
|
||||
.runtime_authority
|
||||
.lock()
|
||||
.unwrap()
|
||||
.resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
|
||||
|
||||
if let Some(access) = resolved_access {
|
||||
let scope_ids = access
|
||||
.iter()
|
||||
.filter_map(|cmd| cmd.scope_id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let command_scope = CommandScope::resolve(self, scope_ids)?;
|
||||
let global_scope = GlobalScope::resolve(self, plugin)?;
|
||||
|
||||
Ok(Some(ResolvedScope {
|
||||
global_scope,
|
||||
command_scope,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Desktop webview setters and actions.
|
||||
|
@ -1702,6 +1779,24 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].
|
||||
pub struct ResolvedScope<T: ScopeObject> {
|
||||
command_scope: CommandScope<T>,
|
||||
global_scope: GlobalScope<T>,
|
||||
}
|
||||
|
||||
impl<T: ScopeObject> ResolvedScope<T> {
|
||||
/// The global plugin scope.
|
||||
pub fn global_scope(&self) -> &GlobalScope<T> {
|
||||
&self.global_scope
|
||||
}
|
||||
|
||||
/// The command-specific scope.
|
||||
pub fn command_scope(&self) -> &CommandScope<T> {
|
||||
&self.command_scope
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
|
|
|
@ -12,6 +12,7 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
event::EventTarget,
|
||||
ipc::ScopeObject,
|
||||
runtime::dpi::{PhysicalPosition, PhysicalSize},
|
||||
window::Monitor,
|
||||
Emitter, Listener, ResourceTable, Window,
|
||||
|
@ -48,7 +49,7 @@ use tauri_macros::default_runtime;
|
|||
#[cfg(windows)]
|
||||
use windows::Win32::Foundation::HWND;
|
||||
|
||||
use super::DownloadEvent;
|
||||
use super::{DownloadEvent, ResolvedScope};
|
||||
|
||||
/// A builder for [`WebviewWindow`], a window that hosts a single webview.
|
||||
pub struct WebviewWindowBuilder<'a, R: Runtime, M: Manager<R>> {
|
||||
|
@ -989,6 +990,52 @@ impl<R: Runtime> WebviewWindow<R> {
|
|||
pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
|
||||
self.window.on_window_event(f);
|
||||
}
|
||||
|
||||
/// Resolves the given command scope for this webview on the currently loaded URL.
|
||||
///
|
||||
/// If the command is not allowed, returns None.
|
||||
///
|
||||
/// If the scope cannot be deserialized to the given type, an error is returned.
|
||||
///
|
||||
/// In a command context this can be directly resolved from the command arguments via [crate::ipc::CommandScope]:
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::ipc::CommandScope;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct ScopeType {
|
||||
/// some_value: String,
|
||||
/// }
|
||||
/// #[tauri::command]
|
||||
/// fn my_command(scope: CommandScope<ScopeType>) {
|
||||
/// // check scope
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use tauri::Manager;
|
||||
///
|
||||
/// #[derive(Debug, serde::Deserialize)]
|
||||
/// struct ScopeType {
|
||||
/// some_value: String,
|
||||
/// }
|
||||
///
|
||||
/// tauri::Builder::default()
|
||||
/// .setup(|app| {
|
||||
/// let webview = app.get_webview_window("main").unwrap();
|
||||
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
|
||||
/// Ok(())
|
||||
/// });
|
||||
/// ```
|
||||
pub fn resolve_command_scope<T: ScopeObject>(
|
||||
&self,
|
||||
plugin: &str,
|
||||
command: &str,
|
||||
) -> crate::Result<Option<ResolvedScope<T>>> {
|
||||
self.webview.resolve_command_scope(plugin, command)
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu APIs
|
||||
|
@ -1038,7 +1085,7 @@ impl<R: Runtime> WebviewWindow<R> {
|
|||
self.window.on_menu_event(f)
|
||||
}
|
||||
|
||||
/// Returns this window menu .
|
||||
/// Returns this window menu.
|
||||
pub fn menu(&self) -> Option<Menu<R>> {
|
||||
self.window.menu()
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ pub struct Sample<R: Runtime>(AppHandle<R>);
|
|||
|
||||
impl<R: Runtime> Sample<R> {
|
||||
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
|
||||
let _ = payload.on_event.send(Event {
|
||||
payload.on_event.send(Event {
|
||||
kind: "ping".to_string(),
|
||||
value: payload.value.clone(),
|
||||
});
|
||||
})?;
|
||||
Ok(PingResponse {
|
||||
value: payload.value,
|
||||
})
|
||||
|
|
|
@ -7,6 +7,8 @@ pub enum Error {
|
|||
#[cfg(mobile)]
|
||||
#[error(transparent)]
|
||||
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
|
||||
#[error(transparent)]
|
||||
Tauri(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
|
Loading…
Reference in New Issue