add AppHandle.fetch_data_store_identifiers and AppHandle.remove_data_store (#12900)

* add `AppHandle::fetch_all_data_store_identifiers` and `AppHandle::remove_data_store`

* make it run on main thread, so you can call the function from any thread and it works.

* changes file

* update signature, move functions to RuntimeHandle

* add api

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
Simon Laux
2025-03-13 22:17:22 +01:00
committed by GitHub
parent be2e6b85fe
commit d8059bad3c
13 changed files with 286 additions and 11 deletions

View File

@@ -0,0 +1,5 @@
---
"@tauri-apps/api": minor:feat
---
add `AppHandle.fetch_data_store_identifiers` and `AppHandle.remove_data_store` (macOS and iOS only)

4
Cargo.lock generated
View File

@@ -10854,9 +10854,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.50.3"
version = "0.50.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ec139df5102db821f92a42033c3fa0467c5ab434511e79c65881d6bdf2b369"
checksum = "804a7d1613bd699beccaa60f3b3c679acee21cebba1945a693f5eab95c08d1fa"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",

View File

@@ -17,7 +17,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
wry = { version = "0.50.3", default-features = false, features = [
wry = { version = "0.50.4", default-features = false, features = [
"drag-drop",
"protocol",
"os-webview",

View File

@@ -40,10 +40,10 @@ use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
use webview2_com::FocusChangedEventHandler;
#[cfg(windows)]
use windows::Win32::Foundation::HWND;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use wry::WebViewBuilderExtDarwin;
#[cfg(windows)]
use wry::WebViewBuilderExtWindows;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use wry::{WebViewBuilderExtDarwin, WebViewExtDarwin};
use tao::{
dpi::{
@@ -1180,11 +1180,15 @@ unsafe impl Send for GtkBox {}
pub struct SendRawWindowHandle(pub raw_window_handle::RawWindowHandle);
unsafe impl Send for SendRawWindowHandle {}
#[cfg(target_os = "macos")]
#[derive(Debug, Clone)]
pub enum ApplicationMessage {
#[cfg(target_os = "macos")]
Show,
#[cfg(target_os = "macos")]
Hide,
#[cfg(any(target_os = "macos", target_os = "ios"))]
FetchDataStoreIdentifiers(Box<dyn FnOnce(Vec<[u8; 16]>) + Send + 'static>),
#[cfg(any(target_os = "macos", target_os = "ios"))]
RemoveDataStore([u8; 16], Box<dyn FnOnce(Result<()>) + Send + 'static>),
}
pub enum WindowMessage {
@@ -1347,7 +1351,6 @@ pub enum Message<T: 'static> {
#[cfg(target_os = "macos")]
SetActivationPolicy(ActivationPolicy),
RequestExit(i32),
#[cfg(target_os = "macos")]
Application(ApplicationMessage),
Window(WindowId, WindowMessage),
Webview(WindowId, WebviewId, WebviewMessage),
@@ -2507,6 +2510,29 @@ impl<T: UserEvent> RuntimeHandle<T> for WryHandle<T> {
{
dispatch(f)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
&self,
cb: F,
) -> Result<()> {
send_user_message(
&self.context,
Message::Application(ApplicationMessage::FetchDataStoreIdentifiers(Box::new(cb))),
)
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
&self,
uuid: [u8; 16],
cb: F,
) -> Result<()> {
send_user_message(
&self.context,
Message::Application(ApplicationMessage::RemoveDataStore(uuid, Box::new(cb))),
)
}
}
impl<T: UserEvent> Wry<T> {
@@ -2923,14 +2949,29 @@ fn handle_user_message<T: UserEvent>(
event_loop.set_activation_policy_at_runtime(tao_activation_policy(activation_policy))
}
Message::RequestExit(_code) => panic!("cannot handle RequestExit on the main thread"),
#[cfg(target_os = "macos")]
Message::Application(application_message) => match application_message {
#[cfg(target_os = "macos")]
ApplicationMessage::Show => {
event_loop.show_application();
}
#[cfg(target_os = "macos")]
ApplicationMessage::Hide => {
event_loop.hide_application();
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
ApplicationMessage::FetchDataStoreIdentifiers(cb) => {
if let Err(e) = WebView::fetch_data_store_identifiers(cb) {
// this shouldn't ever happen because we're running on the main thread
// but let's be safe and warn here
log::error!("failed to fetch data store identifiers: {e}");
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
ApplicationMessage::RemoveDataStore(uuid, cb) => {
WebView::remove_data_store(&uuid, move |res| {
cb(res.map_err(|_| Error::FailedToRemoveDataStore))
})
}
},
Message::Window(id, window_message) => {
let w = windows.0.borrow().get(&id).map(|w| {

View File

@@ -182,6 +182,9 @@ pub enum Error {
InvalidProxyUrl,
#[error("window not found")]
WindowNotFound,
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[error("failed to remove data store")]
FailedToRemoveDataStore,
}
/// Result type.
@@ -338,6 +341,21 @@ pub trait RuntimeHandle<T: UserEvent>: Debug + Clone + Send + Sync + Sized + 'st
fn run_on_android_context<F>(&self, f: F)
where
F: FnOnce(&mut jni::JNIEnv, &jni::objects::JObject, &jni::objects::JObject) + Send + 'static;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
&self,
cb: F,
) -> Result<()>;
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg_attr(docsrs, doc(cfg(any(target_os = "macos", target_os = "ios"))))]
fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
&self,
uuid: [u8; 16],
cb: F,
) -> Result<()>;
}
pub trait EventLoopProxy<T: UserEvent>: Debug + Clone + Send + Sync {

View File

@@ -152,6 +152,8 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
("identifier", true),
("app_show", false),
("app_hide", false),
("fetch_data_store_identifiers", false),
("remove_data_store", false),
("default_window_icon", false),
("set_app_theme", false),
],

View File

@@ -97,6 +97,32 @@ Denies the default_window_icon command without any pre-configured scope.
<tr>
<td>
`core:app:allow-fetch-data-store-identifiers`
</td>
<td>
Enables the fetch_data_store_identifiers command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`core:app:deny-fetch-data-store-identifiers`
</td>
<td>
Denies the fetch_data_store_identifiers command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`core:app:allow-identifier`
</td>
@@ -149,6 +175,32 @@ Denies the name command without any pre-configured scope.
<tr>
<td>
`core:app:allow-remove-data-store`
</td>
<td>
Enables the remove_data_store command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`core:app:deny-remove-data-store`
</td>
<td>
Denies the remove_data_store command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`core:app:allow-set-app-theme`
</td>

File diff suppressed because one or more lines are too long

View File

@@ -370,6 +370,59 @@ impl AppHandle<crate::Wry> {
}
}
#[cfg(target_vendor = "apple")]
impl<R: Runtime> AppHandle<R> {
/// Fetches all Data Store Indentifiers by this app
///
/// Needs to be called from Main Thread
pub async fn fetch_data_store_identifiers(&self) -> crate::Result<Vec<[u8; 16]>> {
use std::sync::Mutex;
let (tx, rx) = tokio::sync::oneshot::channel::<Result<Vec<[u8; 16]>, tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();
self.run_on_main_thread(move || {
let cloned_lock = lock.clone();
if let Err(err) = runtime_handle.fetch_data_store_identifiers(move |ids| {
if let Some(tx) = cloned_lock.lock().unwrap().take() {
let _ = tx.send(Ok(ids));
}
}) {
if let Some(tx) = lock.lock().unwrap().take() {
let _ = tx.send(Err(err));
}
}
})?;
rx.await?.map_err(Into::into)
}
/// Deletes a Data Store of this app
///
/// Needs to be called from Main Thread
pub async fn remove_data_store(&self, uuid: [u8; 16]) -> crate::Result<()> {
use std::sync::Mutex;
let (tx, rx) = tokio::sync::oneshot::channel::<Result<(), tauri_runtime::Error>>();
let lock: Arc<Mutex<Option<_>>> = Arc::new(Mutex::new(Some(tx)));
let runtime_handle = self.runtime_handle.clone();
self.run_on_main_thread(move || {
let cloned_lock = lock.clone();
if let Err(err) = runtime_handle.remove_data_store(uuid, move |result| {
if let Some(tx) = cloned_lock.lock().unwrap().take() {
let _ = tx.send(result);
}
}) {
if let Some(tx) = lock.lock().unwrap().take() {
let _ = tx.send(Err(err));
}
}
})?;
rx.await?.map_err(Into::into)
}
}
impl<R: Runtime> Clone for AppHandle<R> {
fn clone(&self) -> Self {
Self {

View File

@@ -46,6 +46,27 @@ pub fn app_hide<R: Runtime>(app: AppHandle<R>) -> crate::Result<()> {
Ok(())
}
#[command(root = "crate")]
#[allow(unused_variables)]
pub async fn fetch_data_store_identifiers<R: Runtime>(
app: AppHandle<R>,
) -> crate::Result<Vec<[u8; 16]>> {
#[cfg(target_vendor = "apple")]
return app.fetch_data_store_identifiers().await;
#[cfg(not(target_vendor = "apple"))]
return Ok(Vec::new());
}
#[command(root = "crate")]
#[allow(unused_variables)]
pub async fn remove_data_store<R: Runtime>(app: AppHandle<R>, uuid: [u8; 16]) -> crate::Result<()> {
#[cfg(target_vendor = "apple")]
app.remove_data_store(uuid).await?;
#[cfg(not(target_vendor = "apple"))]
let _ = uuid;
Ok(())
}
#[command(root = "crate")]
pub fn default_window_icon<R: Runtime>(
webview: Webview<R>,
@@ -71,6 +92,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
identifier,
app_show,
app_hide,
fetch_data_store_identifiers,
remove_data_store,
default_window_icon,
set_app_theme,
])

View File

@@ -163,6 +163,9 @@ pub enum Error {
/// Illegal event name.
#[error("only alphanumeric, '-', '/', ':', '_' permitted for event names: {0:?}")]
IllegalEventName(String),
/// tokio oneshot channel failed to receive message
#[error(transparent)]
TokioOneshotRecv(#[from] tokio::sync::oneshot::error::RecvError),
}
impl From<getrandom::Error> for Error {

View File

@@ -282,6 +282,23 @@ impl<T: UserEvent> RuntimeHandle<T> for MockRuntimeHandle {
todo!()
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn fetch_data_store_identifiers<F: FnOnce(Vec<[u8; 16]>) + Send + 'static>(
&self,
cb: F,
) -> Result<()> {
todo!()
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
fn remove_data_store<F: FnOnce(Result<()>) + Send + 'static>(
&self,
uuid: [u8; 16],
cb: F,
) -> Result<()> {
todo!()
}
fn cursor_position(&self) -> Result<PhysicalPosition<f64>> {
Ok(PhysicalPosition::new(0.0, 0.0))
}

View File

@@ -6,6 +6,25 @@ import { invoke } from './core'
import { Image } from './image'
import { Theme } from './window'
export type DataStoreIdentifier = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number
]
/**
* Application metadata and related APIs.
*
@@ -101,6 +120,46 @@ async function hide(): Promise<void> {
return invoke('plugin:app|app_hide')
}
/**
* Fetches the data store identifiers on macOS and iOS.
*
* See https://developer.apple.com/documentation/webkit/wkwebsitedatastore for more information.
*
* @example
* ```typescript
* import { fetchDataStoreIdentifiers } from '@tauri-apps/api/app';
* const ids = await fetchDataStoreIdentifiers();
* ```
*
* @since 2.4.0
*/
async function fetchDataStoreIdentifiers(): Promise<DataStoreIdentifier[]> {
return invoke('plugin:app|fetch_data_store_identifiers')
}
/**
* Removes the data store with the given identifier.
*
* Note that any webview using this data store should be closed before running this API.
*
* See https://developer.apple.com/documentation/webkit/wkwebsitedatastore for more information.
*
* @example
* ```typescript
* import { fetchDataStoreIdentifiers, removeDataStore } from '@tauri-apps/api/app';
* for (const id of (await fetchDataStoreIdentifiers())) {
* await removeDataStore(id);
* }
* ```
*
* @since 2.4.0
*/
async function removeDataStore(
uuid: DataStoreIdentifier
): Promise<DataStoreIdentifier[]> {
return invoke('plugin:app|remove_data_store', { uuid })
}
/**
* Get the default window icon.
*
@@ -145,5 +204,7 @@ export {
show,
hide,
defaultWindowIcon,
setTheme
setTheme,
fetchDataStoreIdentifiers,
removeDataStore
}