Compare commits

...

5 Commits

Author SHA1 Message Date
github-actions[bot] 7af38df26a publish new versions 2026-07-21 10:26:23 +00:00
Tony ab7489c964 feat(updater): option to not restart after install (#3299)
* feat(updater): option to not restart after install

* More platform cfg

* Add the same function for `UpdaterBuilder`

* Fix missing `#[cfg(windows)]`

* Mark `current_exe_args` cfg(windows)

* Mark `on_before_exit` cfg(windows)

* Mark `installer_args` cfg(windows)

* Note about `installer_arg` apply to both

* Remove current args and restart together

* Only build needed bundle on windows as well

* Remove `/NS` since it breaks the msi updater

* Add launch updater debug log

* Add a folder to test updates

* Remove unused `#[allow(unused)]`

* Format

* messed up git stage

* Add change file

* Clean up

* Disable NSIS compression for API example

* Bump wry for v1 test to pull in https://github.com/tauri-apps/wry/pull/1703

* format

* Make typescript happy

* Close new update on destroy
2026-07-21 18:25:16 +08:00
Vitor Ayres 3fb27bf13a fix(docs): deep link platform specific and fs scope (#3504)
* deep link fix Platform Specific heading

* fix fs permission toml

* .changes

* Update change files

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>

* Update .changes/fs-deny-scope.md

* fix eol

---------

Co-authored-by: Tony <68118705+Legend-Master@users.noreply.github.com>
2026-07-21 14:56:52 +08:00
Tony 7e45774610 refactor(fs): cfg gate some ios code (#3510) 2026-07-21 14:42:45 +08:00
Pascal Auf der Maur 526726162f fix(nfc): adapt backend invoke to frontend (#3419)
* fix(nfc): adapt backend invoke to frontend

* Revert "fix(nfc): adapt backend invoke to frontend"

This reverts commit e34306e083.

* fix:  enforce nfc function return type

* Update .changes/nfc.md

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>

---------

Co-authored-by: Fabian-Lars <30730186+FabianLars@users.noreply.github.com>
2026-07-20 13:11:20 +02:00
49 changed files with 599 additions and 356 deletions
Generated
+9 -9
View File
@@ -207,7 +207,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "api"
version = "2.0.46"
version = "2.0.47"
dependencies = [
"log",
"serde",
@@ -6506,7 +6506,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
version = "2.4.9"
version = "2.4.10"
dependencies = [
"dunce",
"plist",
@@ -6525,7 +6525,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.7.2"
version = "2.7.3"
dependencies = [
"log",
"raw-window-handle",
@@ -6541,7 +6541,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.5.1"
version = "2.5.2"
dependencies = [
"anyhow",
"dunce",
@@ -6604,7 +6604,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.5.9"
version = "2.5.10"
dependencies = [
"bytes",
"cookie_store",
@@ -6660,7 +6660,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-nfc"
version = "2.3.5"
version = "2.3.6"
dependencies = [
"log",
"serde",
@@ -6728,7 +6728,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-persisted-scope"
version = "2.3.7"
version = "2.3.8"
dependencies = [
"aho-corasick",
"bincode",
@@ -6782,7 +6782,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-single-instance"
version = "2.4.3"
version = "2.4.4"
dependencies = [
"semver",
"serde",
@@ -6852,7 +6852,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-updater"
version = "2.10.1"
version = "2.11.0"
dependencies = [
"base64 0.22.1",
"dirs 6.0.0",
+10
View File
@@ -1,5 +1,15 @@
# Changelog
## \[2.0.43]
### Dependencies
- Upgraded to `fs-js@2.5.2`
- Upgraded to `nfc-js@2.3.6`
- Upgraded to `updater-js@2.11.0`
- Upgraded to `dialog-js@2.7.3`
- Upgraded to `http-js@2.5.10`
## \[2.0.42]
### Dependencies
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "api",
"private": true,
"version": "2.0.42",
"version": "2.0.43",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
@@ -16,19 +16,19 @@
"@tauri-apps/plugin-cli": "workspace:*",
"@tauri-apps/plugin-clipboard-manager": "workspace:*",
"@tauri-apps/plugin-dialog": "2",
"@tauri-apps/plugin-fs": "workspace:*",
"@tauri-apps/plugin-fs": "2",
"@tauri-apps/plugin-geolocation": "workspace:*",
"@tauri-apps/plugin-global-shortcut": "workspace:*",
"@tauri-apps/plugin-haptics": "workspace:*",
"@tauri-apps/plugin-http": "workspace:*",
"@tauri-apps/plugin-nfc": "workspace:*",
"@tauri-apps/plugin-http": "2",
"@tauri-apps/plugin-nfc": "2",
"@tauri-apps/plugin-notification": "workspace:*",
"@tauri-apps/plugin-opener": "workspace:*",
"@tauri-apps/plugin-os": "workspace:*",
"@tauri-apps/plugin-process": "workspace:*",
"@tauri-apps/plugin-shell": "workspace:*",
"@tauri-apps/plugin-store": "2",
"@tauri-apps/plugin-updater": "workspace:*",
"@tauri-apps/plugin-updater": "2",
"@tauri-apps/plugin-upload": "workspace:*",
"@zerodevx/svelte-json-view": "1.0.11"
},
+10
View File
@@ -1,5 +1,15 @@
# Changelog
## \[2.0.47]
### Dependencies
- Upgraded to `fs@2.5.2`
- Upgraded to `nfc@2.3.6`
- Upgraded to `updater@2.11.0`
- Upgraded to `dialog@2.7.3`
- Upgraded to `http@2.5.10`
## \[2.0.46]
### Dependencies
+6 -6
View File
@@ -1,7 +1,7 @@
[package]
name = "api"
publish = false
version = "2.0.46"
version = "2.0.47"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = { workspace = true }
@@ -21,15 +21,15 @@ tiny_http = "0.12"
time = "0.3"
log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.9.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.5.1", features = [
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.5.2", features = [
"watch",
] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.7.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.7.3" }
tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart",
"cookies",
], version = "2.5.9" }
], version = "2.5.10" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.3", features = [
"windows7-compat",
] }
@@ -57,12 +57,12 @@ features = [
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.2" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.10.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.11.0" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.5" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.5" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.6" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.2" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.2" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.2" }
+5
View File
@@ -77,7 +77,9 @@
},
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"dangerousInsecureTransportProtocol": true,
"endpoints": [
"http://localhost:5173/updater-test/updater.json",
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
]
}
@@ -99,6 +101,9 @@
"localePath": "locales/pt-BR.wxl"
}
}
},
"nsis": {
"compression": "none"
}
},
"iOS": {
+19 -11
View File
@@ -1,12 +1,18 @@
<script>
import { check } from '@tauri-apps/plugin-updater'
<script lang="ts">
import { check, Update } from '@tauri-apps/plugin-updater'
import { relaunch } from '@tauri-apps/plugin-process'
import { onDestroy } from 'svelte'
export let onMessage
let { onMessage } = $props()
let isChecking, isInstalling, newUpdate
let totalSize = 0,
downloadedSize = 0
let isChecking = $state(false)
let isInstalling = $state(false)
let newUpdate = $state<Update | undefined>()
let totalSize = $state(0)
let downloadedSize = $state(0)
let progress = $derived(
totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
)
async function checkUpdate() {
isChecking = true
@@ -31,10 +37,10 @@
isInstalling = true
downloadedSize = 0
try {
await newUpdate.downloadAndInstall((downloadProgress) => {
await newUpdate!.downloadAndInstall((downloadProgress) => {
switch (downloadProgress.event) {
case 'Started':
totalSize = downloadProgress.data.contentLength
totalSize = downloadProgress.data.contentLength!
break
case 'Progress':
downloadedSize += downloadProgress.data.chunkLength
@@ -54,14 +60,16 @@
}
}
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
onDestroy(() => {
newUpdate?.close()
})
</script>
<div class="flex children:grow children:h10">
{#if !isChecking && !newUpdate}
<button class="btn" on:click={checkUpdate}>Check update</button>
<button class="btn" onclick={checkUpdate}>Check update</button>
{:else if !isInstalling && newUpdate}
<button class="btn" on:click={install}>Install update</button>
<button class="btn" onclick={install}>Install update</button>
{:else}
<div class="progress">
<span>{progress}%</span>
+5
View File
@@ -0,0 +1,5 @@
*
!.gitignore
!README.md
!updater.json
+10
View File
@@ -0,0 +1,10 @@
To test the updater:
1. Comment out `verify_signature` inside `plugins/updater/src/updater.rs`
2. Run `pnpm tauri build --debug` to build the bundles
3. Copy the bundle you want to test to this folder (`examples/api/updater-test/`)
4. Run `pnpm tauri dev` and open the Updater tab to check
If the bundle you're testing doesn't exist in the `updater.json` yet, add it manually and welcome to open an PR
> The `updater.json` and debug bundles will be served by `vite`
+11
View File
@@ -0,0 +1,11 @@
{
"version": "2.1.0",
"notes": "Test update!",
"pub_date": "2026-03-01T14:04:20+00:00",
"platforms": {
"windows-x86_64": {
"signature": "",
"url": "http://localhost:5173/updater-test/Tauri API_2.0.0_x64_en-US.msi"
}
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ export default defineConfig(async () => {
port: 5173,
strictPort: true,
fs: {
allow: ['.', '../../tooling/api/dist']
allow: ['.']
}
}
}
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.4.10]
- [`3fb27bf1`](https://github.com/tauri-apps/plugins-workspace/commit/3fb27bf13a88dd1e5aa2cc933408be02b0eafc91) ([#3504](https://github.com/tauri-apps/plugins-workspace/pull/3504) by [@vasfvitor](https://github.com/tauri-apps/plugins-workspace/../../vasfvitor)) adds proper Platform-specific sections to the docs for `register`, `unregister`, `isRegistered`, and `onOpenUrl`
## \[2.4.9]
- [`e6cdc9f5`](https://github.com/tauri-apps/plugins-workspace/commit/e6cdc9f52e2cd975b11b8e4c12879d597f1f76c3) ([#3396](https://github.com/tauri-apps/plugins-workspace/pull/3396) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix broken iOS custom URL schemes
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-deep-link"
version = "2.4.9"
version = "2.4.10"
description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true }
license = { workspace = true }
@@ -1,5 +1,11 @@
# Changelog
## \[2.2.13]
### Dependencies
- Upgraded to `deep-link-js@2.4.10`
## \[2.2.12]
### Dependencies
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "deep-link-example",
"private": true,
"version": "2.2.12",
"version": "2.2.13",
"type": "module",
"scripts": {
"dev": "vite",
@@ -11,7 +11,7 @@
},
"dependencies": {
"@tauri-apps/api": "^2.11.0",
"@tauri-apps/plugin-deep-link": "workspace:*"
"@tauri-apps/plugin-deep-link": "2"
},
"devDependencies": {
"@tauri-apps/cli": "2.11.4",
+17 -7
View File
@@ -14,9 +14,11 @@ import { type UnlistenFn, listen } from '@tauri-apps/api/event'
* const urls = await getCurrent();
* ```
*
* #### - **Windows / Linux**: This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values.
* Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`].
* Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect..
* #### Platform-specific
*
* - **Windows / Linux:** This function reads the command line arguments and checks if there's only one value, which must be an URL with scheme matching one of the configured values.
* Note that you must manually check the arguments when registering deep link schemes dynamically with [`Self::register`].
* Additionally, the deep link might have been provided as a CLI argument so you should check if its format matches what you expect.
*
* @since 2.0.0
*/
@@ -35,7 +37,9 @@ export async function getCurrent(): Promise<string[] | null> {
* await register("my-scheme");
* ```
*
* #### - **macOS / Android / iOS**: Unsupported.
* #### Platform-specific
*
* - **macOS / Android / iOS:** Unsupported.
*
* @since 2.0.0
*/
@@ -54,7 +58,9 @@ export async function register(protocol: string): Promise<null> {
* await unregister("my-scheme");
* ```
*
* #### - **macOS / Linux / Android / iOS**: Unsupported.
* #### Platform-specific
*
* - **macOS / Linux / Android / iOS:** Unsupported.
*
* @since 2.0.0
*/
@@ -73,7 +79,9 @@ export async function unregister(protocol: string): Promise<null> {
* await isRegistered("my-scheme");
* ```
*
* #### - **macOS / Android / iOS**: Unsupported.
* #### Platform-specific
*
* - **macOS / Android / iOS:** Unsupported.
*
* @since 2.0.0
*/
@@ -92,7 +100,9 @@ export async function isRegistered(protocol: string): Promise<boolean> {
* await onOpenUrl((urls) => { console.log(urls) });
* ```
*
* #### - **Windows / Linux**: Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument.
* #### Platform-specific
*
* - **Windows / Linux:** Unsupported without the single-instance plugin. The OS will spawn a new app instance passing the URL as a CLI argument.
*
* @since 2.0.0
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-deep-link",
"version": "2.4.9",
"version": "2.4.10",
"description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0",
"authors": [
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.7.3]
### Dependencies
- Upgraded to `fs-js@2.5.2`
## \[2.7.2]
- [`40ae0a7f`](https://github.com/tauri-apps/plugins-workspace/commit/40ae0a7fa0ecc9b0cdbd952297f7a17f37308229) ([#3491](https://github.com/tauri-apps/plugins-workspace/pull/3491) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Use `com.google.android.material.dialog.MaterialAlertDialogBuilder` instead of `AlertDialog` so the dialog follows the app's theme
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-dialog"
version = "2.7.2"
version = "2.7.3"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
@@ -37,7 +37,7 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.5.1" }
tauri-plugin-fs = { path = "../fs", version = "2.5.2" }
[target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] }
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-dialog",
"version": "2.7.2",
"version": "2.7.3",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.5.2]
- [`3fb27bf1`](https://github.com/tauri-apps/plugins-workspace/commit/3fb27bf13a88dd1e5aa2cc933408be02b0eafc91) ([#3504](https://github.com/tauri-apps/plugins-workspace/pull/3504) by [@vasfvitor](https://github.com/tauri-apps/plugins-workspace/../../vasfvitor)) Fixed `deny-webview-data` has no effect
## \[2.5.1]
- [`ec054013`](https://github.com/tauri-apps/plugins-workspace/commit/ec0540138bece081e9a87982091947360e61987a) ([#3323](https://github.com/tauri-apps/plugins-workspace/pull/3323) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated dependency `toml` from 0.9 to 1
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-fs"
version = "2.5.1"
version = "2.5.2"
description = "Access the file system."
authors = { workspace = true }
license = { workspace = true }
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-fs",
"version": "2.5.1",
"version": "2.5.2",
"description": "Access the file system.",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -6,7 +6,7 @@ description = """This denies read access to the
`$APPLOCALDATA` folder on linux as the webview data and configuration values are stored here.
Allowing access can lead to sensitive information disclosure and should be well considered."""
[[scope.deny]]
[[permission.scope.deny]]
path = "$APPLOCALDATA/**"
[[permission]]
@@ -15,5 +15,5 @@ description = """This denies read access to the
`$APPLOCALDATA/EBWebView` folder on windows as the webview data and configuration values are stored here.
Allowing access can lead to sensitive information disclosure and should be well considered."""
[[scope.deny]]
[[permission.scope.deny]]
path = "$APPLOCALDATA/EBWebView/**"
+106 -109
View File
@@ -74,7 +74,7 @@ pub type CommandResult<T> = std::result::Result<T, CommandError>;
/// Represents either a plain PathBuf or a PathHandle that manages security-scoped resources.
pub enum PathKind<R: Runtime> {
/// A plain path that doesn't manage security-scoped resources.
#[allow(dead_code)] // only used on mobile
#[cfg(mobile)] // only used on mobile
Path(PathBuf),
/// A path handle that manages security-scoped resources and will clean them up on drop.
Handle(PathHandle<R>),
@@ -84,6 +84,7 @@ impl<R: Runtime> PathKind<R> {
/// Get a reference to the underlying path.
pub fn as_path(&self) -> &Path {
match self {
#[cfg(mobile)]
PathKind::Path(p) => p.as_ref(),
PathKind::Handle(h) => h.as_ref(),
}
@@ -92,6 +93,7 @@ impl<R: Runtime> PathKind<R> {
/// Get a reference to the underlying PathBuf.
pub fn as_path_buf(&self) -> &PathBuf {
match self {
#[cfg(mobile)]
PathKind::Path(p) => p,
PathKind::Handle(h) => h,
}
@@ -114,27 +116,13 @@ impl<R: Runtime> AsRef<PathBuf> for PathKind<R> {
pub struct FileHandle<R: Runtime> {
file: File,
path: PathKind<R>,
#[allow(dead_code)] // Used in Drop implementation
#[cfg(target_os = "ios")]
path_: SafeFilePath,
#[allow(dead_code)] // Used in Drop implementation
#[cfg(target_os = "ios")]
app_handle: tauri::AppHandle<R>,
}
impl<R: Runtime> FileHandle<R> {
fn new(
file: File,
path: PathKind<R>,
path_: SafeFilePath,
app_handle: tauri::AppHandle<R>,
) -> Self {
Self {
file,
path,
path_,
app_handle,
}
}
/// Get the resolved path.
pub fn path(&self) -> &Path {
self.path.as_path()
@@ -155,41 +143,39 @@ impl<R: Runtime> DerefMut for FileHandle<R> {
}
}
#[cfg(target_os = "ios")]
impl<R: Runtime> Drop for FileHandle<R> {
fn drop(&mut self) {
#[cfg(target_os = "ios")]
{
// Only clean up if we have a plain PathBuf, not a PathHandle
// PathHandle will handle its own cleanup when it's dropped
if let PathKind::Path(_) = &self.path {
use crate::{FilePath, FsExt};
// Convert SafeFilePath to FilePath
let file_path: FilePath = match &self.path_ {
SafeFilePath::Url(url) => FilePath::Url(url.clone()),
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
// Only clean up if we have a plain PathBuf, not a PathHandle
// PathHandle will handle its own cleanup when it's dropped
if let PathKind::Path(_) = &self.path {
use crate::{FilePath, FsExt};
// Convert SafeFilePath to FilePath
let file_path: FilePath = match &self.path_ {
SafeFilePath::Url(url) => FilePath::Url(url.clone()),
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
// Only clean up if we're tracking this resource
// If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere
if let FilePath::Url(url) = file_path {
if url.scheme() == "file" {
let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if we're tracking this resource
// If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere
if let FilePath::Url(url) = file_path {
if url.scheme() == "file" {
let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!("Stopping accessing security-scoped resource for URL: {url} on drop");
let _ = self
.app_handle
.fs()
.stop_accessing_security_scoped_resource(FilePath::Url(
url.clone(),
));
security_scoped_resources.remove(url.as_str());
} else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
// Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!(
"Stopping accessing security-scoped resource for URL: {url} on drop"
);
let _ = self
.app_handle
.fs()
.stop_accessing_security_scoped_resource(FilePath::Url(url.clone()));
security_scoped_resources.remove(url.as_str());
} else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
}
}
@@ -236,38 +222,36 @@ impl<R: Runtime> AsRef<PathBuf> for PathHandle<R> {
}
}
#[cfg(target_os = "ios")]
impl<R: Runtime> Drop for PathHandle<R> {
fn drop(&mut self) {
#[cfg(target_os = "ios")]
{
use crate::{FilePath, FsExt};
// Convert SafeFilePath to FilePath
let file_path: FilePath = match &self.path_ {
SafeFilePath::Url(url) => FilePath::Url(url.clone()),
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
use crate::{FilePath, FsExt};
// Convert SafeFilePath to FilePath
let file_path: FilePath = match &self.path_ {
SafeFilePath::Url(url) => FilePath::Url(url.clone()),
SafeFilePath::Path(safe_path) => FilePath::Path(safe_path.as_ref().to_owned()),
};
// Only clean up if we're tracking this resource (i.e., resolve_path started it)
// If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere
if let FilePath::Url(url) = file_path {
if url.scheme() == "file" {
let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if we're tracking this resource (i.e., resolve_path started it)
// If start_accessing_security_scoped_resource was used, it won't be in our tracking
// and we shouldn't interfere
if let FilePath::Url(url) = file_path {
if url.scheme() == "file" {
let security_scoped_resources =
self.app_handle.state::<crate::SecurityScopedResources>();
// Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!(
"Stopping accessing security-scoped resource for URL: {url} on drop"
);
let _ = self
.app_handle
.fs()
.stop_accessing_security_scoped_resource(FilePath::Url(url.clone()));
security_scoped_resources.remove(url.as_str());
} else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
// Only clean up if it's not tracked manually
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
log::debug!(
"Stopping accessing security-scoped resource for URL: {url} on drop"
);
let _ = self
.app_handle
.fs()
.stop_accessing_security_scoped_resource(FilePath::Url(url.clone()));
security_scoped_resources.remove(url.as_str());
} else {
log::debug!("Not cleaning up security-scoped resource for URL: {url} on drop (manually tracked via start_accessing_security_scoped_resource)");
}
}
}
@@ -288,6 +272,7 @@ pub fn create<R: Runtime>(
path: SafeFilePath,
options: Option<BaseOptions>,
) -> CommandResult<ResourceId> {
#[cfg(target_os = "ios")]
let path_ = path.clone();
let resolved_path_handle = resolve_path(
"create",
@@ -303,13 +288,16 @@ pub fn create<R: Runtime>(
resolved_path_handle.display()
)
})?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone();
let file_handle = FileHandle::new(
let file_handle = FileHandle {
file,
PathKind::Handle(resolved_path_handle),
path: PathKind::Handle(resolved_path_handle),
#[cfg(target_os = "ios")]
path_,
#[cfg(target_os = "ios")]
app_handle,
);
};
let rid = webview
.resources_table()
.add(StdFileResource::new(file_handle));
@@ -1417,6 +1405,7 @@ fn resolve_file_in_fs<R: Runtime>(
path: SafeFilePath,
open_options: OpenOptions,
) -> CommandResult<FileHandle<R>> {
#[cfg(target_os = "ios")]
let path_ = path.clone();
let resolved_path_handle = resolve_path(
permission,
@@ -1436,13 +1425,16 @@ fn resolve_file_in_fs<R: Runtime>(
)
})?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone();
Ok(FileHandle::new(
Ok(FileHandle {
file,
PathKind::Handle(resolved_path_handle),
path: PathKind::Handle(resolved_path_handle),
#[cfg(target_os = "ios")]
path_,
#[cfg(target_os = "ios")]
app_handle,
))
})
}
#[cfg(mobile)]
@@ -1456,6 +1448,7 @@ pub fn resolve_file<R: Runtime>(
) -> CommandResult<FileHandle<R>> {
use crate::FsExt;
#[cfg(target_os = "ios")]
let path_ = path.clone();
match path {
SafeFilePath::Url(url) => {
@@ -1463,13 +1456,16 @@ pub fn resolve_file<R: Runtime>(
let file = webview
.fs()
.open(SafeFilePath::Url(url.clone()), open_options.options)?;
#[cfg(target_os = "ios")]
let app_handle = webview.app_handle().clone();
Ok(FileHandle::new(
Ok(FileHandle {
file,
PathKind::Path(resolved_path),
path: PathKind::Path(resolved_path),
#[cfg(target_os = "ios")]
path_,
#[cfg(target_os = "ios")]
app_handle,
))
})
}
SafeFilePath::Path(path) => resolve_file_in_fs(
permission,
@@ -1494,36 +1490,37 @@ pub fn resolve_path<R: Runtime>(
// On iOS, start accessing security-scoped resource if the path is a file URL
// Only if it hasn't been started already via start_accessing_security_scoped_resource
#[cfg(target_os = "ios")]
{
if let SafeFilePath::Url(url) = &path {
if url.scheme() == "file" {
use objc2_foundation::{NSString, NSURL};
if let SafeFilePath::Url(url) = &path {
if url.scheme() == "file" {
use objc2_foundation::{NSString, NSURL};
let security_scoped_resources = webview.state::<crate::SecurityScopedResources>();
let security_scoped_resources = webview.state::<crate::SecurityScopedResources>();
// Check if already active (started via start_accessing_security_scoped_resource)
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
let url_nsstring = NSString::from_str(url.as_str());
let ns_url = unsafe { NSURL::URLWithString(&url_nsstring) };
if let Some(ns_url) = ns_url {
// Start accessing the security-scoped resource
// This is required for files outside the app's sandbox (e.g., from file picker)
unsafe {
let success = ns_url.startAccessingSecurityScopedResource();
if success {
log::debug!("Started accessing security-scoped resource for URL: {} (via resolve_path)", url.as_str());
// Track it so we know to clean it up
security_scoped_resources.track_manually(url.as_str().to_string());
} else {
log::warn!("Failed to start accessing security-scoped resource for URL: {}", url.as_str());
}
// Check if already active (started via start_accessing_security_scoped_resource)
if !security_scoped_resources.is_tracked_manually(url.as_str()) {
let url_nsstring = NSString::from_str(url.as_str());
let ns_url = unsafe { NSURL::URLWithString(&url_nsstring) };
if let Some(ns_url) = ns_url {
// Start accessing the security-scoped resource
// This is required for files outside the app's sandbox (e.g., from file picker)
unsafe {
let success = ns_url.startAccessingSecurityScopedResource();
if success {
log::debug!("Started accessing security-scoped resource for URL: {} (via resolve_path)", url.as_str());
// Track it so we know to clean it up
security_scoped_resources.track_manually(url.as_str().to_string());
} else {
log::warn!(
"Failed to start accessing security-scoped resource for URL: {}",
url.as_str()
);
}
} else {
log::debug!("Failed to create NSURL from URL: {}, ignoring security-scoped resource access request", url.as_str());
}
} else {
log::debug!("Security-scoped resource already active for URL: {} (started via start_accessing_security_scoped_resource), skipping", url.as_str());
log::debug!("Failed to create NSURL from URL: {}, ignoring security-scoped resource access request", url.as_str());
}
} else {
log::debug!("Security-scoped resource already active for URL: {} (started via start_accessing_security_scoped_resource), skipping", url.as_str());
}
}
}
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.5.10]
### Dependencies
- Upgraded to `fs-js@2.5.2`
## \[2.5.9]
### Dependencies
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-http"
version = "2.5.9"
version = "2.5.10"
description = "Access an HTTP client written in Rust."
edition = { workspace = true }
authors = { workspace = true }
@@ -30,7 +30,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] }
tauri-plugin-fs = { path = "../fs", version = "2.5.1" }
tauri-plugin-fs = { path = "../fs", version = "2.5.2" }
urlpattern = "0.3"
regex = "1"
http = "1"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-http",
"version": "2.5.9",
"version": "2.5.10",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.3.6]
- [`52672616`](https://github.com/tauri-apps/plugins-workspace/commit/526726162f8ddddf9469797436123704a261df87) ([#3419](https://github.com/tauri-apps/plugins-workspace/pull/3419) by [@pascalauroboa](https://github.com/tauri-apps/plugins-workspace/../../pascalauroboa)) Expect a `NFCTag` as input and return a `ScanResponse` in the `scan` function.
## \[2.3.5]
- [`ab037b70`](https://github.com/tauri-apps/plugins-workspace/commit/ab037b70d6fb54f1ad07d5fe21c41bd1997b34dd) ([#3339](https://github.com/tauri-apps/plugins-workspace/pull/3339) by [@crazy-genius](https://github.com/tauri-apps/plugins-workspace/../../crazy-genius)) Fixed syntax error in kotlin files potentially causing issues when trying to write to tags when no prior session existed.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-nfc"
version = "2.3.5"
version = "2.3.6"
description = "Read and write NFC tags on Android and iOS."
edition = { workspace = true }
authors = { workspace = true }
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-nfc",
"version": "2.3.5",
"version": "2.3.6",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
+1
View File
@@ -47,6 +47,7 @@ impl<R: Runtime> Nfc<R> {
pub fn scan(&self, payload: ScanRequest) -> crate::Result<ScanResponse> {
self.0
.run_mobile_plugin("scan", payload)
.map(|v| ScanResponse { tag: v })
.map_err(Into::into)
}
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.3.8]
### Dependencies
- Upgraded to `fs@2.5.2`
## \[2.3.7]
### Dependencies
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-persisted-scope"
version = "2.3.7"
version = "2.3.8"
description = "Save filesystem and asset scopes and restore them when the app is reopened."
authors = { workspace = true }
license = { workspace = true }
@@ -23,7 +23,7 @@ log = { workspace = true }
thiserror = { workspace = true }
aho-corasick = "1"
bincode = "1"
tauri-plugin-fs = { path = "../fs", version = "2.5.1" }
tauri-plugin-fs = { path = "../fs", version = "2.5.2" }
[features]
protocol-asset = ["tauri/protocol-asset"]
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.4.4]
### Dependencies
- Upgraded to `deep-link@2.4.10`
## \[2.4.3]
- [`d1573877`](https://github.com/tauri-apps/plugins-workspace/commit/d1573877226e609461761aa538cd0ca4f24d22be) ([#3466](https://github.com/tauri-apps/plugins-workspace/pull/3466) by [@bajoca05](https://github.com/tauri-apps/plugins-workspace/../../bajoca05)) Fix blocked thread on the single-instance plugin for MacOS: replace standard `UnixListener` with `tokio::net::UnixListener`, so the task can yield.
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-single-instance"
version = "2.4.3"
version = "2.4.4"
description = "Ensure a single instance of your tauri app is running."
authors = { workspace = true }
license = { workspace = true }
@@ -22,7 +22,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.9", optional = true }
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.10", optional = true }
semver = { version = "1", optional = true }
tokio = { version = "1", features = ["net"] }
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.11.0]
- [`ab7489c9`](https://github.com/tauri-apps/plugins-workspace/commit/ab7489c964977c2cc19cdc303bd8f7142d5f5a2c) ([#3299](https://github.com/tauri-apps/plugins-workspace/pull/3299) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) On Windows, add a new option `restartAfterInstall`/`restart_after_install` to install an update without the installer re-launching the app
## \[2.10.1]
- [`31ab6f8d`](https://github.com/tauri-apps/plugins-workspace/commit/31ab6f8d2466d86c80b1d70510c0400ce2cdcb0a) ([#3285](https://github.com/tauri-apps/plugins-workspace/pull/3285) by [@hrzlgnm](https://github.com/tauri-apps/plugins-workspace/../../hrzlgnm)) fix: preserve file extension of updater package, otherwise users may get confused when presented with a sudo dialog suggesting to install a file with the extension `.rpm` using `dpkg -i`
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-updater"
version = "2.10.1"
version = "2.11.0"
description = "In-app updates for Tauri applications."
edition = { workspace = true }
authors = { workspace = true }
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(t){"use strict";function e(t,e,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(t):n?n.value:e.get(t)}function s(t,e,s,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var n,i,a,r,o;"function"==typeof SuppressedError&&SuppressedError;const d="__TAURI_TO_IPC_KEY__";class c{constructor(t){n.set(this,void 0),i.set(this,0),a.set(this,[]),r.set(this,void 0),s(this,n,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}(t=>{const o=t.index;if("end"in t)return void(o==e(this,i,"f")?this.cleanupCallback():s(this,r,o));const d=t.message;if(o==e(this,i,"f")){for(e(this,n,"f").call(this,d),s(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,a,"f");){const t=e(this,a,"f")[e(this,i,"f")];e(this,n,"f").call(this,t),delete e(this,a,"f")[e(this,i,"f")],s(this,i,e(this,i,"f")+1)}e(this,i,"f")===e(this,r,"f")&&this.cleanupCallback()}else e(this,a,"f")[o]=d})}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){s(this,n,t)}get onmessage(){return e(this,n,"f")}[(n=new WeakMap,i=new WeakMap,a=new WeakMap,r=new WeakMap,d)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[d]()}}async function l(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}class h{get rid(){return e(this,o,"f")}constructor(t){o.set(this,void 0),s(this,o,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}o=new WeakMap;class u extends h{constructor(t){super(t.rid),this.available=!0,this.currentVersion=t.currentVersion,this.version=t.version,this.date=t.date,this.body=t.body,this.rawJson=t.rawJson}async download(t,e){_(e);const s=new c;t&&(s.onmessage=t);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...e});this.downloadedBytes=new h(n)}async install(t){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid,...t}),this.downloadedBytes=void 0}async downloadAndInstall(t,e){_(e);const s=new c;t&&(s.onmessage=t),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...e})}async close(){await(this.downloadedBytes?.close()),await super.close()}}function _(t){t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries()))}return t.Update=u,t.check=async function(t){_(t);const e=await l("plugin:updater|check",{...t});return e?new u(e):null},t}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
+12 -3
View File
@@ -40,6 +40,14 @@ interface DownloadOptions {
timeout?: number
}
/** Options used when installing an update */
interface InstallOptions {
/**
* If the Windows installer should restart the app after installed, default is `true`
*/
restartAfterInstall?: boolean
}
interface UpdateMetadata {
rid: number
currentVersion: string
@@ -95,14 +103,15 @@ class Update extends Resource {
}
/** Install downloaded updater package */
async install(): Promise<void> {
async install(options?: InstallOptions): Promise<void> {
if (!this.downloadedBytes) {
throw new Error('Update.install called before Update.download')
}
await invoke('plugin:updater|install', {
updateRid: this.rid,
bytesRid: this.downloadedBytes.rid
bytesRid: this.downloadedBytes.rid,
...options
})
// Don't need to call close, we did it in rust side already
@@ -112,7 +121,7 @@ class Update extends Resource {
/** Downloads the updater package and installs it */
async downloadAndInstall(
onEvent?: (progress: DownloadEvent) => void,
options?: DownloadOptions
options?: DownloadOptions & InstallOptions
): Promise<void> {
convertToRustHeaders(options)
const channel = new Channel<DownloadEvent>()
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-updater",
"version": "2.10.1",
"version": "2.11.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
+16 -1
View File
@@ -142,12 +142,22 @@ pub(crate) async fn install<R: Runtime>(
webview: Webview<R>,
update_rid: ResourceId,
bytes_rid: ResourceId,
restart_after_install: Option<bool>,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(update_rid)?;
let bytes = webview
.resources_table()
.get::<DownloadedBytes>(bytes_rid)?;
update.install(&bytes.0)?;
if let Some(restart_after_install) = restart_after_install {
let update = (*update).clone();
update
.restart_after_install(restart_after_install)
.install(&bytes.0)?;
} else {
update.install(&bytes.0)?;
}
let _ = webview.resources_table().close(bytes_rid);
Ok(())
}
@@ -159,6 +169,7 @@ pub(crate) async fn download_and_install<R: Runtime>(
on_event: Channel<DownloadEvent>,
headers: Option<Vec<(String, String)>>,
timeout: Option<u64>,
restart_after_install: Option<bool>,
) -> Result<()> {
let update = webview.resources_table().get::<Update>(rid)?;
@@ -176,6 +187,10 @@ pub(crate) async fn download_and_install<R: Runtime>(
update.timeout = Some(Duration::from_millis(timeout));
}
if let Some(restart_after_install) = restart_after_install {
update = update.restart_after_install(restart_after_install);
}
let mut first_chunk = true;
update
+17 -2
View File
@@ -32,17 +32,30 @@ impl WindowsUpdateInstallMode {
}
}
#[cfg(windows)]
pub(crate) fn msi_restart_after_install_args(&self) -> &'static [&'static str] {
&["AUTOLAUNCHAPP=True"]
}
/// Returns the associated nsis arguments.
pub fn nsis_args(&self) -> &'static [&'static str] {
// `/P`: Passive
// `/S`: Silent
// `/R`: Restart
match self {
Self::Passive => &["/P", "/R"],
Self::Quiet => &["/S", "/R"],
Self::Passive => &["/P"],
Self::Quiet => &["/S"],
_ => &[],
}
}
#[cfg(windows)]
pub(crate) fn nsis_restart_after_install_args(&self) -> &'static [&'static str] {
match self {
Self::BasicUi => &[],
_ => &["/R"],
}
}
}
impl Display for WindowsUpdateInstallMode {
@@ -63,6 +76,8 @@ impl Display for WindowsUpdateInstallMode {
#[serde(rename_all = "camelCase")]
pub struct WindowsConfig {
/// Additional arguments given to the NSIS or WiX installer.
///
/// Note: this applies to both WiX and NSIS installers
#[serde(
default,
alias = "installer-args",
+3 -3
View File
@@ -83,9 +83,9 @@ impl<R: Runtime, T: Manager<R>> UpdaterExt<R> for T {
builder = builder.target(target);
}
let args = self.env().args_os;
if !args.is_empty() {
builder = builder.current_exe_args(args);
#[cfg(windows)]
{
builder = builder.current_exe_args(self.env().args_os);
}
builder.version_comparator = version_comparator.clone();
+212 -148
View File
@@ -129,16 +129,34 @@ impl RemoteRelease {
pub type OnBeforeExit = Arc<dyn Fn() + Send + Sync + 'static>;
pub type OnBeforeRequest = Arc<dyn Fn(ClientBuilder) -> ClientBuilder + Send + Sync + 'static>;
pub type VersionComparator = Arc<dyn Fn(Version, RemoteRelease) -> bool + Send + Sync>;
#[cfg(target_os = "macos")]
type MainThreadClosure = Box<dyn FnOnce() + Send + Sync + 'static>;
type RunOnMainThread =
Box<dyn Fn(MainThreadClosure) -> std::result::Result<(), tauri::Error> + Send + Sync + 'static>;
#[cfg(target_os = "macos")]
type RunOnMainThread = Arc<dyn Fn(MainThreadClosure) -> tauri::Result<()> + Send + Sync + 'static>;
// TODO: Move more fields to this in v3 if we can mark those fields non `pub`
/// Updater context shared between [`UpdaterBuilder`], [`Updater`] and [`Update`]
#[derive(Clone)]
struct UpdaterContext {
config: Config,
configure_client: Option<OnBeforeRequest>,
#[cfg(target_os = "macos")]
run_on_main_thread: RunOnMainThread,
/// App name, used for creating named tempfiles
#[cfg(windows)]
app_name: String,
#[cfg(windows)]
installer_args: Vec<OsString>,
#[cfg(windows)]
current_exe_args: Vec<OsString>,
#[cfg(windows)]
on_before_exit: Option<OnBeforeExit>,
#[cfg(windows)]
restart_after_install: bool,
}
pub struct UpdaterBuilder {
#[allow(dead_code)]
run_on_main_thread: RunOnMainThread,
app_name: String,
current_version: Version,
config: Config,
pub(crate) version_comparator: Option<VersionComparator>,
executable_path: Option<PathBuf>,
target: Option<String>,
@@ -147,27 +165,38 @@ pub struct UpdaterBuilder {
timeout: Option<Duration>,
proxy: Option<Url>,
no_proxy: bool,
installer_args: Vec<OsString>,
current_exe_args: Vec<OsString>,
on_before_exit: Option<OnBeforeExit>,
configure_client: Option<OnBeforeRequest>,
context: UpdaterContext,
}
impl UpdaterBuilder {
pub(crate) fn new<R: Runtime>(app: &AppHandle<R>, config: crate::Config) -> Self {
let app_ = app.clone();
let run_on_main_thread = move |f| app_.run_on_main_thread(f);
#[cfg(target_os = "macos")]
let run_on_main_thread = {
let app_ = app.clone();
Arc::new(move |f| app_.run_on_main_thread(f))
};
Self {
run_on_main_thread: Box::new(run_on_main_thread),
installer_args: config
.windows
.as_ref()
.map(|w| w.installer_args.clone())
.unwrap_or_default(),
current_exe_args: Vec::new(),
app_name: app.package_info().name.clone(),
context: UpdaterContext {
#[cfg(windows)]
installer_args: config
.windows
.as_ref()
.map(|w| w.installer_args.clone())
.unwrap_or_default(),
config,
configure_client: None,
#[cfg(target_os = "macos")]
run_on_main_thread,
#[cfg(windows)]
app_name: app.package_info().name.clone(),
#[cfg(windows)]
current_exe_args: Vec::new(),
#[cfg(windows)]
on_before_exit: None,
#[cfg(windows)]
restart_after_install: true,
},
current_version: app.package_info().version.clone(),
config,
version_comparator: None,
executable_path: None,
target: None,
@@ -176,8 +205,6 @@ impl UpdaterBuilder {
timeout: None,
proxy: None,
no_proxy: false,
on_before_exit: None,
configure_client: None,
}
}
@@ -197,7 +224,7 @@ impl UpdaterBuilder {
pub fn endpoints(mut self, endpoints: Vec<Url>) -> Result<Self> {
crate::config::validate_endpoints(
&endpoints,
self.config.dangerous_insecure_transport_protocol,
self.context.config.dangerous_insecure_transport_protocol,
)?;
self.endpoints.replace(endpoints);
@@ -251,26 +278,40 @@ impl UpdaterBuilder {
}
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
self.config.pubkey = pubkey.into();
self.context.config.pubkey = pubkey.into();
self
}
/// Adds an argument to pass to the Windows installer.
///
/// Note: this applies to both WiX and NSIS installers
#[cfg_attr(not(windows), allow(unused))]
pub fn installer_arg<S>(mut self, arg: S) -> Self
where
S: Into<OsString>,
{
self.installer_args.push(arg.into());
#[cfg(windows)]
{
self.context.installer_args.push(arg.into());
}
self
}
/// Adds multiple arguments to pass to the Windows installer.
///
/// Note: this applies to both WiX and NSIS installers
#[cfg_attr(not(windows), allow(unused))]
pub fn installer_args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.installer_args.extend(args.into_iter().map(Into::into));
#[cfg(windows)]
{
self.context
.installer_args
.extend(args.into_iter().map(Into::into));
}
self
}
@@ -280,14 +321,32 @@ impl UpdaterBuilder {
/// [`Self::installer_arg`], [`crate::Builder::installer_arg`]
/// and the `plugins > updater > windows > installerArgs` config,
/// not the ones managed by us (e.g. `/UPDATER` flag passed to the NSIS installer)
#[cfg_attr(not(windows), allow(unused))]
pub fn clear_installer_args(mut self) -> Self {
self.installer_args.clear();
#[cfg(windows)]
{
self.context.installer_args.clear();
}
self
}
/// Function to run before we run the installer and exit the app through `std::process::exit(0)` on Windows
#[cfg_attr(not(windows), allow(unused))]
pub fn on_before_exit<F: Fn() + Send + Sync + 'static>(mut self, f: F) -> Self {
self.on_before_exit.replace(Arc::new(f));
#[cfg(windows)]
{
self.context.on_before_exit.replace(Arc::new(f));
}
self
}
/// If the Windows installer should restart the app after installed, default is `true`
#[cfg_attr(not(windows), allow(unused))]
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
#[cfg(windows)]
{
self.context.restart_after_install = restart_after_install;
}
self
}
@@ -299,14 +358,14 @@ impl UpdaterBuilder {
mut self,
f: F,
) -> Self {
self.configure_client.replace(Arc::new(f));
self.context.configure_client.replace(Arc::new(f));
self
}
pub fn build(self) -> Result<Updater> {
let endpoints = self
.endpoints
.unwrap_or_else(|| self.config.endpoints.clone());
.unwrap_or_else(|| self.context.config.endpoints.clone());
if endpoints.is_empty() {
return Err(Error::EmptyEndpoints);
@@ -324,44 +383,36 @@ impl UpdaterBuilder {
};
Ok(Updater {
run_on_main_thread: Arc::new(self.run_on_main_thread),
config: self.config,
app_name: self.app_name,
current_version: self.current_version,
version_comparator: self.version_comparator,
timeout: self.timeout,
proxy: self.proxy,
no_proxy: self.no_proxy,
endpoints,
installer_args: self.installer_args,
current_exe_args: self.current_exe_args,
arch,
target: self.target,
headers: self.headers,
extract_path,
on_before_exit: self.on_before_exit,
configure_client: self.configure_client,
context: self.context.clone(),
})
}
}
#[cfg(windows)]
impl UpdaterBuilder {
pub(crate) fn current_exe_args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<OsString>,
{
self.current_exe_args
self.context
.current_exe_args
.extend(args.into_iter().map(Into::into));
self
}
}
pub struct Updater {
#[allow(dead_code)]
run_on_main_thread: Arc<RunOnMainThread>,
config: Config,
app_name: String,
current_version: Version,
version_comparator: Option<VersionComparator>,
timeout: Option<Duration>,
@@ -374,12 +425,7 @@ pub struct Updater {
target: Option<String>,
headers: HeaderMap,
extract_path: PathBuf,
on_before_exit: Option<OnBeforeExit>,
configure_client: Option<OnBeforeRequest>,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
current_exe_args: Vec<OsString>,
context: UpdaterContext,
}
impl Updater {
@@ -449,10 +495,10 @@ impl Updater {
}
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if self.config.dangerous_accept_invalid_certs {
if self.context.config.dangerous_accept_invalid_certs {
request = request.danger_accept_invalid_certs(true);
}
if self.config.dangerous_accept_invalid_hostnames {
if self.context.config.dangerous_accept_invalid_hostnames {
request = request.danger_accept_invalid_hostnames(true);
}
if let Some(timeout) = self.timeout {
@@ -467,7 +513,7 @@ impl Updater {
request = request.proxy(proxy);
}
if let Some(ref configure_client) = self.configure_client {
if let Some(ref configure_client) = self.context.configure_client {
request = configure_client(request);
}
@@ -537,10 +583,6 @@ impl Updater {
let update = if should_update {
Some(Update {
run_on_main_thread: self.run_on_main_thread.clone(),
config: self.config.clone(),
on_before_exit: self.on_before_exit.clone(),
app_name: self.app_name.clone(),
current_version: self.current_version.to_string(),
target: target.to_owned(),
extract_path: self.extract_path.clone(),
@@ -554,9 +596,7 @@ impl Updater {
proxy: self.proxy.clone(),
no_proxy: self.no_proxy,
headers: self.headers.clone(),
installer_args: self.installer_args.clone(),
current_exe_args: self.current_exe_args.clone(),
configure_client: self.configure_client.clone(),
context: self.context.clone(),
})
} else {
None
@@ -600,11 +640,6 @@ impl Updater {
#[derive(Clone)]
pub struct Update {
#[allow(dead_code)]
run_on_main_thread: Arc<RunOnMainThread>,
config: Config,
#[allow(unused)]
on_before_exit: Option<OnBeforeExit>,
/// Update description
pub body: Option<String>,
/// Version used to check for update
@@ -633,14 +668,7 @@ pub struct Update {
/// Extract path
#[allow(unused)]
extract_path: PathBuf,
/// App name, used for creating named tempfiles on Windows
#[allow(unused)]
app_name: String,
#[allow(unused)]
installer_args: Vec<OsString>,
#[allow(unused)]
current_exe_args: Vec<OsString>,
configure_client: Option<OnBeforeRequest>,
context: UpdaterContext,
}
impl Resource for Update {}
@@ -661,10 +689,10 @@ impl Update {
}
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if self.config.dangerous_accept_invalid_certs {
if self.context.config.dangerous_accept_invalid_certs {
request = request.danger_accept_invalid_certs(true);
}
if self.config.dangerous_accept_invalid_hostnames {
if self.context.config.dangerous_accept_invalid_hostnames {
request = request.danger_accept_invalid_hostnames(true);
}
if let Some(timeout) = self.timeout {
@@ -676,7 +704,7 @@ impl Update {
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
if let Some(ref configure_client) = self.configure_client {
if let Some(ref configure_client) = self.context.configure_client {
request = configure_client(request);
}
let response = request
@@ -709,7 +737,7 @@ impl Update {
}
on_download_finish();
verify_signature(&buffer, &self.signature, &self.config.pubkey)?;
verify_signature(&buffer, &self.signature, &self.context.config.pubkey)?;
Ok(buffer)
}
@@ -733,6 +761,16 @@ impl Update {
fn install_inner(&self, _bytes: &[u8]) -> Result<()> {
Ok(())
}
/// Whether the Windows installer should restart the app after installed, default is `true`
#[cfg_attr(not(windows), allow(unused))]
pub fn restart_after_install(mut self, restart_after_install: bool) -> Self {
#[cfg(windows)]
{
self.context.restart_after_install = restart_after_install;
}
self
}
}
#[cfg(windows)]
@@ -785,7 +823,6 @@ impl Update {
/// │ └──[AppName]_[version]_x64-setup.exe # NSIS installer
/// └── ...
fn install_inner(&self, bytes: &[u8]) -> Result<()> {
use std::iter::once;
use windows_sys::{
w,
Win32::UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
@@ -793,48 +830,7 @@ impl Update {
let updater_type = self.extract(bytes)?;
let install_mode = self.config.install_mode();
let current_args = &self.current_exe_args()[1..];
let msi_args;
let nsis_args;
let installer_args: Vec<&OsStr> = match &updater_type {
WindowsUpdaterType::Nsis { .. } => {
nsis_args = current_args
.iter()
.map(escape_nsis_current_exe_arg)
.collect::<Vec<_>>();
install_mode
.nsis_args()
.iter()
.map(OsStr::new)
.chain(once(OsStr::new("/UPDATE")))
.chain(once(OsStr::new("/ARGS")))
.chain(nsis_args.iter().map(OsStr::new))
.chain(self.installer_args())
.collect()
}
WindowsUpdaterType::Msi { path, .. } => {
let escaped_args = current_args
.iter()
.map(escape_msi_property_arg)
.collect::<Vec<_>>()
.join(" ");
msi_args = OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""));
[OsStr::new("/i"), path.as_os_str()]
.into_iter()
.chain(install_mode.msiexec_args().iter().map(OsStr::new))
.chain(once(OsStr::new("/promptrestart")))
.chain(self.installer_args())
.chain(once(OsStr::new("AUTOLAUNCHAPP=True")))
.chain(once(msi_args.as_os_str()))
.collect()
}
};
if let Some(on_before_exit) = self.on_before_exit.as_ref() {
if let Some(on_before_exit) = self.context.on_before_exit.as_ref() {
log::debug!("running on_before_exit hook");
on_before_exit();
}
@@ -846,9 +842,11 @@ impl Update {
|p| OsString::from(format!("{p}\\System32\\msiexec.exe")),
),
};
let file = encode_wide(file);
let parameters = self.updater_parameters(&updater_type);
let parameters = installer_args.join(OsStr::new(" "));
log::debug!("Executing updater {file:?} with parameters: {parameters:?}");
let file = encode_wide(file);
let parameters = encode_wide(parameters);
unsafe {
@@ -865,18 +863,72 @@ impl Update {
std::process::exit(0);
}
fn installer_args(&self) -> Vec<&OsStr> {
self.installer_args
.iter()
.map(OsStr::new)
.collect::<Vec<_>>()
fn updater_parameters(&self, updater_type: &WindowsUpdaterType) -> OsString {
let install_mode = self.context.config.install_mode();
let current_args = &self.context.current_exe_args[1..];
match updater_type {
WindowsUpdaterType::Nsis { .. } => {
let mut installer_args: Vec<&OsStr> = Vec::new();
installer_args.extend(install_mode.nsis_args().iter().map(OsStr::new));
installer_args.push(OsStr::new("/UPDATE"));
let nsis_current_exe_arg;
if self.context.restart_after_install {
nsis_current_exe_arg = current_args
.iter()
.map(escape_nsis_current_exe_arg)
.collect::<Vec<_>>();
installer_args.extend(
install_mode
.nsis_restart_after_install_args()
.iter()
.map(OsStr::new),
);
installer_args.push(OsStr::new("/ARGS"));
installer_args.extend(nsis_current_exe_arg.iter().map(OsStr::new));
}
installer_args.extend(self.installer_args());
installer_args.join(OsStr::new(" "))
}
WindowsUpdaterType::Msi { path, .. } => {
let mut installer_args: Vec<&OsStr> = vec![OsStr::new("/i"), path.as_os_str()];
installer_args.extend(install_mode.msiexec_args().iter().map(OsStr::new));
installer_args.push(OsStr::new("/promptrestart"));
installer_args.extend(self.installer_args());
let msi_current_exe_arg;
if self.context.restart_after_install {
msi_current_exe_arg = format!(
"LAUNCHAPPARGS=\"{}\"",
current_args
.iter()
.map(escape_msi_property_arg)
.collect::<Vec<_>>()
.join(" ")
);
installer_args.extend(
install_mode
.msi_restart_after_install_args()
.iter()
.map(OsStr::new),
);
installer_args.push(OsStr::new(&msi_current_exe_arg));
}
installer_args.join(OsStr::new(" "))
}
}
}
fn current_exe_args(&self) -> Vec<&OsStr> {
self.current_exe_args
.iter()
.map(OsStr::new)
.collect::<Vec<_>>()
fn installer_args(
&self,
) -> std::iter::Map<std::slice::Iter<'_, OsString>, fn(&OsString) -> &OsStr> {
self.context.installer_args.iter().map(OsStr::new)
}
fn extract(&self, bytes: &[u8]) -> Result<WindowsUpdaterType> {
@@ -890,7 +942,10 @@ impl Update {
fn make_temp_dir(&self) -> Result<PathBuf> {
Ok(tempfile::Builder::new()
.prefix(&format!("{}-{}-updater-", self.app_name, self.version))
.prefix(&format!(
"{}-{}-updater-",
self.context.app_name, self.version
))
.tempdir()?
.keep())
}
@@ -938,7 +993,10 @@ impl Update {
let temp_dir = self.make_temp_dir()?;
let mut temp_file = tempfile::Builder::new()
.prefix(&format!("{}-{}-installer", self.app_name, self.version))
.prefix(&format!(
"{}-{}-installer",
self.context.app_name, self.version
))
.suffix(ext)
.rand_bytes(0)
.tempfile_in(temp_dir)?;
@@ -1277,7 +1335,7 @@ impl Update {
);
let (tx, rx) = std::sync::mpsc::channel();
let res = (self.run_on_main_thread)(Box::new(move || {
let res = (self.context.run_on_main_thread)(Box::new(move || {
let mut script =
osakit::Script::new_from_source(osakit::Language::AppleScript, &apple_script);
script.compile().expect("invalid AppleScript");
@@ -1450,7 +1508,7 @@ where
}
// Validate signature
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<bool> {
fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Result<()> {
// we need to convert the pub key
let pub_key_decoded = base64_to_string(pub_key)?;
let public_key = PublicKey::decode(&pub_key_decoded)?;
@@ -1459,7 +1517,7 @@ fn verify_signature(data: &[u8], release_signature: &str, pub_key: &str) -> Resu
// Validate signature or bail out
public_key.verify(data, &signature, true)?;
Ok(true)
Ok(())
}
fn base64_to_string(base64_string: &str) -> Result<String> {
@@ -1498,25 +1556,31 @@ impl PathExt for PathBuf {
// adapted from https://github.com/rust-lang/rust/blob/1c047506f94cd2d05228eb992b0a6bbed1942349/library/std/src/sys/args/windows.rs#L174
#[cfg(windows)]
fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String {
let arg = arg.to_string_lossy();
let mut cmd: Vec<char> = Vec::new();
fn escape_nsis_current_exe_arg(arg: impl AsRef<OsStr>) -> OsString {
use std::os::windows::ffi::{OsStrExt, OsStringExt};
let arg = arg.as_ref();
let mut cmd: Vec<u16> = Vec::new();
// compared to std we additionally escape `/` so that nsis won't interpret them as a beginning of an nsis argument.
let quote = arg.chars().any(|c| c == ' ' || c == '\t' || c == '/') || arg.is_empty();
let quote = arg
.as_encoded_bytes()
.iter()
.any(|c| *c == b' ' || *c == b'\t' || *c == b'/')
|| arg.is_empty();
let escape = true;
if quote {
cmd.push('"');
cmd.push('"' as u16);
}
let mut backslashes: usize = 0;
for x in arg.chars() {
for x in arg.encode_wide() {
if escape {
if x == '\\' {
if x == '\\' as u16 {
backslashes += 1;
} else {
if x == '"' {
if x == '"' as u16 {
// Add n+1 backslashes to total 2n+1 before internal '"'.
cmd.extend((0..=backslashes).map(|_| '\\'));
cmd.extend((0..=backslashes).map(|_| '\\' as u16));
}
backslashes = 0;
}
@@ -1525,10 +1589,10 @@ fn escape_nsis_current_exe_arg(arg: &&OsStr) -> String {
}
if quote {
// Add n backslashes to total 2n before ending '"'.
cmd.extend((0..backslashes).map(|_| '\\'));
cmd.push('"');
cmd.extend((0..backslashes).map(|_| '\\' as u16));
cmd.push('"' as u16);
}
cmd.into_iter().collect()
OsString::from_wide(&cmd)
}
#[cfg(windows)]
@@ -6,8 +6,7 @@
"endpoints": ["http://localhost:3007"],
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
"windows": {
"installMode": "quiet",
"installerArgs": ["/NS"]
"installMode": "quiet"
}
}
},
@@ -46,7 +46,7 @@ struct Update {
platforms: HashMap<String, PlatformUpdate>,
}
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) {
fn build_app(cwd: &Path, config: &Config, target: Option<BundleTarget>) {
let mut command = Command::new("cargo");
command
.args(["tauri", "build", "--verbose"])
@@ -56,17 +56,10 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
.current_dir(cwd);
#[cfg(target_os = "linux")]
command.args(["--bundles", target.name()]);
#[cfg(target_os = "macos")]
command.args(["--bundles", target.name()]);
if bundle_updater {
#[cfg(windows)]
command.args(["--bundles", "msi", "nsis"]);
if let Some(target) = target {
command.arg("--bundles").arg(target.name());
} else {
#[cfg(windows)]
command.args(["--bundles", target.name()]);
command.arg("--no-bundle");
}
let status = command
@@ -318,7 +311,7 @@ fn update_app() {
{
// bundle app update
config.version = "1.0.0";
build_app(&manifest_dir, &config, true, BundleTarget::default());
build_app(&manifest_dir, &config, Some(bundle_target));
let bundle_updater_ext = if v1_compatible {
out_bundle_path
@@ -401,7 +394,7 @@ fn update_app() {
config.version = "0.1.0";
// bundle initial app version
build_app(&manifest_dir, &config, false, bundle_target);
build_app(&manifest_dir, &config, None);
for expected_exit_code in status_checks {
let mut binary_cmd = if cfg!(windows) {
+3 -3
View File
@@ -3794,7 +3794,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -4137,9 +4137,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.10"
version = "0.24.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45"
checksum = "4a2a144c3ab5e83e04724bc8e67cea552ffae413185fda459fafdae173fd985d"
dependencies = [
"base64 0.13.1",
"block",
+45 -10
View File
@@ -66,8 +66,8 @@ importers:
specifier: '2'
version: 2.7.1
'@tauri-apps/plugin-fs':
specifier: workspace:*
version: link:../../plugins/fs
specifier: '2'
version: 2.5.1
'@tauri-apps/plugin-geolocation':
specifier: workspace:*
version: link:../../plugins/geolocation
@@ -78,11 +78,11 @@ importers:
specifier: workspace:*
version: link:../../plugins/haptics
'@tauri-apps/plugin-http':
specifier: workspace:*
version: link:../../plugins/http
specifier: '2'
version: 2.5.9
'@tauri-apps/plugin-nfc':
specifier: workspace:*
version: link:../../plugins/nfc
specifier: '2'
version: 2.3.5
'@tauri-apps/plugin-notification':
specifier: workspace:*
version: link:../../plugins/notification
@@ -102,8 +102,8 @@ importers:
specifier: '2'
version: 2.4.3
'@tauri-apps/plugin-updater':
specifier: workspace:*
version: link:../../plugins/updater
specifier: '2'
version: 2.10.1
'@tauri-apps/plugin-upload':
specifier: workspace:*
version: link:../../plugins/upload
@@ -182,8 +182,8 @@ importers:
specifier: ^2.11.0
version: 2.11.0
'@tauri-apps/plugin-deep-link':
specifier: workspace:*
version: link:../..
specifier: '2'
version: 2.4.9
devDependencies:
'@tauri-apps/cli':
specifier: 2.11.4
@@ -1123,12 +1123,27 @@ packages:
engines: {node: '>= 10'}
hasBin: true
'@tauri-apps/plugin-deep-link@2.4.9':
resolution: {integrity: sha512-u0SKOUHnJ1wqeqXsDFq2+kASCBj9xxbG0g9XZWPy9SOmU4wXtp6b/wiYpm6oH6/5fBTQsLqnLhIvqLBRpgHJlA==}
'@tauri-apps/plugin-dialog@2.7.1':
resolution: {integrity: sha512-OK1UBXYt+ojcmxMktzzuyonYIFta8CmAASpX+CA+DTGK24KlHjhYI6x2iOJ/TjZF4N7/ACK1oFmEOjIY9IhzOQ==}
'@tauri-apps/plugin-fs@2.5.1':
resolution: {integrity: sha512-9Lz+Jopp6QyeEWhlpkMx4R/+P9HgR+AVAI4vOZhlT8Xaymtz8iVI/Ov984/XTqgJz/5gz5NretqPB/XEMS3NhQ==}
'@tauri-apps/plugin-http@2.5.9':
resolution: {integrity: sha512-lCiY0+vs4HvIUSvZrBs8TC3TiCB0MOPRmiUjTq4prW7SlcJE2jdLeT6KBsJrT9Tlplufl7W1pY6SFAO3gCWxDA==}
'@tauri-apps/plugin-nfc@2.3.5':
resolution: {integrity: sha512-6w6tY9maiA+YInVS3i5sEPthetZm9/9RR6nB4xAn4BbLgkt+Udd5LWBZxAlfluvKJZn7Svnkd0fVm7juf3YNXw==}
'@tauri-apps/plugin-store@2.4.3':
resolution: {integrity: sha512-9LWPj9yMphRi9czEtUv87XHbl1b6xgd9EXpPrUnq6nG7+nbtoF84d4Kwz9xhAv/Hf30sr58pq7EOlyI936y8qw==}
'@tauri-apps/plugin-updater@2.10.1':
resolution: {integrity: sha512-NFYMg+tWOZPJdzE/PpFj2qfqwAWwNS3kXrb1tm1gnBJ9mYzZ4WDRrwy8udzWoAnfGCHLuePNLY1WVCNHnh3eRA==}
'@tybys/wasm-util@0.10.2':
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
@@ -2475,14 +2490,34 @@ snapshots:
'@tauri-apps/cli-win32-ia32-msvc': 2.11.4
'@tauri-apps/cli-win32-x64-msvc': 2.11.4
'@tauri-apps/plugin-deep-link@2.4.9':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-dialog@2.7.1':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-fs@2.5.1':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-http@2.5.9':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-nfc@2.3.5':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-store@2.4.3':
dependencies:
'@tauri-apps/api': 2.11.0
'@tauri-apps/plugin-updater@2.10.1':
dependencies:
'@tauri-apps/api': 2.11.0
'@tybys/wasm-util@0.10.2':
dependencies:
tslib: 2.8.1