Skip to content

Commit f0da0bd

Browse files
authored
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
1 parent a5bf48e commit f0da0bd

File tree

7 files changed

+201
-45
lines changed

7 files changed

+201
-45
lines changed

.changes/resolve_command_scope.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch:feat
3+
---
4+
5+
Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime.

crates/tauri-cli/src/mobile/init.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ fn get_str_array(helper: &Helper, formatter: impl Fn(&str) -> String) -> Option<
204204
.map(|val| {
205205
val.as_str().map(
206206
#[allow(clippy::redundant_closure)]
207-
|s| formatter(s),
207+
&formatter,
208208
)
209209
})
210210
.collect()

crates/tauri/src/ipc/authority.rs

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use tauri_utils::platform::Target;
2323
use url::Url;
2424

2525
use crate::{ipc::InvokeError, sealed::ManagerBase, Runtime};
26-
use crate::{AppHandle, Manager, StateManager};
26+
use crate::{AppHandle, Manager, StateManager, Webview};
2727

2828
use super::{CommandArg, CommandItem};
2929

@@ -614,6 +614,33 @@ pub struct CommandScope<T: ScopeObject> {
614614
}
615615

616616
impl<T: ScopeObject> CommandScope<T> {
617+
pub(crate) fn resolve<R: Runtime>(
618+
webview: &Webview<R>,
619+
scope_ids: Vec<u64>,
620+
) -> crate::Result<Self> {
621+
let mut allow = Vec::new();
622+
let mut deny = Vec::new();
623+
624+
for scope_id in scope_ids {
625+
let scope = webview
626+
.manager()
627+
.runtime_authority
628+
.lock()
629+
.unwrap()
630+
.scope_manager
631+
.get_command_scope_typed::<R, T>(webview.app_handle(), &scope_id)?;
632+
633+
for s in scope.allows() {
634+
allow.push(s.clone());
635+
}
636+
for s in scope.denies() {
637+
deny.push(s.clone());
638+
}
639+
}
640+
641+
Ok(CommandScope { allow, deny })
642+
}
643+
617644
/// What this access scope allows.
618645
pub fn allows(&self) -> &Vec<Arc<T>> {
619646
&self.allow
@@ -698,29 +725,7 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
698725
.collect::<Vec<_>>()
699726
});
700727
if let Some(scope_ids) = scope_ids {
701-
let mut allow = Vec::new();
702-
let mut deny = Vec::new();
703-
704-
for scope_id in scope_ids {
705-
let scope = command
706-
.message
707-
.webview
708-
.manager()
709-
.runtime_authority
710-
.lock()
711-
.unwrap()
712-
.scope_manager
713-
.get_command_scope_typed::<R, T>(command.message.webview.app_handle(), &scope_id)?;
714-
715-
for s in scope.allows() {
716-
allow.push(s.clone());
717-
}
718-
for s in scope.denies() {
719-
deny.push(s.clone());
720-
}
721-
}
722-
723-
Ok(CommandScope { allow, deny })
728+
CommandScope::resolve(&command.message.webview, scope_ids).map_err(Into::into)
724729
} else {
725730
Ok(CommandScope {
726731
allow: Default::default(),
@@ -735,6 +740,17 @@ impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for CommandScope<T> {
735740
pub struct GlobalScope<T: ScopeObject>(ScopeValue<T>);
736741

737742
impl<T: ScopeObject> GlobalScope<T> {
743+
pub(crate) fn resolve<R: Runtime>(webview: &Webview<R>, plugin: &str) -> crate::Result<Self> {
744+
webview
745+
.manager()
746+
.runtime_authority
747+
.lock()
748+
.unwrap()
749+
.scope_manager
750+
.get_global_scope_typed(webview.app_handle(), plugin)
751+
.map(Self)
752+
}
753+
738754
/// What this access scope allows.
739755
pub fn allows(&self) -> &Vec<Arc<T>> {
740756
&self.0.allow
@@ -749,20 +765,11 @@ impl<T: ScopeObject> GlobalScope<T> {
749765
impl<'a, R: Runtime, T: ScopeObject> CommandArg<'a, R> for GlobalScope<T> {
750766
/// Grabs the [`ResolvedScope`] from the [`CommandItem`] and returns the associated [`GlobalScope`].
751767
fn from_command(command: CommandItem<'a, R>) -> Result<Self, InvokeError> {
752-
command
753-
.message
754-
.webview
755-
.manager()
756-
.runtime_authority
757-
.lock()
758-
.unwrap()
759-
.scope_manager
760-
.get_global_scope_typed(
761-
command.message.webview.app_handle(),
762-
command.plugin.unwrap_or(APP_ACL_KEY),
763-
)
764-
.map_err(InvokeError::from_error)
765-
.map(GlobalScope)
768+
GlobalScope::resolve(
769+
&command.message.webview,
770+
command.plugin.unwrap_or(APP_ACL_KEY),
771+
)
772+
.map_err(InvokeError::from_error)
766773
}
767774
}
768775

crates/tauri/src/webview/mod.rs

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ use crate::{
2929
app::{UriSchemeResponder, WebviewEvent},
3030
event::{EmitArgs, EventTarget},
3131
ipc::{
32-
CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage,
33-
InvokeResolver, Origin, OwnedInvokeResponder,
32+
CallbackFn, CommandArg, CommandItem, CommandScope, GlobalScope, Invoke, InvokeBody,
33+
InvokeError, InvokeMessage, InvokeResolver, Origin, OwnedInvokeResponder, ScopeObject,
3434
},
3535
manager::AppManager,
3636
sealed::{ManagerBase, RuntimeOrDispatch},
@@ -880,6 +880,83 @@ impl<R: Runtime> Webview<R> {
880880
.dispatcher
881881
.on_webview_event(move |event| f(&event.clone().into()));
882882
}
883+
884+
/// Resolves the given command scope for this webview on the currently loaded URL.
885+
///
886+
/// If the command is not allowed, returns None.
887+
///
888+
/// If the scope cannot be deserialized to the given type, an error is returned.
889+
///
890+
/// In a command context this can be directly resolved from the command arguments via [CommandScope]:
891+
///
892+
/// ```
893+
/// use tauri::ipc::CommandScope;
894+
///
895+
/// #[derive(Debug, serde::Deserialize)]
896+
/// struct ScopeType {
897+
/// some_value: String,
898+
/// }
899+
/// #[tauri::command]
900+
/// fn my_command(scope: CommandScope<ScopeType>) {
901+
/// // check scope
902+
/// }
903+
/// ```
904+
///
905+
/// # Examples
906+
///
907+
/// ```
908+
/// use tauri::Manager;
909+
///
910+
/// #[derive(Debug, serde::Deserialize)]
911+
/// struct ScopeType {
912+
/// some_value: String,
913+
/// }
914+
///
915+
/// tauri::Builder::default()
916+
/// .setup(|app| {
917+
/// let webview = app.get_webview_window("main").unwrap();
918+
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
919+
/// Ok(())
920+
/// });
921+
/// ```
922+
pub fn resolve_command_scope<T: ScopeObject>(
923+
&self,
924+
plugin: &str,
925+
command: &str,
926+
) -> crate::Result<Option<ResolvedScope<T>>> {
927+
let current_url = self.url()?;
928+
let is_local = self.is_local_url(&current_url);
929+
let origin = if is_local {
930+
Origin::Local
931+
} else {
932+
Origin::Remote { url: current_url }
933+
};
934+
935+
let cmd_name = format!("plugin:{plugin}|{command}");
936+
let resolved_access = self
937+
.manager()
938+
.runtime_authority
939+
.lock()
940+
.unwrap()
941+
.resolve_access(&cmd_name, self.window().label(), self.label(), &origin);
942+
943+
if let Some(access) = resolved_access {
944+
let scope_ids = access
945+
.iter()
946+
.filter_map(|cmd| cmd.scope_id)
947+
.collect::<Vec<_>>();
948+
949+
let command_scope = CommandScope::resolve(self, scope_ids)?;
950+
let global_scope = GlobalScope::resolve(self, plugin)?;
951+
952+
Ok(Some(ResolvedScope {
953+
global_scope,
954+
command_scope,
955+
}))
956+
} else {
957+
Ok(None)
958+
}
959+
}
883960
}
884961

885962
/// Desktop webview setters and actions.
@@ -1702,6 +1779,24 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Webview<R> {
17021779
}
17031780
}
17041781

1782+
/// Resolved scope that can be obtained via [`Webview::resolve_command_scope`].
1783+
pub struct ResolvedScope<T: ScopeObject> {
1784+
command_scope: CommandScope<T>,
1785+
global_scope: GlobalScope<T>,
1786+
}
1787+
1788+
impl<T: ScopeObject> ResolvedScope<T> {
1789+
/// The global plugin scope.
1790+
pub fn global_scope(&self) -> &GlobalScope<T> {
1791+
&self.global_scope
1792+
}
1793+
1794+
/// The command-specific scope.
1795+
pub fn command_scope(&self) -> &CommandScope<T> {
1796+
&self.command_scope
1797+
}
1798+
}
1799+
17051800
#[cfg(test)]
17061801
mod tests {
17071802
#[test]

crates/tauri/src/webview/webview_window.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{
1212

1313
use crate::{
1414
event::EventTarget,
15+
ipc::ScopeObject,
1516
runtime::dpi::{PhysicalPosition, PhysicalSize},
1617
window::Monitor,
1718
Emitter, Listener, ResourceTable, Window,
@@ -48,7 +49,7 @@ use tauri_macros::default_runtime;
4849
#[cfg(windows)]
4950
use windows::Win32::Foundation::HWND;
5051

51-
use super::DownloadEvent;
52+
use super::{DownloadEvent, ResolvedScope};
5253

5354
/// A builder for [`WebviewWindow`], a window that hosts a single webview.
5455
pub struct WebviewWindowBuilder<'a, R: Runtime, M: Manager<R>> {
@@ -989,6 +990,52 @@ impl<R: Runtime> WebviewWindow<R> {
989990
pub fn on_window_event<F: Fn(&WindowEvent) + Send + 'static>(&self, f: F) {
990991
self.window.on_window_event(f);
991992
}
993+
994+
/// Resolves the given command scope for this webview on the currently loaded URL.
995+
///
996+
/// If the command is not allowed, returns None.
997+
///
998+
/// If the scope cannot be deserialized to the given type, an error is returned.
999+
///
1000+
/// In a command context this can be directly resolved from the command arguments via [crate::ipc::CommandScope]:
1001+
///
1002+
/// ```
1003+
/// use tauri::ipc::CommandScope;
1004+
///
1005+
/// #[derive(Debug, serde::Deserialize)]
1006+
/// struct ScopeType {
1007+
/// some_value: String,
1008+
/// }
1009+
/// #[tauri::command]
1010+
/// fn my_command(scope: CommandScope<ScopeType>) {
1011+
/// // check scope
1012+
/// }
1013+
/// ```
1014+
///
1015+
/// # Examples
1016+
///
1017+
/// ```
1018+
/// use tauri::Manager;
1019+
///
1020+
/// #[derive(Debug, serde::Deserialize)]
1021+
/// struct ScopeType {
1022+
/// some_value: String,
1023+
/// }
1024+
///
1025+
/// tauri::Builder::default()
1026+
/// .setup(|app| {
1027+
/// let webview = app.get_webview_window("main").unwrap();
1028+
/// let scope = webview.resolve_command_scope::<ScopeType>("my-plugin", "read");
1029+
/// Ok(())
1030+
/// });
1031+
/// ```
1032+
pub fn resolve_command_scope<T: ScopeObject>(
1033+
&self,
1034+
plugin: &str,
1035+
command: &str,
1036+
) -> crate::Result<Option<ResolvedScope<T>>> {
1037+
self.webview.resolve_command_scope(plugin, command)
1038+
}
9921039
}
9931040

9941041
/// Menu APIs
@@ -1038,7 +1085,7 @@ impl<R: Runtime> WebviewWindow<R> {
10381085
self.window.on_menu_event(f)
10391086
}
10401087

1041-
/// Returns this window menu .
1088+
/// Returns this window menu.
10421089
pub fn menu(&self) -> Option<Menu<R>> {
10431090
self.window.menu()
10441091
}

examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ pub struct Sample<R: Runtime>(AppHandle<R>);
1919

2020
impl<R: Runtime> Sample<R> {
2121
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
22-
let _ = payload.on_event.send(Event {
22+
payload.on_event.send(Event {
2323
kind: "ping".to_string(),
2424
value: payload.value.clone(),
25-
});
25+
})?;
2626
Ok(PingResponse {
2727
value: payload.value,
2828
})

examples/api/src-tauri/tauri-plugin-sample/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pub enum Error {
77
#[cfg(mobile)]
88
#[error(transparent)]
99
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
10+
#[error(transparent)]
11+
Tauri(#[from] tauri::Error),
1012
}
1113

1214
pub type Result<T> = std::result::Result<T, Error>;

0 commit comments

Comments
 (0)