diff --git a/.changes/deep-link-fix-different-exec.md b/.changes/deep-link-fix-different-exec.md
new file mode 100644
index 000000000..1f5b5cda8
--- /dev/null
+++ b/.changes/deep-link-fix-different-exec.md
@@ -0,0 +1,6 @@
+---
+"deep-link": patch
+"deep-link-js": patch
+---
+
+Fix Exec= field in desktop handler if executable path changes
diff --git a/.changes/enhance-fs-error-message.md b/.changes/enhance-fs-error-message.md
new file mode 100644
index 000000000..540e65a50
--- /dev/null
+++ b/.changes/enhance-fs-error-message.md
@@ -0,0 +1,6 @@
+---
+"fs": patch
+"fs-js": patch
+---
+
+Enhance error messages.
diff --git a/.changes/geolocation-android-timeout.md b/.changes/geolocation-android-timeout.md
new file mode 100644
index 000000000..dc3d952e2
--- /dev/null
+++ b/.changes/geolocation-android-timeout.md
@@ -0,0 +1,6 @@
+---
+geolocation: patch
+geolocation-js: patch
+---
+
+On Android, use the `timeout` value for `setMinUpdateIntervalMillis`, `setMaxUpdateDelayMillis` and `setIntervalMillis` instead of just `minUpdateInterval`.
diff --git a/.changes/nfc-close-session.md b/.changes/nfc-close-session.md
deleted file mode 100644
index 70735b34f..000000000
--- a/.changes/nfc-close-session.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-nfc: patch
-nfc-js: patch
----
-
-On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
diff --git a/.changes/opener-same-origin-link.md b/.changes/opener-same-origin-link.md
new file mode 100644
index 000000000..5bed71c4a
--- /dev/null
+++ b/.changes/opener-same-origin-link.md
@@ -0,0 +1,6 @@
+---
+"opener": patch
+"opener-js": patch
+---
+
+Fix opener doesn't open same origin links in the browser
diff --git a/.changes/store-defaults-js.md b/.changes/store-defaults-js.md
deleted file mode 100644
index 122ef8195..000000000
--- a/.changes/store-defaults-js.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-store: minor
-store-js: minor
----
-
-Allow setting defaults from the JavaScript API
diff --git a/.changes/store-load-override-defaults.md b/.changes/store-load-override-defaults.md
deleted file mode 100644
index 1fb8376e0..000000000
--- a/.changes/store-load-override-defaults.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-store: minor
-store-js: minor
----
-
-Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
diff --git a/.changes/updater-new-bundle-support.md b/.changes/updater-new-bundle-support.md
new file mode 100644
index 000000000..99cf2e889
--- /dev/null
+++ b/.changes/updater-new-bundle-support.md
@@ -0,0 +1,6 @@
+---
+"updater": minor
+"updater-js": minor
+---
+
+Updater plugin now supports all bundle types: Deb, Rpm and AppImage for Linux; NSiS, MSI for Windows.
diff --git a/.gitignore b/.gitignore
index 38051f657..41022b01c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,4 +58,4 @@ pids
.idea
debug.log
TODO.md
-.aider*
+.aider.*
diff --git a/Cargo.lock b/Cargo.lock
index 7d504d866..afef03afa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,7 +220,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "api"
-version = "2.0.32"
+version = "2.0.36"
dependencies = [
"log",
"serde",
@@ -903,7 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
dependencies = [
"serde",
- "toml",
+ "toml 0.8.20",
]
[[package]]
@@ -1608,9 +1608,9 @@ dependencies = [
[[package]]
name = "dlopen2"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6"
+checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff"
dependencies = [
"dlopen2_derive",
"libc",
@@ -1770,7 +1770,7 @@ dependencies = [
"cc",
"memchr",
"rustc_version",
- "toml",
+ "toml 0.8.20",
"vswhom",
"winreg 0.52.0",
]
@@ -3648,9 +3648,9 @@ dependencies = [
[[package]]
name = "muda"
-version = "0.16.1"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492"
+checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a"
dependencies = [
"crossbeam-channel",
"dpi",
@@ -3664,7 +3664,7 @@ dependencies = [
"png",
"serde",
"thiserror 2.0.12",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -4140,6 +4140,17 @@ dependencies = [
"objc2-foundation 0.3.0",
]
+[[package]]
+name = "objc2-security"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3126341c65c5d5728423ae95d788e1b660756486ad0592307ab87ba02d9a7268"
+dependencies = [
+ "bitflags 2.9.0",
+ "objc2 0.6.0",
+ "objc2-core-foundation",
+]
+
[[package]]
name = "objc2-ui-kit"
version = "0.3.0"
@@ -4164,6 +4175,7 @@ dependencies = [
"objc2-app-kit",
"objc2-core-foundation",
"objc2-foundation 0.3.0",
+ "objc2-security",
]
[[package]]
@@ -5668,6 +5680,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_spanned"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -5712,9 +5733,9 @@ dependencies = [
[[package]]
name = "serialize-to-javascript"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
+checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
dependencies = [
"serde",
"serde_json",
@@ -5723,13 +5744,13 @@ dependencies = [
[[package]]
name = "serialize-to-javascript-impl"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
+checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.100",
]
[[package]]
@@ -6364,17 +6385,18 @@ dependencies = [
"cfg-expr",
"heck 0.5.0",
"pkg-config",
- "toml",
+ "toml 0.8.20",
"version-compare",
]
[[package]]
name = "tao"
-version = "0.34.0"
+version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
+checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf"
dependencies = [
"bitflags 2.9.0",
+ "block2 0.6.0",
"core-foundation 0.10.0",
"core-graphics",
"crossbeam-channel",
@@ -6443,16 +6465,17 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
-version = "2.6.0"
+version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f7a0f4019c80391d143ee26cd7cd1ed271ac241d3087d333f99f3269ba90812"
+checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348"
dependencies = [
"anyhow",
"bytes",
+ "cookie",
"dirs 6.0.0",
"dunce",
"embed_plist",
- "getrandom 0.2.15",
+ "getrandom 0.3.2",
"glob",
"gtk",
"heck 0.5.0",
@@ -6468,6 +6491,7 @@ dependencies = [
"objc2-app-kit",
"objc2-foundation 0.3.0",
"objc2-ui-kit",
+ "objc2-web-kit",
"percent-encoding",
"plist",
"raw-window-handle",
@@ -6497,9 +6521,9 @@ dependencies = [
[[package]]
name = "tauri-build"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
+checksum = "67945dbaf8920dbe3a1e56721a419a0c3d085254ab24cff5b9ad55e2b0016e0b"
dependencies = [
"anyhow",
"cargo_toml",
@@ -6515,15 +6539,15 @@ dependencies = [
"tauri-codegen",
"tauri-utils",
"tauri-winres",
- "toml",
+ "toml 0.9.5",
"walkdir",
]
[[package]]
name = "tauri-codegen"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
+checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
dependencies = [
"base64 0.22.1",
"ico",
@@ -6547,9 +6571,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f59e1d1fa9651212dcb890a0c66226d819b716490b0cf43c078514da3591705"
+checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -6561,9 +6585,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3"
+checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
dependencies = [
"anyhow",
"glob",
@@ -6572,7 +6596,7 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
- "toml",
+ "toml 0.9.5",
"walkdir",
]
@@ -6641,9 +6665,10 @@ dependencies = [
[[package]]
name = "tauri-plugin-deep-link"
-version = "2.4.1"
+version = "2.4.3"
dependencies = [
"dunce",
+ "plist",
"rust-ini",
"serde",
"serde_json",
@@ -6659,7 +6684,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
-version = "2.3.2"
+version = "2.4.0"
dependencies = [
"log",
"raw-window-handle",
@@ -6675,7 +6700,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
-version = "2.4.1"
+version = "2.4.2"
dependencies = [
"anyhow",
"dunce",
@@ -6691,7 +6716,7 @@ dependencies = [
"tauri-plugin",
"tauri-utils",
"thiserror 2.0.12",
- "toml",
+ "toml 0.9.5",
"url",
]
@@ -6736,7 +6761,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
-version = "2.5.1"
+version = "2.5.2"
dependencies = [
"bytes",
"cookie_store",
@@ -6772,7 +6797,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-log"
-version = "2.6.0"
+version = "2.7.0"
dependencies = [
"android_logger",
"byte-unit",
@@ -6793,7 +6818,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-nfc"
-version = "2.3.0"
+version = "2.3.1"
dependencies = [
"log",
"serde",
@@ -6806,7 +6831,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-notification"
-version = "2.3.0"
+version = "2.3.1"
dependencies = [
"color-backtrace",
"ctor",
@@ -6828,7 +6853,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-opener"
-version = "2.4.0"
+version = "2.5.0"
dependencies = [
"dunce",
"glob",
@@ -6848,7 +6873,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-os"
-version = "2.3.0"
+version = "2.3.1"
dependencies = [
"gethostname 1.0.1",
"log",
@@ -6864,7 +6889,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-persisted-scope"
-version = "2.3.1"
+version = "2.3.2"
dependencies = [
"aho-corasick",
"bincode",
@@ -6916,7 +6941,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-shell"
-version = "2.3.0"
+version = "2.3.1"
dependencies = [
"encoding_rs",
"log",
@@ -6935,7 +6960,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-single-instance"
-version = "2.3.2"
+version = "2.3.4"
dependencies = [
"semver",
"serde",
@@ -6967,7 +6992,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-store"
-version = "2.3.0"
+version = "2.4.0"
dependencies = [
"dunce",
"serde",
@@ -7080,9 +7105,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
-version = "2.7.0"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
+checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
dependencies = [
"cookie",
"dpi",
@@ -7091,20 +7116,23 @@ dependencies = [
"jni",
"objc2 0.6.0",
"objc2-ui-kit",
+ "objc2-web-kit",
"raw-window-handle",
"serde",
"serde_json",
"tauri-utils",
"thiserror 2.0.12",
"url",
+ "webkit2gtk",
+ "webview2-com",
"windows 0.61.1",
]
[[package]]
name = "tauri-runtime-wry"
-version = "2.7.0"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe52ed0ef40fd7ad51a620ecb3018e32eba3040bb95025216a962a37f6f050c5"
+checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
dependencies = [
"gtk",
"http",
@@ -7129,16 +7157,16 @@ dependencies = [
[[package]]
name = "tauri-utils"
-version = "2.5.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
+checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
dependencies = [
"aes-gcm",
"anyhow",
"cargo_metadata",
"ctor",
"dunce",
- "getrandom 0.2.15",
+ "getrandom 0.3.2",
"glob",
"html5ever",
"http",
@@ -7160,7 +7188,7 @@ dependencies = [
"serialize-to-javascript",
"swift-rs",
"thiserror 2.0.12",
- "toml",
+ "toml 0.9.5",
"url",
"urlpattern",
"uuid",
@@ -7174,7 +7202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0"
dependencies = [
"embed-resource",
- "toml",
+ "toml 0.8.20",
]
[[package]]
@@ -7476,11 +7504,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
- "serde_spanned",
- "toml_datetime",
+ "serde_spanned 0.6.8",
+ "toml_datetime 0.6.8",
"toml_edit 0.22.24",
]
+[[package]]
+name = "toml"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
+dependencies = [
+ "indexmap 2.9.0",
+ "serde",
+ "serde_spanned 1.0.0",
+ "toml_datetime 0.7.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 0.7.12",
+]
+
[[package]]
name = "toml_datetime"
version = "0.6.8"
@@ -7490,6 +7533,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "toml_datetime"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "toml_edit"
version = "0.19.15"
@@ -7497,7 +7549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.9.0",
- "toml_datetime",
+ "toml_datetime 0.6.8",
"winnow 0.5.40",
]
@@ -7508,7 +7560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap 2.9.0",
- "toml_datetime",
+ "toml_datetime 0.6.8",
"winnow 0.5.40",
]
@@ -7520,11 +7572,26 @@ checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap 2.9.0",
"serde",
- "serde_spanned",
- "toml_datetime",
- "winnow 0.7.6",
+ "serde_spanned 0.6.8",
+ "toml_datetime 0.6.8",
+ "winnow 0.7.12",
]
+[[package]]
+name = "toml_parser"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
+dependencies = [
+ "winnow 0.7.12",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
+
[[package]]
name = "tower"
version = "0.5.2"
@@ -7586,9 +7653,9 @@ dependencies = [
[[package]]
name = "tray-icon"
-version = "0.20.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5"
+checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2"
dependencies = [
"crossbeam-channel",
"dirs 6.0.0",
@@ -8833,9 +8900,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.7.6"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
+checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [
"memchr",
]
@@ -8901,14 +8968,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
-version = "0.52.0"
+version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b08db04817a654a7e3339647d9cf8b497ed9ddcd4ec7cfda5a3a220c10a3bba3"
+checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7"
dependencies = [
"base64 0.22.1",
"block2 0.6.0",
"cookie",
"crossbeam-channel",
+ "dirs 6.0.0",
"dpi",
"dunce",
"gdkx11",
@@ -9069,7 +9137,7 @@ dependencies = [
"tracing",
"uds_windows",
"windows-sys 0.60.2",
- "winnow 0.7.6",
+ "winnow 0.7.12",
"zbus_macros",
"zbus_names",
"zvariant",
@@ -9108,7 +9176,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [
"serde",
"static_assertions",
- "winnow 0.7.6",
+ "winnow 0.7.12",
"zvariant",
]
@@ -9295,7 +9363,7 @@ dependencies = [
"enumflags2",
"serde",
"url",
- "winnow 0.7.6",
+ "winnow 0.7.12",
"zvariant_derive",
"zvariant_utils",
]
@@ -9324,5 +9392,5 @@ dependencies = [
"serde",
"static_assertions",
"syn 2.0.100",
- "winnow 0.7.6",
+ "winnow 0.7.12",
]
diff --git a/Cargo.toml b/Cargo.toml
index 2d1b4c0e6..071f54ffb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,10 +12,10 @@ resolver = "2"
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
log = "0.4"
-tauri = { version = "2.6", default-features = false }
-tauri-build = "2.3"
-tauri-plugin = "2.3"
-tauri-utils = "2.5"
+tauri = { version = "2.8.2", default-features = false }
+tauri-build = "2.4"
+tauri-plugin = "2.4"
+tauri-utils = "2.7"
serde_json = "1"
thiserror = "2"
url = "2"
diff --git a/eslint.config.js b/eslint.config.js
index f34103ee4..2500c686e 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -3,11 +3,12 @@
// SPDX-License-Identifier: MIT
import eslint from '@eslint/js'
+import { defineConfig } from 'eslint/config'
import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginSecurity from 'eslint-plugin-security'
import tseslint from 'typescript-eslint'
-export default tseslint.config(
+export default defineConfig(
{
ignores: [
'**/target',
diff --git a/examples/api/CHANGELOG.md b/examples/api/CHANGELOG.md
index ea5d611d4..cd296557c 100644
--- a/examples/api/CHANGELOG.md
+++ b/examples/api/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## \[2.0.32]
+
+### Dependencies
+
+- Upgraded to `dialog-js@2.4.0`
+- Upgraded to `log-js@2.7.0`
+
+## \[2.0.31]
+
+### Dependencies
+
+- Upgraded to `shell-js@2.3.1`
+
+## \[2.0.30]
+
+### Dependencies
+
+- Upgraded to `notification-js@2.3.1`
+
+## \[2.0.29]
+
+### Dependencies
+
+- Upgraded to `fs-js@2.4.2`
+- Upgraded to `nfc-js@2.3.1`
+- Upgraded to `opener-js@2.5.0`
+- Upgraded to `os-js@2.3.1`
+- Upgraded to `store-js@2.4.0`
+- Upgraded to `dialog-js@2.3.3`
+- Upgraded to `http-js@2.5.2`
+
## \[2.0.28]
### Dependencies
diff --git a/examples/api/index.html b/examples/api/index.html
index 919cfd2a7..655165ea0 100644
--- a/examples/api/index.html
+++ b/examples/api/index.html
@@ -4,7 +4,7 @@
Svelte + Vite App
diff --git a/examples/api/package.json b/examples/api/package.json
index 200ac8f89..91763334f 100644
--- a/examples/api/package.json
+++ b/examples/api/package.json
@@ -1,7 +1,7 @@
{
"name": "api",
"private": true,
- "version": "2.0.28",
+ "version": "2.0.32",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
@@ -10,25 +10,25 @@
"tauri": "tauri"
},
"dependencies": {
- "@tauri-apps/api": "2.7.0",
+ "@tauri-apps/api": "2.8.0",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
"@tauri-apps/plugin-biometric": "^2.3.0",
"@tauri-apps/plugin-cli": "^2.4.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
- "@tauri-apps/plugin-dialog": "^2.3.2",
- "@tauri-apps/plugin-fs": "^2.4.1",
+ "@tauri-apps/plugin-dialog": "^2.4.0",
+ "@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
"@tauri-apps/plugin-haptics": "^2.2.0",
- "@tauri-apps/plugin-http": "^2.5.1",
- "@tauri-apps/plugin-nfc": "^2.3.0",
- "@tauri-apps/plugin-notification": "^2.3.0",
- "@tauri-apps/plugin-opener": "^2.4.0",
- "@tauri-apps/plugin-os": "^2.3.0",
+ "@tauri-apps/plugin-http": "^2.5.2",
+ "@tauri-apps/plugin-nfc": "^2.3.1",
+ "@tauri-apps/plugin-notification": "^2.3.1",
+ "@tauri-apps/plugin-opener": "^2.5.0",
+ "@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-process": "^2.3.0",
- "@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-secure-storage": "file:../../plugins/secure-storage",
- "@tauri-apps/plugin-store": "^2.3.0",
+ "@tauri-apps/plugin-shell": "^2.3.1",
+ "@tauri-apps/plugin-store": "^2.4.0",
"@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11"
@@ -37,10 +37,10 @@
"@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
- "@tauri-apps/cli": "2.7.1",
+ "@tauri-apps/cli": "2.8.4",
"@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4",
"unocss": "^66.3.3",
- "vite": "^7.0.4"
+ "vite": "^7.0.7"
}
}
diff --git a/examples/api/src-tauri/CHANGELOG.md b/examples/api/src-tauri/CHANGELOG.md
index 9fdae693e..7e4eaae8d 100644
--- a/examples/api/src-tauri/CHANGELOG.md
+++ b/examples/api/src-tauri/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## \[2.0.36]
+
+### Dependencies
+
+- Upgraded to `dialog@2.4.0`
+- Upgraded to `log@2.7.0`
+
+## \[2.0.35]
+
+### Dependencies
+
+- Upgraded to `shell@2.3.1`
+
+## \[2.0.34]
+
+### Dependencies
+
+- Upgraded to `notification@2.3.1`
+
+## \[2.0.33]
+
+### Dependencies
+
+- Upgraded to `fs@2.4.2`
+- Upgraded to `nfc@2.3.1`
+- Upgraded to `opener@2.5.0`
+- Upgraded to `os@2.3.1`
+- Upgraded to `store@2.4.0`
+- Upgraded to `dialog@2.3.3`
+- Upgraded to `http@2.5.2`
+
## \[2.0.32]
### Dependencies
diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml
index 5557e09bd..1134fc410 100644
--- a/examples/api/src-tauri/Cargo.toml
+++ b/examples/api/src-tauri/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "api"
publish = false
-version = "2.0.32"
+version = "2.0.36"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = { workspace = true }
@@ -20,25 +20,25 @@ serde = { workspace = true }
tiny_http = "0.12"
time = "0.3"
log = { workspace = true }
-tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" }
-tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features = [
+tauri-plugin-log = { path = "../../../plugins/log", version = "2.7.0" }
+tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.2", features = [
"watch",
] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
-tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.2" }
+tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.4.0" }
tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart",
"cookies",
-], version = "2.5.1" }
-tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.0", features = [
+], version = "2.5.2" }
+tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.1", features = [
"windows7-compat",
] }
-tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.0" }
+tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.1" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
-tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" }
+tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.0" }
tauri-plugin-secure-storage = { path = "../../../plugins/secure-storage" }
-tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" }
-tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" }
+tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.1" }
+tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.0" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
[dependencies.tauri]
@@ -63,7 +63,7 @@ tauri-plugin-window-state = { path = "../../../plugins/window-state", version =
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" }
-tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.0" }
+tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.1" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" }
diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json
index e17b3c3af..80d18a90b 100644
--- a/examples/api/src-tauri/capabilities/base.json
+++ b/examples/api/src-tauri/capabilities/base.json
@@ -68,6 +68,9 @@
"fs:allow-rename",
"fs:allow-mkdir",
"fs:allow-remove",
+ "fs:allow-stat",
+ "fs:allow-fstat",
+ "fs:allow-lstat",
"fs:allow-write-text-file",
"fs:read-meta",
"fs:scope-download-recursive",
@@ -75,6 +78,9 @@
{
"identifier": "fs:scope-appdata-recursive",
"allow": [
+ {
+ "path": "$APPDATA/db/"
+ },
{
"path": "$APPDATA/db/**"
}
diff --git a/examples/api/src/views/Dialog.svelte b/examples/api/src/views/Dialog.svelte
index 462eecff7..5aadad5a9 100644
--- a/examples/api/src/views/Dialog.svelte
+++ b/examples/api/src/views/Dialog.svelte
@@ -44,6 +44,13 @@
await message("Tauri is awesome!");
}
+ async function msgCustom(result) {
+ const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
+ await message(`Tauri is: `, { buttons })
+ .then((res) => onMessage(`Tauri is ${res}`))
+ .catch(onMessage);
+ }
+
function openDialog() {
open({
title: "My wonderful open dialog",
@@ -136,12 +143,17 @@
Directory
-Open dialog
-Open save dialog
-Prompt
-Prompt (custom)
-Message
+
+
+ Open dialog
+ Open save dialog
+ Prompt
+ Prompt (custom)
+ Message
+ Message (custom)
+
+
\ No newline at end of file
diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte
index baf9f6072..fbec86282 100644
--- a/examples/api/src/views/FileSystem.svelte
+++ b/examples/api/src/views/FileSystem.svelte
@@ -1,194 +1,214 @@
+ {#if isMobile}
+
+ On mobile, paths outside of App* paths require the use of dialogs
+ regardless of Tauri's scope mechanism.
+
+
+ {/if}
-
- None
- {#each DirOptions as dir}
- {dir[0]}
+
+ None
+ {#each dirOptions as dir}
+ {dir}
{/each}
-
Open
-
Read
-
Mkdir
-
Remove
+
Open
+
Read
+
Mkdir
+
Remove
- Rename
+ Rename
-
Use as img src
+
Use as img src
{#if file}
- Truncate
- Stat
+ Write
+ Truncate
+ Stat
{/if}
@@ -241,8 +262,8 @@
- Watch
- Unwatch
+ Watch
+ Unwatch
diff --git a/examples/api/src/views/Notifications.svelte b/examples/api/src/views/Notifications.svelte
index e01d57fcd..11273fdc3 100644
--- a/examples/api/src/views/Notifications.svelte
+++ b/examples/api/src/views/Notifications.svelte
@@ -1,16 +1,21 @@
-
+
+
Send test notification
diff --git a/package.json b/package.json
index 55a7d97fe..89c5909e7 100644
--- a/package.json
+++ b/package.json
@@ -11,20 +11,21 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
},
"devDependencies": {
- "@eslint/js": "9.32.0",
+ "@eslint/js": "9.36.0",
"@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "12.1.4",
"covector": "^0.12.4",
- "eslint": "9.32.0",
+ "eslint": "9.36.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "3.0.1",
"prettier": "3.6.2",
- "rollup": "4.46.2",
+ "rollup": "4.52.3",
"tslib": "2.8.1",
"typescript": "5.9.2",
- "typescript-eslint": "8.39.0"
+ "typescript-eslint": "8.44.1"
},
+ "minimumReleaseAge": 4320,
"pnpm": {
"overrides": {
"esbuild@<0.25.0": ">=0.25.0"
@@ -34,6 +35,6 @@
]
},
"engines": {
- "pnpm": "^10.0.0"
+ "pnpm": "^10.16.0"
}
}
diff --git a/plugins/autostart/package.json b/plugins/autostart/package.json
index 172c022f0..5f3d2adf4 100644
--- a/plugins/autostart/package.json
+++ b/plugins/autostart/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/barcode-scanner/package.json b/plugins/barcode-scanner/package.json
index b9194415c..87b6dc718 100644
--- a/plugins/barcode-scanner/package.json
+++ b/plugins/barcode-scanner/package.json
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/biometric/package.json b/plugins/biometric/package.json
index 559010f3f..e6c4d9881 100644
--- a/plugins/biometric/package.json
+++ b/plugins/biometric/package.json
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/cli/package.json b/plugins/cli/package.json
index 2177132ae..4aeeb8ec9 100644
--- a/plugins/cli/package.json
+++ b/plugins/cli/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/clipboard-manager/package.json b/plugins/clipboard-manager/package.json
index abe2a4fbd..ac6ac49c7 100644
--- a/plugins/clipboard-manager/package.json
+++ b/plugins/clipboard-manager/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/deep-link/CHANGELOG.md b/plugins/deep-link/CHANGELOG.md
index e55810a55..d9163a4f3 100644
--- a/plugins/deep-link/CHANGELOG.md
+++ b/plugins/deep-link/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## \[2.4.3]
+
+- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928).
+
+## \[2.4.2]
+
+- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail.
+
## \[2.4.1]
- [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux
diff --git a/plugins/deep-link/Cargo.toml b/plugins/deep-link/Cargo.toml
index 7c8524bfc..631e056ba 100644
--- a/plugins/deep-link/Cargo.toml
+++ b/plugins/deep-link/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-deep-link"
-version = "2.4.1"
+version = "2.4.3"
description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true }
license = { workspace = true }
@@ -27,6 +27,9 @@ serde_json = { workspace = true }
tauri-utils = { workspace = true }
tauri-plugin = { workspace = true, features = ["build"] }
+[target."cfg(target_os = \"macos\")".build-dependencies]
+plist = "1"
+
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/plugins/deep-link/build.rs b/plugins/deep-link/build.rs
index 418746b23..16d2a96e5 100644
--- a/plugins/deep-link/build.rs
+++ b/plugins/deep-link/build.rs
@@ -10,50 +10,64 @@ const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registe
// TODO: Consider using activity-alias in case users may have multiple activities in their app.
fn intent_filter(domain: &AssociatedDomain) -> String {
+ let host = domain
+ .host
+ .as_ref()
+ .map(|h| format!(r#" "#))
+ .unwrap_or_default();
+
+ let auto_verify = if domain.is_app_link() {
+ r#"android:autoVerify="true" "#.to_string()
+ } else {
+ String::new()
+ };
+
format!(
- r#"
+ r#"
- {}
-
- {}
- {}
- {}
- {}
+ {schemes}
+ {host}
+ {domains}
+ {path_patterns}
+ {path_prefixes}
+ {path_suffixes}
"#,
- domain
+ schemes = domain
.scheme
.iter()
.map(|scheme| format!(r#" "#))
.collect::>()
.join("\n "),
- domain.host,
- domain
+ host = host,
+ domains = domain
.path
.iter()
.map(|path| format!(r#" "#))
.collect::>()
.join("\n "),
- domain
+ path_patterns = domain
.path_pattern
.iter()
.map(|pattern| format!(r#" "#))
.collect::>()
.join("\n "),
- domain
+ path_prefixes = domain
.path_prefix
.iter()
.map(|prefix| format!(r#" "#))
.collect::>()
.join("\n "),
- domain
+ path_suffixes = domain
.path_suffix
.iter()
.map(|suffix| format!(r#" "#))
.collect::>()
.join("\n "),
)
+ .trim()
+ .to_string()
}
fn main() {
@@ -68,6 +82,16 @@ fn main() {
}
if let Some(config) = tauri_plugin::plugin_config::("deep-link") {
+ let errors: Vec = config
+ .mobile
+ .iter()
+ .filter_map(|d| d.validate().err())
+ .collect();
+
+ if !errors.is_empty() {
+ panic!("Deep link config validation failed:\n{}", errors.join("\n"));
+ }
+
tauri_plugin::mobile::update_android_manifest(
"DEEP LINK PLUGIN",
"activity",
@@ -80,20 +104,89 @@ fn main() {
)
.expect("failed to rewrite AndroidManifest.xml");
- #[cfg(target_os = "macos")]
+ #[cfg(any(target_os = "macos", target_os = "ios"))]
{
- tauri_plugin::mobile::update_entitlements(|entitlements| {
- entitlements.insert(
- "com.apple.developer.associated-domains".into(),
- config
- .mobile
- .into_iter()
- .map(|d| format!("applinks:{}", d.host).into())
- .collect::>()
- .into(),
- );
- })
- .expect("failed to update entitlements");
+ // we need to ensure that the entitlements are only
+ // generated for explicit app links and not
+ // other deep links because then they
+ // are just going to complain and not be built or signed
+ let has_app_links = config.mobile.iter().any(|d| d.is_app_link());
+
+ if !has_app_links {
+ tauri_plugin::mobile::update_entitlements(|entitlements| {
+ entitlements.remove("com.apple.developer.associated-domains");
+ })
+ .expect("failed to update entitlements");
+ } else {
+ tauri_plugin::mobile::update_entitlements(|entitlements| {
+ entitlements.insert(
+ "com.apple.developer.associated-domains".into(),
+ config
+ .mobile
+ .iter()
+ .filter(|d| d.is_app_link())
+ .filter_map(|d| d.host.as_ref())
+ .map(|host| format!("applinks:{}", host).into())
+ .collect::>()
+ .into(),
+ );
+ })
+ .expect("failed to update entitlements");
+ }
+
+ let deep_link_domains = config
+ .mobile
+ .iter()
+ .filter_map(|domain| {
+ if domain.is_app_link() {
+ return None;
+ }
+
+ Some(domain)
+ })
+ .collect::>();
+
+ if deep_link_domains.is_empty() {
+ tauri_plugin::mobile::update_info_plist(|info_plist| {
+ info_plist.remove("CFBundleURLTypes");
+ })
+ .expect("failed to update Info.plist");
+ } else {
+ tauri_plugin::mobile::update_info_plist(|info_plist| {
+ info_plist.insert(
+ "CFBundleURLTypes".into(),
+ deep_link_domains
+ .iter()
+ .map(|domain| {
+ let schemes = domain
+ .scheme
+ .iter()
+ .filter(|scheme| {
+ scheme.as_str() != "https" && scheme.as_str() != "http"
+ })
+ .collect::>();
+
+ let mut dict = plist::Dictionary::new();
+ dict.insert(
+ "CFBundleURLSchemes".into(),
+ schemes
+ .iter()
+ .map(|s| s.to_string().into())
+ .collect::>()
+ .into(),
+ );
+ dict.insert(
+ "CFBundleURLName".into(),
+ format!("{}", domain.scheme[0]).into(),
+ );
+ plist::Value::Dictionary(dict)
+ })
+ .collect::>()
+ .into(),
+ );
+ })
+ .expect("failed to update Info.plist");
+ }
}
}
}
diff --git a/plugins/deep-link/examples/app/CHANGELOG.md b/plugins/deep-link/examples/app/CHANGELOG.md
index 773bef385..a95225512 100644
--- a/plugins/deep-link/examples/app/CHANGELOG.md
+++ b/plugins/deep-link/examples/app/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## \[2.2.6]
+
+### Dependencies
+
+- Upgraded to `deep-link-js@2.4.3`
+
+## \[2.2.5]
+
+### Dependencies
+
+- Upgraded to `deep-link-js@2.4.2`
+
## \[2.2.4]
### Dependencies
diff --git a/plugins/deep-link/examples/app/package.json b/plugins/deep-link/examples/app/package.json
index 0df59d850..f7da42f96 100644
--- a/plugins/deep-link/examples/app/package.json
+++ b/plugins/deep-link/examples/app/package.json
@@ -1,7 +1,7 @@
{
"name": "deep-link-example",
"private": true,
- "version": "2.2.4",
+ "version": "2.2.6",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,12 +10,12 @@
"tauri": "tauri"
},
"dependencies": {
- "@tauri-apps/api": "2.7.0",
- "@tauri-apps/plugin-deep-link": "2.4.1"
+ "@tauri-apps/api": "2.8.0",
+ "@tauri-apps/plugin-deep-link": "2.4.3"
},
"devDependencies": {
- "@tauri-apps/cli": "2.7.1",
+ "@tauri-apps/cli": "2.8.4",
"typescript": "^5.7.3",
- "vite": "^7.0.4"
+ "vite": "^7.0.7"
}
}
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts b/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts
index f434bbfb0..13ec1ffd6 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/app/build.gradle.kts
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
}
android {
- compileSdk = 34
+ compileSdk = 36
namespace = "com.tauri.deep_link_example"
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.deep_link_example"
minSdk = 24
- targetSdk = 34
+ targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
}
@@ -58,9 +58,10 @@ rust {
}
dependencies {
- implementation("androidx.webkit:webkit:1.6.1")
- implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("com.google.android.material:material:1.8.0")
+ implementation("androidx.webkit:webkit:1.14.0")
+ implementation("androidx.appcompat:appcompat:1.7.1")
+ implementation("androidx.activity:activity-ktx:1.10.1")
+ implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
index 05265e325..591f3c61e 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/AndroidManifest.xml
@@ -23,23 +23,40 @@
-
+
-
+
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt b/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt
index e13fb9956..313161c22 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/app/src/main/java/com/tauri/deep_link_example/MainActivity.kt
@@ -1,7 +1,11 @@
-// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
package com.tauri.deep_link_example
-class MainActivity : TauriActivity()
+import android.os.Bundle
+import androidx.activity.enableEdgeToEdge
+
+class MainActivity : TauriActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ }
+}
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts b/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts
index c5ef452a6..607240bc8 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/build.gradle.kts
@@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("com.android.tools.build:gradle:8.5.1")
+ classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
}
}
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts b/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts
index 39e90b054..5c55bba71 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/buildSrc/build.gradle.kts
@@ -18,6 +18,6 @@ repositories {
dependencies {
compileOnly(gradleApi())
- implementation("com.android.tools.build:gradle:8.5.1")
+ implementation("com.android.tools.build:gradle:8.11.0")
}
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties
index 0df10d556..c5f9a53c2 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties
+++ b/plugins/deep-link/examples/app/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json b/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json
index dd3b8bcc5..90eea7ec7 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,116 +1,116 @@
{
- "images": [
+ "images" : [
{
- "size": "20x20",
- "idiom": "iphone",
- "filename": "AppIcon-20x20@2x.png",
- "scale": "2x"
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-20x20@2x.png",
+ "scale" : "2x"
},
{
- "size": "20x20",
- "idiom": "iphone",
- "filename": "AppIcon-20x20@3x.png",
- "scale": "3x"
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-20x20@3x.png",
+ "scale" : "3x"
},
{
- "size": "29x29",
- "idiom": "iphone",
- "filename": "AppIcon-29x29@2x-1.png",
- "scale": "2x"
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-29x29@2x-1.png",
+ "scale" : "2x"
},
{
- "size": "29x29",
- "idiom": "iphone",
- "filename": "AppIcon-29x29@3x.png",
- "scale": "3x"
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-29x29@3x.png",
+ "scale" : "3x"
},
{
- "size": "40x40",
- "idiom": "iphone",
- "filename": "AppIcon-40x40@2x.png",
- "scale": "2x"
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-40x40@2x.png",
+ "scale" : "2x"
},
{
- "size": "40x40",
- "idiom": "iphone",
- "filename": "AppIcon-40x40@3x.png",
- "scale": "3x"
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-40x40@3x.png",
+ "scale" : "3x"
},
{
- "size": "60x60",
- "idiom": "iphone",
- "filename": "AppIcon-60x60@2x.png",
- "scale": "2x"
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-60x60@2x.png",
+ "scale" : "2x"
},
{
- "size": "60x60",
- "idiom": "iphone",
- "filename": "AppIcon-60x60@3x.png",
- "scale": "3x"
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "AppIcon-60x60@3x.png",
+ "scale" : "3x"
},
{
- "size": "20x20",
- "idiom": "ipad",
- "filename": "AppIcon-20x20@1x.png",
- "scale": "1x"
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-20x20@1x.png",
+ "scale" : "1x"
},
{
- "size": "20x20",
- "idiom": "ipad",
- "filename": "AppIcon-20x20@2x-1.png",
- "scale": "2x"
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-20x20@2x-1.png",
+ "scale" : "2x"
},
{
- "size": "29x29",
- "idiom": "ipad",
- "filename": "AppIcon-29x29@1x.png",
- "scale": "1x"
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-29x29@1x.png",
+ "scale" : "1x"
},
{
- "size": "29x29",
- "idiom": "ipad",
- "filename": "AppIcon-29x29@2x.png",
- "scale": "2x"
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-29x29@2x.png",
+ "scale" : "2x"
},
{
- "size": "40x40",
- "idiom": "ipad",
- "filename": "AppIcon-40x40@1x.png",
- "scale": "1x"
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-40x40@1x.png",
+ "scale" : "1x"
},
{
- "size": "40x40",
- "idiom": "ipad",
- "filename": "AppIcon-40x40@2x-1.png",
- "scale": "2x"
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-40x40@2x-1.png",
+ "scale" : "2x"
},
{
- "size": "76x76",
- "idiom": "ipad",
- "filename": "AppIcon-76x76@1x.png",
- "scale": "1x"
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-76x76@1x.png",
+ "scale" : "1x"
},
{
- "size": "76x76",
- "idiom": "ipad",
- "filename": "AppIcon-76x76@2x.png",
- "scale": "2x"
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-76x76@2x.png",
+ "scale" : "2x"
},
{
- "size": "83.5x83.5",
- "idiom": "ipad",
- "filename": "AppIcon-83.5x83.5@2x.png",
- "scale": "2x"
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "AppIcon-83.5x83.5@2x.png",
+ "scale" : "2x"
},
{
- "size": "1024x1024",
- "idiom": "ios-marketing",
- "filename": "AppIcon-512@2x.png",
- "scale": "1x"
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "AppIcon-512@2x.png",
+ "scale" : "1x"
}
],
- "info": {
- "version": 1,
- "author": "xcode"
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
}
-}
+}
\ No newline at end of file
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json b/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json
index 97a8662eb..da4a164c9 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/Assets.xcassets/Contents.json
@@ -1,6 +1,6 @@
{
- "info": {
- "version": 1,
- "author": "xcode"
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
}
-}
+}
\ No newline at end of file
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard b/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard
index dd79351ec..81b5f90e2 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/LaunchScreen.storyboard
@@ -1,5 +1,5 @@
-
+
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj b/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj
index 450bd8470..4c01a958a 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 56;
+ objectVersion = 63;
objects = {
/* Begin PBXBuildFile section */
@@ -167,6 +167,8 @@
dependencies = (
);
name = "deep-link-example_iOS";
+ packageProductDependencies = (
+ );
productName = "deep-link-example_iOS";
productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */;
productType = "com.apple.product-type.application";
@@ -189,6 +191,7 @@
en,
);
mainGroup = 1DC58B1705AA3ECC6B887FE7;
+ minimizedProjectReferenceProxies = 1;
projectDirPath = "";
projectRoot = "";
targets = (
@@ -227,7 +230,6 @@
outputPaths = (
"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a",
"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a",
- "$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -314,18 +316,13 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ARCHS = (
- arm64,
- "arm64-sim",
- );
+ ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = Q93MBH6S2F;
+ DEVELOPMENT_TEAM = "Q93MBH6S2F";
ENABLE_BITCODE = NO;
- "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -335,13 +332,6 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
- "$(inherited)",
- "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
- "$(SDKROOT)/usr/lib/swift",
- "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
- "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
- );
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -360,7 +350,7 @@
PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = "arm64 arm64-sim";
+ VALID_ARCHS = arm64;
};
name = debug;
};
@@ -424,18 +414,13 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
- ARCHS = (
- arm64,
- "arm64-sim",
- );
+ ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
- CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = Q93MBH6S2F;
+ DEVELOPMENT_TEAM = "Q93MBH6S2F";
ENABLE_BITCODE = NO;
- "EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
- "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
+ "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -445,13 +430,6 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
- "$(inherited)",
- "$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
- "$(SDKROOT)/usr/lib/swift",
- "$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
- "$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
- );
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -470,7 +448,7 @@
PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
- VALID_ARCHS = "arm64 arm64-sim";
+ VALID_ARCHS = arm64;
};
name = release;
};
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d981003..000000000
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist b/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist
index 7ce866140..bc74b01bc 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/deep-link-example_iOS/Info.plist
@@ -15,7 +15,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.0.0
+ 0.1.0
CFBundleVersion
0.1.0
LSRequiresIPhoneOS
@@ -40,5 +40,16 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ taurideeplink
+
+ CFBundleURLName
+ taurideeplink
+
+
\ No newline at end of file
diff --git a/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml b/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml
index c924ca77b..74d0ab491 100644
--- a/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml
+++ b/plugins/deep-link/examples/app/src-tauri/gen/apple/project.yml
@@ -1,7 +1,3 @@
-# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
-# SPDX-License-Identifier: Apache-2.0
-# SPDX-License-Identifier: MIT
-
name: deep-link-example
options:
bundleIdPrefix: com.tauri.deep-link-example
@@ -16,7 +12,6 @@ settingGroups:
base:
PRODUCT_NAME: deep-link-example
PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example
- DEVELOPMENT_TEAM: Q93MBH6S2F
targetTemplates:
app:
type: application
@@ -56,8 +51,8 @@ targets:
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
- CFBundleShortVersionString: 0.0.0
- CFBundleVersion: 0.0.0
+ CFBundleShortVersionString: 0.1.0
+ CFBundleVersion: '0.1.0'
entitlements:
path: deep-link-example_iOS/deep-link-example_iOS.entitlements
scheme:
@@ -67,14 +62,12 @@ targets:
settings:
base:
ENABLE_BITCODE: false
- ARCHS: [arm64, arm64-sim]
- VALID_ARCHS: arm64 arm64-sim
+ ARCHS: [arm64]
+ VALID_ARCHS: arm64
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
- LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
- EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64
- EXCLUDED_ARCHS[sdk=iphoneos*]: arm64-sim x86_64
+ EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64
groups: [app]
dependencies:
- framework: libapp.a
@@ -93,4 +86,3 @@ targets:
outputFiles:
- $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a
- $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a
- - $(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a
diff --git a/plugins/deep-link/examples/app/src-tauri/tauri.conf.json b/plugins/deep-link/examples/app/src-tauri/tauri.conf.json
index ac1c292b4..61f7394df 100644
--- a/plugins/deep-link/examples/app/src-tauri/tauri.conf.json
+++ b/plugins/deep-link/examples/app/src-tauri/tauri.conf.json
@@ -35,6 +35,9 @@
},
{
"host": "tauri.app"
+ },
+ {
+ "scheme": ["taurideeplink"]
}
],
"desktop": {
diff --git a/plugins/deep-link/package.json b/plugins/deep-link/package.json
index b316c481d..7eebc79ef 100644
--- a/plugins/deep-link/package.json
+++ b/plugins/deep-link/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-deep-link",
- "version": "2.4.1",
+ "version": "2.4.3",
"description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,9 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
+ },
+ "devDependencies": {
+ "@tauri-apps/cli": "2.8.4"
}
}
diff --git a/plugins/deep-link/src/config.rs b/plugins/deep-link/src/config.rs
index 88222a246..49de68e3d 100644
--- a/plugins/deep-link/src/config.rs
+++ b/plugins/deep-link/src/config.rs
@@ -11,10 +11,8 @@ use tauri_utils::config::DeepLinkProtocol;
pub struct AssociatedDomain {
#[serde(default = "default_schemes")]
pub scheme: Vec,
-
- #[serde(deserialize_with = "deserialize_associated_host")]
- pub host: String,
-
+ #[serde(default, deserialize_with = "deserialize_associated_host")]
+ pub host: Option, // Optional custom uri schemes dont NEED a host (may have one still), but required for https/http schemes
#[serde(default)]
pub path: Vec,
#[serde(default, alias = "path-pattern", rename = "pathPattern")]
@@ -23,6 +21,41 @@ pub struct AssociatedDomain {
pub path_prefix: Vec,
#[serde(default, alias = "path-suffix", rename = "pathSuffix")]
pub path_suffix: Vec,
+ #[serde(default, alias = "app-link", rename = "appLink")]
+ pub app_link: Option,
+}
+
+impl AssociatedDomain {
+ /// Returns true if the domain uses http or https scheme.
+ pub fn is_web_link(&self) -> bool {
+ self.scheme.iter().any(|s| s == "https" || s == "http")
+ }
+
+ /// Returns true if the domain uses http or https scheme and has proper host configuration.
+ pub fn is_app_link(&self) -> bool {
+ self.app_link
+ .unwrap_or_else(|| self.is_web_link() && self.host.is_some())
+ }
+
+ pub fn validate(&self) -> Result<(), String> {
+ // Rule 1: All web links require a host.
+ if self.is_web_link() && self.host.is_none() {
+ return Err("Web link requires a host".into());
+ }
+
+ // Rule 2: If it's an App Link, ensure http(s) and host.
+ if self.is_app_link() {
+ if !self.is_web_link() {
+ return Err("AppLink must be a valid web link (https/http + host)".into());
+ }
+ if self.scheme.iter().any(|s| s == "http") && !self.scheme.iter().any(|s| s == "https")
+ {
+ eprintln!("Warning: AppLink uses only 'http' — allowed on Android but not secure for production.");
+ }
+ }
+
+ Ok(())
+ }
}
// TODO: Consider removing this in v3
@@ -30,18 +63,19 @@ fn default_schemes() -> Vec {
vec!["https".to_string(), "http".to_string()]
}
-fn deserialize_associated_host<'de, D>(deserializer: D) -> Result
+fn deserialize_associated_host<'de, D>(deserializer: D) -> Result, D::Error>
where
D: Deserializer<'de>,
{
- let host = String::deserialize(deserializer)?;
- if let Some((scheme, _host)) = host.split_once("://") {
- Err(serde::de::Error::custom(format!(
- "host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
- )))
- } else {
- Ok(host)
+ let opt = Option::::deserialize(deserializer)?;
+ if let Some(ref host) = opt {
+ if let Some((scheme, _)) = host.split_once("://") {
+ return Err(serde::de::Error::custom(format!(
+ "host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
+ )));
+ }
}
+ Ok(opt)
}
#[derive(Deserialize, Clone)]
diff --git a/plugins/deep-link/src/error.rs b/plugins/deep-link/src/error.rs
index 88c71e8a5..41eb764f1 100644
--- a/plugins/deep-link/src/error.rs
+++ b/plugins/deep-link/src/error.rs
@@ -28,6 +28,16 @@ pub enum Error {
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
}
+// TODO(v3): change this into an error in v3,
+// see .
+#[inline]
+#[cfg(target_os = "linux")]
+pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a {
+ move |e| {
+ tracing::error!("Failed to run OS command `{command}`: {e}");
+ }
+}
+
impl Serialize for Error {
fn serialize(&self, serializer: S) -> std::result::Result
where
diff --git a/plugins/deep-link/src/lib.rs b/plugins/deep-link/src/lib.rs
index 1cd13b580..00757bf2a 100644
--- a/plugins/deep-link/src/lib.rs
+++ b/plugins/deep-link/src/lib.rs
@@ -254,6 +254,7 @@ mod imp {
///
/// ## Platform-specific:
///
+ /// - **Linux**: Needs the `xdg-mime` and `update-desktop-database` commands available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn register>(&self, _protocol: S) -> crate::Result<()> {
#[cfg(windows)]
@@ -292,6 +293,7 @@ mod imp {
.unwrap_or_else(|| bin.into_os_string())
.to_string_lossy()
.to_string();
+ let qualified_exec = format!("{} %u", exec);
let target = self.app.path().data_dir()?.join("applications");
@@ -303,12 +305,28 @@ mod imp {
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
- // it's ok to remove it - we only write to the file if it's missing
- // and in that case we include old_mimes
let old_mimes = section.remove("MimeType").unwrap_or_default();
+ let mut change = false;
+ // if the mime type is not present, append it to the list
if !old_mimes.split(';').any(|mime| mime == mime_type) {
section.append("MimeType", format!("{mime_type};{old_mimes}"));
+ change = true;
+ } else {
+ section.insert("MimeType".to_string(), old_mimes);
+ }
+
+ // if the exec command doesnt match, update to the new one
+ let old_exec = section.remove("Exec").unwrap_or_default();
+ if old_exec != qualified_exec {
+ section.append("Exec", qualified_exec);
+ change = true;
+ } else {
+ section.insert("Exec".to_string(), old_exec.to_string());
+ }
+
+ // if any property has changed, rewrite the .desktop file
+ if change {
desktop_file.write_to_file(&target_file)?;
}
}
@@ -323,7 +341,7 @@ mod imp {
.product_name
.clone()
.unwrap_or_else(|| file_name.clone()),
- exec = exec,
+ qualified_exec = qualified_exec,
mime_type = mime_type
)
.as_bytes(),
@@ -332,11 +350,15 @@ mod imp {
Command::new("update-desktop-database")
.arg(target)
- .status()?;
+ .status()
+ .inspect_err(crate::error::inspect_command_error(
+ "update-desktop-database",
+ ))?;
Command::new("xdg-mime")
.args(["default", &file_name, mime_type.as_str()])
- .status()?;
+ .status()
+ .inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(())
}
@@ -405,6 +427,7 @@ mod imp {
///
/// ## Platform-specific:
///
+ /// - **Linux**: Needs the `xdg-mime` command available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn is_registered>(&self, _protocol: S) -> crate::Result {
#[cfg(windows)]
@@ -439,7 +462,8 @@ mod imp {
"default",
&format!("x-scheme-handler/{}", _protocol.as_ref()),
])
- .output()?;
+ .output()
+ .inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
}
diff --git a/plugins/deep-link/src/template.desktop b/plugins/deep-link/src/template.desktop
index 0fb89abb4..068beb103 100644
--- a/plugins/deep-link/src/template.desktop
+++ b/plugins/deep-link/src/template.desktop
@@ -1,7 +1,7 @@
[Desktop Entry]
Type=Application
Name={name}
-Exec={exec} %u
+Exec={qualified_exec}
Terminal=false
MimeType={mime_type}
-NoDisplay=true
\ No newline at end of file
+NoDisplay=true
diff --git a/plugins/dialog/CHANGELOG.md b/plugins/dialog/CHANGELOG.md
index b5fe23475..fd5f8dd2d 100644
--- a/plugins/dialog/CHANGELOG.md
+++ b/plugins/dialog/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## \[2.4.0]
+
+- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons.
+
+## \[2.3.3]
+
+### Dependencies
+
+- Upgraded to `fs-js@2.4.2`
+
## \[2.3.2]
- [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set.
diff --git a/plugins/dialog/Cargo.toml b/plugins/dialog/Cargo.toml
index c57ad66b9..7856e68db 100644
--- a/plugins/dialog/Cargo.toml
+++ b/plugins/dialog/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-dialog"
-version = "2.3.2"
+version = "2.4.0"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
@@ -34,7 +34,7 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
-tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
+tauri-plugin-fs = { path = "../fs", version = "2.4.2" }
[target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] }
diff --git a/plugins/dialog/android/src/main/java/DialogPlugin.kt b/plugins/dialog/android/src/main/java/DialogPlugin.kt
index 1d87a6e91..b93596353 100644
--- a/plugins/dialog/android/src/main/java/DialogPlugin.kt
+++ b/plugins/dialog/android/src/main/java/DialogPlugin.kt
@@ -38,6 +38,7 @@ class MessageOptions {
var title: String? = null
lateinit var message: String
var okButtonLabel: String? = null
+ var noButtonLabel: String? = null
var cancelButtonLabel: String? = null
}
@@ -139,9 +140,8 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return
}
- val handler = { cancelled: Boolean, value: Boolean ->
+ val handler = { value: String ->
val ret = JSObject()
- ret.put("cancelled", cancelled)
ret.put("value", value)
invoke.resolve(ret)
}
@@ -153,24 +153,34 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
if (args.title != null) {
builder.setTitle(args.title)
}
+
+ val okButtonLabel = args.okButtonLabel ?: "Ok"
+
builder
.setMessage(args.message)
- .setPositiveButton(
- args.okButtonLabel ?: "OK"
- ) { dialog, _ ->
+ .setPositiveButton(okButtonLabel) { dialog, _ ->
dialog.dismiss()
- handler(false, true)
+ handler(okButtonLabel)
}
.setOnCancelListener { dialog ->
dialog.dismiss()
- handler(true, false)
+ handler(args.cancelButtonLabel ?: "Cancel")
}
+
+ if (args.noButtonLabel != null) {
+ builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
+ dialog.dismiss()
+ handler(args.noButtonLabel!!)
+ }
+ }
+
if (args.cancelButtonLabel != null) {
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
dialog.dismiss()
- handler(false, false)
+ handler(args.cancelButtonLabel!!)
}
}
+
val dialog = builder.create()
dialog.show()
}
diff --git a/plugins/dialog/api-iife.js b/plugins/dialog/api-iife.js
index c2e0870c8..a357f2c0b 100644
--- a/plugins/dialog/api-iife.js
+++ b/plugins/dialog/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,yesButtonLabel:i?.okLabel?.toString(),noButtonLabel:i?.cancelLabel?.toString()})},t.confirm=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),cancelButtonLabel:i?.cancelLabel?.toString()})},t.message=async function(t,e){const i="string"==typeof e?{title:e}:e;await n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString()})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}function e(t){if(void 0!==t)return"string"==typeof t?t:"ok"in t&&"cancel"in t?{OkCancelCustom:[t.ok,t.cancel]}:"yes"in t&&"no"in t&&"cancel"in t?{YesNoCancelCustom:[t.yes,t.no,t.cancel]}:"ok"in t?{OkCustom:t.ok}:void 0}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,yesButtonLabel:o?.okLabel?.toString(),noButtonLabel:o?.cancelLabel?.toString()})},t.confirm=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,okButtonLabel:o?.okLabel?.toString(),cancelButtonLabel:o?.cancelLabel?.toString()})},t.message=async function(t,o){const i="string"==typeof o?{title:o}:o;return n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),buttons:e(i?.buttons)})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
diff --git a/plugins/dialog/guest-js/index.ts b/plugins/dialog/guest-js/index.ts
index 150be95a0..a77857545 100644
--- a/plugins/dialog/guest-js/index.ts
+++ b/plugins/dialog/guest-js/index.ts
@@ -77,6 +77,80 @@ interface SaveDialogOptions {
canCreateDirectories?: boolean
}
+/**
+ * Default buttons for a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogDefaultButtons =
+ | 'Ok'
+ | 'OkCancel'
+ | 'YesNo'
+ | 'YesNoCancel'
+
+/** All possible button keys. */
+type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no'
+
+/** Ban everything except a set of keys. */
+type BanExcept = Partial<
+ Record, never>
+>
+
+/**
+ * The Yes, No and Cancel buttons of a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogButtonsYesNoCancel = {
+ /** The Yes button. */
+ yes: string
+ /** The No button. */
+ no: string
+ /** The Cancel button. */
+ cancel: string
+} & BanExcept<'yes' | 'no' | 'cancel'>
+
+/**
+ * The Ok and Cancel buttons of a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogButtonsOkCancel = {
+ /** The Ok button. */
+ ok: string
+ /** The Cancel button. */
+ cancel: string
+} & BanExcept<'ok' | 'cancel'>
+
+/**
+ * The Ok button of a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogButtonsOk = {
+ /** The Ok button. */
+ ok: string
+} & BanExcept<'ok'>
+
+/**
+ * Custom buttons for a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogCustomButtons =
+ | MessageDialogButtonsYesNoCancel
+ | MessageDialogButtonsOkCancel
+ | MessageDialogButtonsOk
+
+/**
+ * The buttons of a message dialog.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogButtons =
+ | MessageDialogDefaultButtons
+ | MessageDialogCustomButtons
+
/**
* @since 2.0.0
*/
@@ -85,8 +159,58 @@ interface MessageDialogOptions {
title?: string
/** The kind of the dialog. Defaults to `info`. */
kind?: 'info' | 'warning' | 'error'
- /** The label of the confirm button. */
+ /**
+ * The label of the Ok button.
+ *
+ * @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
+ */
okLabel?: string
+ /**
+ * The buttons of the dialog.
+ *
+ * @example
+ *
+ * ```ts
+ * // Use system default buttons texts
+ * await message('Hello World!', { buttons: 'Ok' })
+ * await message('Hello World!', { buttons: 'OkCancel' })
+ *
+ * // Or with custom button texts
+ * await message('Hello World!', { buttons: { ok: 'Yes!' } })
+ * await message('Take on the task?', {
+ * buttons: { ok: 'Accept', cancel: 'Cancel' }
+ * })
+ * await message('Show the file content?', {
+ * buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' }
+ * })
+ * ```
+ *
+ * @since 2.4.0
+ */
+ buttons?: MessageDialogButtons
+}
+
+/**
+ * Internal function to convert the buttons to the Rust type.
+ */
+function buttonsToRust(buttons: MessageDialogButtons | undefined) {
+ if (buttons === undefined) {
+ return undefined
+ }
+
+ if (typeof buttons === 'string') {
+ return buttons
+ } else if ('ok' in buttons && 'cancel' in buttons) {
+ return { OkCancelCustom: [buttons.ok, buttons.cancel] }
+ } else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
+ return {
+ YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
+ }
+ } else if ('ok' in buttons) {
+ return { OkCustom: buttons.ok }
+ }
+
+ return undefined
}
interface ConfirmDialogOptions {
@@ -202,6 +326,16 @@ async function save(options: SaveDialogOptions = {}): Promise {
return await invoke('plugin:dialog|save', { options })
}
+/**
+ * The result of a message dialog.
+ *
+ * The result is a string if the dialog has custom buttons,
+ * otherwise it is one of the default buttons.
+ *
+ * @since 2.4.0
+ */
+export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})
+
/**
* Shows a message dialog with an `Ok` button.
* @example
@@ -222,13 +356,15 @@ async function save(options: SaveDialogOptions = {}): Promise {
async function message(
message: string,
options?: string | MessageDialogOptions
-): Promise {
+): Promise {
const opts = typeof options === 'string' ? { title: options } : options
- await invoke('plugin:dialog|message', {
+
+ return invoke('plugin:dialog|message', {
message: message.toString(),
title: opts?.title?.toString(),
kind: opts?.kind,
- okButtonLabel: opts?.okLabel?.toString()
+ okButtonLabel: opts?.okLabel?.toString(),
+ buttons: buttonsToRust(opts?.buttons)
})
}
diff --git a/plugins/dialog/ios/Sources/DialogPlugin.swift b/plugins/dialog/ios/Sources/DialogPlugin.swift
index b3f7e7da6..710fd0bb2 100644
--- a/plugins/dialog/ios/Sources/DialogPlugin.swift
+++ b/plugins/dialog/ios/Sources/DialogPlugin.swift
@@ -20,6 +20,7 @@ struct MessageDialogOptions: Decodable {
var title: String?
let message: String
var okButtonLabel: String?
+ var noButtonLabel: String?
var cancelButtonLabel: String?
}
@@ -200,36 +201,38 @@ class DialogPlugin: Plugin {
let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
- let cancelButtonLabel = args.cancelButtonLabel ?? ""
- if !cancelButtonLabel.isEmpty {
+ if let cancelButtonLabel = args.cancelButtonLabel {
alert.addAction(
UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
- Logger.error("cancel")
-
- invoke.resolve([
- "value": false,
- "cancelled": false,
- ])
- }))
+ invoke.resolve(["value": cancelButtonLabel])
+ }
+ )
+ )
}
- let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
- if !okButtonLabel.isEmpty {
+ if let noButtonLabel = args.noButtonLabel {
alert.addAction(
UIAlertAction(
- title: okButtonLabel, style: UIAlertAction.Style.default,
+ title: noButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
- Logger.error("ok")
-
- invoke.resolve([
- "value": true,
- "cancelled": false,
- ])
- }))
+ invoke.resolve(["value": noButtonLabel])
+ }
+ )
+ )
}
+ let okButtonLabel = args.okButtonLabel ?? "Ok"
+ alert.addAction(
+ UIAlertAction(
+ title: okButtonLabel, style: UIAlertAction.Style.default,
+ handler: { (_) -> Void in
+ invoke.resolve(["value": okButtonLabel])
+ }
+ )
+ )
+
manager.viewController?.present(alert, animated: true, completion: nil)
}
}
diff --git a/plugins/dialog/package.json b/plugins/dialog/package.json
index 4412461cc..ac363a667 100644
--- a/plugins/dialog/package.json
+++ b/plugins/dialog/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-dialog",
- "version": "2.3.2",
+ "version": "2.4.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/dialog/src/commands.rs b/plugins/dialog/src/commands.rs
index c3caf027f..5298de9d0 100644
--- a/plugins/dialog/src/commands.rs
+++ b/plugins/dialog/src/commands.rs
@@ -9,8 +9,8 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt;
use crate::{
- Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL,
- NO, OK, YES,
+ Dialog, FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogButtons,
+ MessageDialogKind, MessageDialogResult, Result, CANCEL, NO, OK, YES,
};
#[derive(Serialize)]
@@ -248,7 +248,7 @@ fn message_dialog(
message: String,
kind: Option,
buttons: MessageDialogButtons,
-) -> bool {
+) -> MessageDialogBuilder {
let mut builder = dialog.message(message);
builder = builder.buttons(buttons);
@@ -266,7 +266,7 @@ fn message_dialog(
builder = builder.kind(kind);
}
- builder.blocking_show()
+ builder
}
#[command]
@@ -277,19 +277,15 @@ pub(crate) async fn message(
message: String,
kind: Option,
ok_button_label: Option,
-) -> Result {
- Ok(message_dialog(
- window,
- dialog,
- title,
- message,
- kind,
- if let Some(ok_button_label) = ok_button_label {
- MessageDialogButtons::OkCustom(ok_button_label)
- } else {
- MessageDialogButtons::Ok
- },
- ))
+ buttons: Option,
+) -> Result {
+ let buttons = buttons.unwrap_or(if let Some(ok_button_label) = ok_button_label {
+ MessageDialogButtons::OkCustom(ok_button_label)
+ } else {
+ MessageDialogButtons::Ok
+ });
+
+ Ok(message_dialog(window, dialog, title, message, kind, buttons).blocking_show_with_result())
}
#[command]
@@ -302,7 +298,7 @@ pub(crate) async fn ask(
yes_button_label: Option,
no_button_label: Option,
) -> Result {
- Ok(message_dialog(
+ let dialog = message_dialog(
window,
dialog,
title,
@@ -318,7 +314,9 @@ pub(crate) async fn ask(
} else {
MessageDialogButtons::YesNo
},
- ))
+ );
+
+ Ok(dialog.blocking_show())
}
#[command]
@@ -331,7 +329,7 @@ pub(crate) async fn confirm(
ok_button_label: Option,
cancel_button_label: Option,
) -> Result {
- Ok(message_dialog(
+ let dialog = message_dialog(
window,
dialog,
title,
@@ -347,5 +345,7 @@ pub(crate) async fn confirm(
} else {
MessageDialogButtons::OkCancel
},
- ))
+ );
+
+ Ok(dialog.blocking_show())
}
diff --git a/plugins/dialog/src/desktop.rs b/plugins/dialog/src/desktop.rs
index d1a3e8b21..8d3b08f95 100644
--- a/plugins/dialog/src/desktop.rs
+++ b/plugins/dialog/src/desktop.rs
@@ -13,7 +13,7 @@ use rfd::{AsyncFileDialog, AsyncMessageDialog};
use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Runtime};
-use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder, OK};
+use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder};
pub fn init(
app: &AppHandle,
@@ -115,6 +115,10 @@ impl From for rfd::MessageButtons {
MessageDialogButtons::YesNo => Self::YesNo,
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
+ MessageDialogButtons::YesNoCancel => Self::YesNoCancel,
+ MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
+ Self::YesNoCancelCustom(yes, no, cancel)
+ }
}
}
}
@@ -208,28 +212,46 @@ pub fn save_file) + Send + 'static>(
}
/// Shows a message dialog
-pub fn show_message_dialog(
+pub fn show_message_dialog(
dialog: MessageDialogBuilder,
- f: F,
+ callback: F,
) {
- use rfd::MessageDialogResult;
-
- let ok_label = match &dialog.buttons {
- MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
- MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
- _ => None,
- };
- let f = move |res| {
- f(match res {
- MessageDialogResult::Ok | MessageDialogResult::Yes => true,
- MessageDialogResult::Custom(s) => ok_label.map_or(s == OK, |ok_label| ok_label == s),
- _ => false,
- });
- };
+ let f = move |res: rfd::MessageDialogResult| callback(res.into());
let handle = dialog.dialog.app_handle().to_owned();
let _ = handle.run_on_main_thread(move || {
+ let buttons = dialog.buttons.clone();
let dialog = AsyncMessageDialog::from(dialog).show();
- std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
+ std::thread::spawn(move || {
+ let result = tauri::async_runtime::block_on(dialog);
+ // on Linux rfd does not return rfd::MessageDialogResult::Custom, so we must map manually
+ let result = match (result, buttons) {
+ (rfd::MessageDialogResult::Ok, MessageDialogButtons::OkCustom(s)) => {
+ rfd::MessageDialogResult::Custom(s)
+ }
+ (
+ rfd::MessageDialogResult::Ok,
+ MessageDialogButtons::OkCancelCustom(ok, _cancel),
+ ) => rfd::MessageDialogResult::Custom(ok),
+ (
+ rfd::MessageDialogResult::Cancel,
+ MessageDialogButtons::OkCancelCustom(_ok, cancel),
+ ) => rfd::MessageDialogResult::Custom(cancel),
+ (
+ rfd::MessageDialogResult::Yes,
+ MessageDialogButtons::YesNoCancelCustom(yes, _no, _cancel),
+ ) => rfd::MessageDialogResult::Custom(yes),
+ (
+ rfd::MessageDialogResult::No,
+ MessageDialogButtons::YesNoCancelCustom(_yes, no, _cancel),
+ ) => rfd::MessageDialogResult::Custom(no),
+ (
+ rfd::MessageDialogResult::Cancel,
+ MessageDialogButtons::YesNoCancelCustom(_yes, _no, cancel),
+ ) => rfd::MessageDialogResult::Custom(cancel),
+ (result, _) => result,
+ };
+ f(result);
+ });
});
}
diff --git a/plugins/dialog/src/lib.rs b/plugins/dialog/src/lib.rs
index 2ef1c1ead..17d9a829d 100644
--- a/plugins/dialog/src/lib.rs
+++ b/plugins/dialog/src/lib.rs
@@ -216,6 +216,7 @@ pub(crate) struct MessageDialogPayload<'a> {
message: &'a String,
kind: &'a MessageDialogKind,
ok_button_label: Option<&'a str>,
+ no_button_label: Option<&'a str>,
cancel_button_label: Option<&'a str>,
}
@@ -238,13 +239,17 @@ impl MessageDialogBuilder {
#[cfg(mobile)]
pub(crate) fn payload(&self) -> MessageDialogPayload<'_> {
- let (ok_button_label, cancel_button_label) = match &self.buttons {
- MessageDialogButtons::Ok => (Some(OK), None),
- MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)),
- MessageDialogButtons::YesNo => (Some(YES), Some(NO)),
- MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)),
+ let (ok_button_label, no_button_label, cancel_button_label) = match &self.buttons {
+ MessageDialogButtons::Ok => (Some(OK), None, None),
+ MessageDialogButtons::OkCancel => (Some(OK), None, Some(CANCEL)),
+ MessageDialogButtons::YesNo => (Some(YES), Some(NO), None),
+ MessageDialogButtons::YesNoCancel => (Some(YES), Some(NO), Some(CANCEL)),
+ MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), None, None),
MessageDialogButtons::OkCancelCustom(ok, cancel) => {
- (Some(ok.as_str()), Some(cancel.as_str()))
+ (Some(ok.as_str()), None, Some(cancel.as_str()))
+ }
+ MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
+ (Some(yes.as_str()), Some(no.as_str()), Some(cancel.as_str()))
}
};
MessageDialogPayload {
@@ -252,6 +257,7 @@ impl MessageDialogBuilder {
message: &self.message,
kind: &self.kind,
ok_button_label,
+ no_button_label,
cancel_button_label,
}
}
@@ -295,16 +301,55 @@ impl MessageDialogBuilder {
}
/// Shows a message dialog
+ ///
+ /// Returns `true` if the user pressed the OK/Yes button,
pub fn show(self, f: F) {
+ let ok_label = match &self.buttons {
+ MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
+ MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
+ MessageDialogButtons::YesNoCancelCustom(yes, _, _) => Some(yes.clone()),
+ _ => None,
+ };
+
+ show_message_dialog(self, move |res| {
+ let sucess = match res {
+ MessageDialogResult::Ok | MessageDialogResult::Yes => true,
+ MessageDialogResult::Custom(s) => {
+ ok_label.map_or(s == OK, |ok_label| ok_label == s)
+ }
+ _ => false,
+ };
+
+ f(sucess)
+ })
+ }
+
+ /// Shows a message dialog and returns the button that was pressed.
+ ///
+ /// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
+ pub fn show_with_result(self, f: F) {
show_message_dialog(self, f)
}
/// Shows a message dialog.
+ ///
+ /// Returns `true` if the user pressed the OK/Yes button,
+ ///
/// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context.
pub fn blocking_show(self) -> bool {
blocking_fn!(self, show)
}
+
+ /// Shows a message dialog and returns the button that was pressed.
+ ///
+ /// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
+ ///
+ /// This is a blocking operation,
+ /// and should *NOT* be used when running on the main thread context.
+ pub fn blocking_show_with_result(self) -> MessageDialogResult {
+ blocking_fn!(self, show_with_result)
+ }
}
#[derive(Debug, Serialize)]
pub(crate) struct Filter {
diff --git a/plugins/dialog/src/mobile.rs b/plugins/dialog/src/mobile.rs
index b73def4f9..46ea3a276 100644
--- a/plugins/dialog/src/mobile.rs
+++ b/plugins/dialog/src/mobile.rs
@@ -8,7 +8,7 @@ use tauri::{
AppHandle, Runtime,
};
-use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder};
+use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogResult};
#[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
@@ -107,13 +107,11 @@ pub fn save_file) + Send + 'static>(
#[derive(Debug, Deserialize)]
struct ShowMessageDialogResponse {
- #[allow(dead_code)]
- cancelled: bool,
- value: bool,
+ value: String,
}
/// Shows a message dialog
-pub fn show_message_dialog(
+pub fn show_message_dialog(
dialog: MessageDialogBuilder,
f: F,
) {
@@ -122,6 +120,8 @@ pub fn show_message_dialog(
.dialog
.0
.run_mobile_plugin::("showMessageDialog", dialog.payload());
- f(res.map(|r| r.value).unwrap_or_default())
+
+ let res = res.map(|res| res.value.into());
+ f(res.unwrap_or_default())
});
}
diff --git a/plugins/dialog/src/models.rs b/plugins/dialog/src/models.rs
index d6452bce7..0b2de2c9a 100644
--- a/plugins/dialog/src/models.rs
+++ b/plugins/dialog/src/models.rs
@@ -52,7 +52,7 @@ impl Serialize for MessageDialogKind {
/// Set of button that will be displayed on the dialog
#[non_exhaustive]
-#[derive(Debug, Default, Clone)]
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum MessageDialogButtons {
#[default]
/// A single `Ok` button with OS default dialog text
@@ -61,8 +61,49 @@ pub enum MessageDialogButtons {
OkCancel,
/// 2 buttons `Yes` and `No` with OS default dialog texts
YesNo,
+ /// 3 buttons `Yes`, `No` and `Cancel` with OS default dialog texts
+ YesNoCancel,
/// A single `Ok` button with custom text
OkCustom(String),
/// 2 buttons `Ok` and `Cancel` with custom texts
OkCancelCustom(String, String),
+ /// 3 buttons `Yes`, `No` and `Cancel` with custom texts
+ YesNoCancelCustom(String, String, String),
+}
+
+/// Result of a message dialog
+#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
+pub enum MessageDialogResult {
+ Yes,
+ No,
+ Ok,
+ #[default]
+ Cancel,
+ #[serde(untagged)]
+ Custom(String),
+}
+
+#[cfg(desktop)]
+impl From for MessageDialogResult {
+ fn from(result: rfd::MessageDialogResult) -> Self {
+ match result {
+ rfd::MessageDialogResult::Yes => Self::Yes,
+ rfd::MessageDialogResult::No => Self::No,
+ rfd::MessageDialogResult::Ok => Self::Ok,
+ rfd::MessageDialogResult::Cancel => Self::Cancel,
+ rfd::MessageDialogResult::Custom(s) => Self::Custom(s),
+ }
+ }
+}
+
+impl From for MessageDialogResult {
+ fn from(value: String) -> Self {
+ match value.as_str() {
+ "Yes" => Self::Yes,
+ "No" => Self::No,
+ "Ok" => Self::Ok,
+ "Cancel" => Self::Cancel,
+ _ => Self::Custom(value),
+ }
+ }
}
diff --git a/plugins/fs/CHANGELOG.md b/plugins/fs/CHANGELOG.md
index a46c067e9..e1e1ad40a 100644
--- a/plugins/fs/CHANGELOG.md
+++ b/plugins/fs/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## \[2.4.2]
+
+- [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
+- [`515182a1`](https://github.com/tauri-apps/plugins-workspace/commit/515182a179d4439079b2b7f6927555ba5ab0b035) ([#2915](https://github.com/tauri-apps/plugins-workspace/pull/2915) by [@samhinshaw](https://github.com/tauri-apps/plugins-workspace/../../samhinshaw)) `readFile` now returns a more specific type `Promise>` instead of the default `Promise`
+
## \[2.4.1]
- [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream`
diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml
index 73df3d0e2..bff3baeac 100644
--- a/plugins/fs/Cargo.toml
+++ b/plugins/fs/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-fs"
-version = "2.4.1"
+version = "2.4.2"
description = "Access the file system."
authors = { workspace = true }
license = { workspace = true }
@@ -24,7 +24,7 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
tauri-plugin = { workspace = true, features = ["build"] }
schemars = { workspace = true }
serde = { workspace = true }
-toml = "0.8"
+toml = "0.9"
tauri-utils = { workspace = true, features = ["build"] }
[dependencies]
diff --git a/plugins/fs/api-iife.js b/plugins/fs/api-iife.js
index aab5a9691..5fdd186d9 100644
--- a/plugins/fs/api-iife.js
+++ b/plugins/fs/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){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,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=y,t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_file",{path:t instanceof URL?t.toString():t,options:e});return n instanceof ArrayBuffer?new Uint8Array(n):Uint8Array.from(n)},t.readTextFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_text_file",{path:t instanceof URL?t.toString():t,options:e}),i=n instanceof ArrayBuffer?n:Uint8Array.from(n);return(new TextDecoder).decode(i)},t.readTextFileLines=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=t instanceof URL?t.toString():t;return await Promise.resolve({path:n,rid:null,async next(){null===this.rid&&(this.rid=await l("plugin:fs|read_text_file_lines",{path:n,options:e}));const t=await l("plugin:fs|read_text_file_lines_next",{rid:this.rid}),i=t instanceof ArrayBuffer?new Uint8Array(t):Uint8Array.from(t),o=1===i[i.byteLength-1];if(o)return this.rid=null,{value:null,done:o};return{value:(new TextDecoder).decode(i.slice(0,i.byteLength)),done:o}},[Symbol.asyncIterator](){return this}})},t.remove=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|remove",{path:t instanceof URL?t.toString():t,options:e})},t.rename=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|rename",{oldPath:t instanceof URL?t.toString():t,newPath:e instanceof URL?e.toString():e,options:n})},t.size=async function(t){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|size",{path:t instanceof URL?t.toString():t})},t.stat=async function(t,e){return d(await l("plugin:fs|stat",{path:t instanceof URL?t.toString():t,options:e}))},t.truncate=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|truncate",{path:t instanceof URL?t.toString():t,len:e,options:n})},t.watch=async function(t,e,n){return await R(t,e,{delayMs:2e3,...n})},t.watchImmediate=async function(t,e,n){return await R(t,e,{...n,delayMs:void 0})},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await y(t,{create:!0,...n}),o=e.getReader();try{for(;;){const{done:t,value:e}=await o.read();if(t)break;await i.write(e)}}finally{o.releaseLock(),await i.close()}}else await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){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,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=y,t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_file",{path:t instanceof URL?t.toString():t,options:e});return n instanceof ArrayBuffer?new Uint8Array(n):Uint8Array.from(n)},t.readTextFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_text_file",{path:t instanceof URL?t.toString():t,options:e}),i=n instanceof ArrayBuffer?n:Uint8Array.from(n);return(new TextDecoder).decode(i)},t.readTextFileLines=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=t instanceof URL?t.toString():t;return await Promise.resolve({path:n,rid:null,async next(){null===this.rid&&(this.rid=await l("plugin:fs|read_text_file_lines",{path:n,options:e}));const t=await l("plugin:fs|read_text_file_lines_next",{rid:this.rid}),i=t instanceof ArrayBuffer?new Uint8Array(t):Uint8Array.from(t),o=1===i[i.byteLength-1];if(o)return this.rid=null,{value:null,done:o};return{value:(new TextDecoder).decode(i.slice(0,i.byteLength)),done:o}},[Symbol.asyncIterator](){return this}})},t.remove=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|remove",{path:t instanceof URL?t.toString():t,options:e})},t.rename=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|rename",{oldPath:t instanceof URL?t.toString():t,newPath:e instanceof URL?e.toString():e,options:n})},t.size=async function(t){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|size",{path:t instanceof URL?t.toString():t})},t.stat=async function(t,e){return d(await l("plugin:fs|stat",{path:t instanceof URL?t.toString():t,options:e}))},t.truncate=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|truncate",{path:t instanceof URL?t.toString():t,len:e,options:n})},t.watch=async function(t,e,n){return await R(t,e,{delayMs:2e3,...n})},t.watchImmediate=async function(t,e,n){return await R(t,e,{...n,delayMs:void 0})},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await y(t,{read:!1,create:!0,write:!0,...n}),o=e.getReader();try{for(;;){const{done:t,value:e}=await o.read();if(t)break;await i.write(e)}}finally{o.releaseLock(),await i.close()}}else await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})}
diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts
index a78fa17b8..572cc1e85 100644
--- a/plugins/fs/guest-js/index.ts
+++ b/plugins/fs/guest-js/index.ts
@@ -739,7 +739,7 @@ interface ReadFileOptions {
async function readFile(
path: string | URL,
options?: ReadFileOptions
-): Promise {
+): Promise> {
if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.')
}
@@ -1074,7 +1074,12 @@ async function writeFile(
}
if (data instanceof ReadableStream) {
- const file = await open(path, { create: true, ...options })
+ const file = await open(path, {
+ read: false,
+ create: true,
+ write: true,
+ ...options
+ })
const reader = data.getReader()
try {
diff --git a/plugins/fs/package.json b/plugins/fs/package.json
index 1ce3cb57e..203fb2adc 100644
--- a/plugins/fs/package.json
+++ b/plugins/fs/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-fs",
- "version": "2.4.1",
+ "version": "2.4.2",
"description": "Access the file system.",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/fs/permissions/autogenerated/reference.md b/plugins/fs/permissions/autogenerated/reference.md
index c1a603194..7f021a7f3 100644
--- a/plugins/fs/permissions/autogenerated/reference.md
+++ b/plugins/fs/permissions/autogenerated/reference.md
@@ -3748,6 +3748,28 @@ This enables all index or metadata related commands without any pre-configured a
An empty permission you can use to modify the global scope.
+## Example
+
+```json
+{
+ "identifier": "read-documents",
+ "windows": ["main"],
+ "permissions": [
+ "fs:allow-read",
+ {
+ "identifier": "fs:scope",
+ "allow": [
+ "$APPDATA/documents/**/*"
+ ],
+ "deny": [
+ "$APPDATA/documents/secret.txt"
+ ]
+ }
+ ]
+}
+```
+
+
diff --git a/plugins/fs/permissions/schemas/schema.json b/plugins/fs/permissions/schemas/schema.json
index 54c6798b3..e1c051f70 100644
--- a/plugins/fs/permissions/schemas/schema.json
+++ b/plugins/fs/permissions/schemas/schema.json
@@ -2005,10 +2005,10 @@
"markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths."
},
{
- "description": "An empty permission you can use to modify the global scope.",
+ "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n",
"type": "string",
"const": "scope",
- "markdownDescription": "An empty permission you can use to modify the global scope."
+ "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n"
},
{
"description": "This enables all write related commands without any pre-configured accessible paths.",
diff --git a/plugins/fs/permissions/scope.toml b/plugins/fs/permissions/scope.toml
index 7e945aa8b..03ba275a4 100644
--- a/plugins/fs/permissions/scope.toml
+++ b/plugins/fs/permissions/scope.toml
@@ -2,4 +2,27 @@
[[permission]]
identifier = "scope"
-description = "An empty permission you can use to modify the global scope."
+description = """
+An empty permission you can use to modify the global scope.
+
+## Example
+
+```json
+{
+ "identifier": "read-documents",
+ "windows": ["main"],
+ "permissions": [
+ "fs:allow-read",
+ {
+ "identifier": "fs:scope",
+ "allow": [
+ "$APPDATA/documents/**/*"
+ ],
+ "deny": [
+ "$APPDATA/documents/secret.txt"
+ ]
+ }
+ ]
+}
+```
+"""
diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs
index bd1400ea1..ad837a4c0 100644
--- a/plugins/fs/src/commands.rs
+++ b/plugins/fs/src/commands.rs
@@ -85,6 +85,7 @@ pub fn create(
options: Option,
) -> CommandResult {
let resolved_path = resolve_path(
+ "create",
&webview,
&global_scope,
&command_scope,
@@ -119,6 +120,7 @@ pub fn open(
options: Option,
) -> CommandResult {
let (file, _path) = resolve_file(
+ "open",
&webview,
&global_scope,
&command_scope,
@@ -167,6 +169,7 @@ pub async fn copy_file(
options: Option,
) -> CommandResult<()> {
let resolved_from_path = resolve_path(
+ "copy-file",
&webview,
&global_scope,
&command_scope,
@@ -174,6 +177,7 @@ pub async fn copy_file(
options.as_ref().and_then(|o| o.from_path_base_dir),
)?;
let resolved_to_path = resolve_path(
+ "copy-file",
&webview,
&global_scope,
&command_scope,
@@ -208,6 +212,7 @@ pub fn mkdir(
options: Option,
) -> CommandResult<()> {
let resolved_path = resolve_path(
+ "mkdir",
&webview,
&global_scope,
&command_scope,
@@ -255,6 +260,7 @@ pub async fn read_dir(
options: Option,
) -> CommandResult> {
let resolved_path = resolve_path(
+ "read-dir",
&webview,
&global_scope,
&command_scope,
@@ -331,8 +337,8 @@ pub async fn read(
Ok(tauri::ipc::Response::new(data))
}
-#[tauri::command]
-pub async fn read_file(
+async fn read_file_inner(
+ permission: &str,
webview: Webview,
global_scope: GlobalScope,
command_scope: CommandScope,
@@ -340,6 +346,7 @@ pub async fn read_file(
options: Option,
) -> CommandResult {
let (mut file, path) = resolve_file(
+ permission,
&webview,
&global_scope,
&command_scope,
@@ -367,6 +374,25 @@ pub async fn read_file(
Ok(tauri::ipc::Response::new(contents))
}
+#[tauri::command]
+pub async fn read_file(
+ webview: Webview,
+ global_scope: GlobalScope,
+ command_scope: CommandScope,
+ path: SafeFilePath,
+ options: Option,
+) -> CommandResult {
+ read_file_inner(
+ "read-file",
+ webview,
+ global_scope,
+ command_scope,
+ path,
+ options,
+ )
+ .await
+}
+
// TODO, remove in v3, rely on `read_file` command instead
#[tauri::command]
pub async fn read_text_file(
@@ -376,7 +402,15 @@ pub async fn read_text_file(
path: SafeFilePath,
options: Option,
) -> CommandResult {
- read_file(webview, global_scope, command_scope, path, options).await
+ read_file_inner(
+ "read-text-file",
+ webview,
+ global_scope,
+ command_scope,
+ path,
+ options,
+ )
+ .await
}
#[tauri::command]
@@ -388,6 +422,7 @@ pub fn read_text_file_lines(
options: Option,
) -> CommandResult {
let resolved_path = resolve_path(
+ "read-text-file-lines",
&webview,
&global_scope,
&command_scope,
@@ -452,6 +487,7 @@ pub fn remove(
options: Option,
) -> CommandResult<()> {
let resolved_path = resolve_path(
+ "remove",
&webview,
&global_scope,
&command_scope,
@@ -521,6 +557,7 @@ pub fn rename(
options: Option,
) -> CommandResult<()> {
let resolved_old_path = resolve_path(
+ "rename",
&webview,
&global_scope,
&command_scope,
@@ -528,6 +565,7 @@ pub fn rename(
options.as_ref().and_then(|o| o.old_path_base_dir),
)?;
let resolved_new_path = resolve_path(
+ "rename",
&webview,
&global_scope,
&command_scope,
@@ -575,6 +613,7 @@ pub async fn seek(
#[cfg(target_os = "android")]
fn get_metadata std::io::Result>(
+ permission: &str,
metadata_fn: F,
webview: &Webview,
global_scope: &GlobalScope,
@@ -585,6 +624,7 @@ fn get_metadata std::io::Result {
let (file, path) = resolve_file(
+ permission,
webview,
global_scope,
command_scope,
@@ -606,6 +646,7 @@ fn get_metadata std::io::Result get_fs_metadata(
+ permission,
metadata_fn,
webview,
global_scope,
@@ -618,6 +659,7 @@ fn get_metadata std::io::Result std::io::Result>(
+ permission: &str,
metadata_fn: F,
webview: &Webview,
global_scope: &GlobalScope,
@@ -626,6 +668,7 @@ fn get_metadata std::io::Result,
) -> CommandResult {
get_fs_metadata(
+ permission,
metadata_fn,
webview,
global_scope,
@@ -636,6 +679,7 @@ fn get_metadata std::io::Result std::io::Result>(
+ permission: &str,
metadata_fn: F,
webview: &Webview,
global_scope: &GlobalScope,
@@ -644,6 +688,7 @@ fn get_fs_metadata std::io::Result,
) -> CommandResult {
let resolved_path = resolve_path(
+ permission,
webview,
global_scope,
command_scope,
@@ -668,6 +713,7 @@ pub fn stat(
options: Option,
) -> CommandResult {
let metadata = get_metadata(
+ "stat",
|p| std::fs::metadata(p),
&webview,
&global_scope,
@@ -688,6 +734,7 @@ pub fn lstat(
options: Option,
) -> CommandResult {
let metadata = get_metadata(
+ "lstat",
|p| std::fs::symlink_metadata(p),
&webview,
&global_scope,
@@ -716,6 +763,7 @@ pub async fn truncate(
options: Option,
) -> CommandResult<()> {
let resolved_path = resolve_path(
+ "truncate",
&webview,
&global_scope,
&command_scope,
@@ -784,23 +832,13 @@ fn default_create_value() -> bool {
true
}
-#[tauri::command]
-pub async fn write_file(
+async fn write_file_inner(
+ permission: &str,
webview: Webview,
global_scope: GlobalScope,
command_scope: CommandScope,
request: tauri::ipc::Request<'_>,
) -> CommandResult<()> {
- let data = match request.body() {
- tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
- tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
- data.iter()
- .flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
- .collect(),
- ),
- _ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
- };
-
let path = request
.headers()
.get("path")
@@ -811,6 +849,7 @@ pub async fn write_file(
.map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into())
})
.and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?;
+
let options: Option = request
.headers()
.get("options")
@@ -818,6 +857,7 @@ pub async fn write_file(
.and_then(|opts| serde_json::from_str(opts).ok());
let (mut file, path) = resolve_file(
+ permission,
&webview,
&global_scope,
&command_scope,
@@ -853,6 +893,16 @@ pub async fn write_file(
},
)?;
+ let data = match request.body() {
+ tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
+ tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
+ data.iter()
+ .flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
+ .collect(),
+ ),
+ _ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
+ };
+
file.write_all(&data)
.map_err(|e| {
format!(
@@ -863,6 +913,16 @@ pub async fn write_file(
.map_err(Into::into)
}
+#[tauri::command]
+pub async fn write_file(
+ webview: Webview,
+ global_scope: GlobalScope,
+ command_scope: CommandScope,
+ request: tauri::ipc::Request<'_>,
+) -> CommandResult<()> {
+ write_file_inner("write-file", webview, global_scope, command_scope, request).await
+}
+
// TODO, remove in v3, rely on `write_file` command instead
#[tauri::command]
pub async fn write_text_file(
@@ -871,7 +931,14 @@ pub async fn write_text_file(
command_scope: CommandScope,
request: tauri::ipc::Request<'_>,
) -> CommandResult<()> {
- write_file(webview, global_scope, command_scope, request).await
+ write_file_inner(
+ "write-text-file",
+ webview,
+ global_scope,
+ command_scope,
+ request,
+ )
+ .await
}
#[tauri::command]
@@ -883,6 +950,7 @@ pub fn exists(
options: Option,
) -> CommandResult {
let resolved_path = resolve_path(
+ "exists",
&webview,
&global_scope,
&command_scope,
@@ -901,6 +969,7 @@ pub async fn size(
options: Option,
) -> CommandResult {
let resolved_path = resolve_path(
+ "size",
&webview,
&global_scope,
&command_scope,
@@ -943,16 +1012,25 @@ fn get_dir_size(path: &PathBuf) -> CommandResult {
#[cfg(not(target_os = "android"))]
pub fn resolve_file(
+ permission: &str,
webview: &Webview,
global_scope: &GlobalScope,
command_scope: &CommandScope,
path: SafeFilePath,
open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> {
- resolve_file_in_fs(webview, global_scope, command_scope, path, open_options)
+ resolve_file_in_fs(
+ permission,
+ webview,
+ global_scope,
+ command_scope,
+ path,
+ open_options,
+ )
}
fn resolve_file_in_fs(
+ permission: &str,
webview: &Webview,
global_scope: &GlobalScope,
command_scope: &CommandScope,
@@ -960,6 +1038,7 @@ fn resolve_file_in_fs(
open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> {
let path = resolve_path(
+ permission,
webview,
global_scope,
command_scope,
@@ -980,6 +1059,7 @@ fn resolve_file_in_fs(
#[cfg(target_os = "android")]
pub fn resolve_file(
+ permission: &str,
webview: &Webview,
global_scope: &GlobalScope,
command_scope: &CommandScope,
@@ -997,6 +1077,7 @@ pub fn resolve_file(
Ok((file, path))
}
SafeFilePath::Path(path) => resolve_file_in_fs(
+ permission,
webview,
global_scope,
command_scope,
@@ -1007,6 +1088,7 @@ pub fn resolve_file(
}
pub fn resolve_path(
+ permission: &str,
webview: &Webview,
global_scope: &GlobalScope,
command_scope: &CommandScope,
@@ -1052,7 +1134,17 @@ pub fn resolve_path(
if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) {
Ok(path)
} else {
- Err(CommandError::Plugin(Error::PathForbidden(path)))
+ #[cfg(not(debug_assertions))]
+ return Err(CommandError::Plugin(Error::PathForbidden(path)));
+
+ #[cfg(debug_assertions)]
+ Err(
+ anyhow::anyhow!(
+ "forbidden path: {}, maybe it is not allowed on the scope for `allow-{permission}` permission in your capability file",
+ path.display()
+ )
+ )
+ .map_err(Into::into)
}
}
diff --git a/plugins/fs/src/mobile.rs b/plugins/fs/src/mobile.rs
index 06422be6b..472f2c8a9 100644
--- a/plugins/fs/src/mobile.rs
+++ b/plugins/fs/src/mobile.rs
@@ -89,7 +89,7 @@ impl Fs {
std::fs::File::from_raw_fd(fd)
})
} else {
- todo!()
+ unimplemented!()
}
}
}
diff --git a/plugins/fs/src/watcher.rs b/plugins/fs/src/watcher.rs
index 89446b884..de9a85d31 100644
--- a/plugins/fs/src/watcher.rs
+++ b/plugins/fs/src/watcher.rs
@@ -49,6 +49,7 @@ pub fn watch(
.into_iter()
.map(|path| {
resolve_path(
+ "watch",
&webview,
&global_scope,
&command_scope,
diff --git a/plugins/geolocation/android/src/main/java/Geolocation.kt b/plugins/geolocation/android/src/main/java/Geolocation.kt
index b16a44825..444711a4c 100644
--- a/plugins/geolocation/android/src/main/java/Geolocation.kt
+++ b/plugins/geolocation/android/src/main/java/Geolocation.kt
@@ -91,11 +91,9 @@ public class Geolocation(private val context: Context) {
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
- Logger.error(prio.toString())
-
- val locationRequest = LocationRequest.Builder(10000)
+ val locationRequest = LocationRequest.Builder(timeout)
.setMaxUpdateDelayMillis(timeout)
- .setMinUpdateIntervalMillis(5000)
+ .setMinUpdateIntervalMillis(timeout)
.setPriority(prio)
.build()
@@ -145,4 +143,4 @@ public class Geolocation(private val context: Context) {
return lastLoc
}
-}
\ No newline at end of file
+}
diff --git a/plugins/geolocation/package.json b/plugins/geolocation/package.json
index 272943b28..937b96ddb 100644
--- a/plugins/geolocation/package.json
+++ b/plugins/geolocation/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/global-shortcut/package.json b/plugins/global-shortcut/package.json
index f33653280..2b796c7a5 100644
--- a/plugins/global-shortcut/package.json
+++ b/plugins/global-shortcut/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/haptics/package.json b/plugins/haptics/package.json
index b4f485a2a..6111cef45 100644
--- a/plugins/haptics/package.json
+++ b/plugins/haptics/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/http/CHANGELOG.md b/plugins/http/CHANGELOG.md
index 9544fae25..95bc6a40d 100644
--- a/plugins/http/CHANGELOG.md
+++ b/plugins/http/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## \[2.5.2]
+
+### Dependencies
+
+- Upgraded to `fs-js@2.4.2`
+
## \[2.5.1]
### Dependencies
diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml
index 6f9ec1d84..66530117b 100644
--- a/plugins/http/Cargo.toml
+++ b/plugins/http/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-http"
-version = "2.5.1"
+version = "2.5.2"
description = "Access an HTTP client written in Rust."
edition = { workspace = true }
authors = { workspace = true }
@@ -34,7 +34,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] }
-tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
+tauri-plugin-fs = { path = "../fs", version = "2.4.2" }
urlpattern = "0.3"
regex = "1"
http = "1"
diff --git a/plugins/http/package.json b/plugins/http/package.json
index 130057710..e897de2fb 100644
--- a/plugins/http/package.json
+++ b/plugins/http/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-http",
- "version": "2.5.1",
+ "version": "2.5.2",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/log/CHANGELOG.md b/plugins/log/CHANGELOG.md
index 2d18252fa..138f3c4f9 100644
--- a/plugins/log/CHANGELOG.md
+++ b/plugins/log/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## \[2.7.0]
+
+- [`625bb1c0`](https://github.com/tauri-apps/plugins-workspace/commit/625bb1c0965394b88522643731f78ccbcca84add) ([#2965](https://github.com/tauri-apps/plugins-workspace/pull/2965) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Re-export the log crate.
+
## \[2.6.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/log/Cargo.toml b/plugins/log/Cargo.toml
index 68dedd8a8..1cbe906ec 100644
--- a/plugins/log/Cargo.toml
+++ b/plugins/log/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-log"
-version = "2.6.0"
+version = "2.7.0"
description = "Configurable logging for your Tauri app."
authors = { workspace = true }
license = { workspace = true }
diff --git a/plugins/log/package.json b/plugins/log/package.json
index a71b2f44b..d96fed282 100644
--- a/plugins/log/package.json
+++ b/plugins/log/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-log",
- "version": "2.6.0",
+ "version": "2.7.0",
"description": "Configurable logging for your Tauri app.",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/log/src/commands.rs b/plugins/log/src/commands.rs
new file mode 100644
index 000000000..c402db76e
--- /dev/null
+++ b/plugins/log/src/commands.rs
@@ -0,0 +1,73 @@
+// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use std::collections::HashMap;
+
+use log::RecordBuilder;
+
+use crate::{LogLevel, WEBVIEW_TARGET};
+
+#[tauri::command]
+pub fn log(
+ level: LogLevel,
+ message: String,
+ location: Option<&str>,
+ file: Option<&str>,
+ line: Option,
+ key_values: Option>,
+) {
+ let level = log::Level::from(level);
+
+ let target = if let Some(location) = location {
+ format!("{WEBVIEW_TARGET}:{location}")
+ } else {
+ WEBVIEW_TARGET.to_string()
+ };
+
+ let mut builder = RecordBuilder::new();
+ builder.level(level).target(&target).file(file).line(line);
+
+ let key_values = key_values.unwrap_or_default();
+ let mut kv = HashMap::new();
+ for (k, v) in key_values.iter() {
+ kv.insert(k.as_str(), v.as_str());
+ }
+ builder.key_values(&kv);
+ #[cfg(feature = "tracing")]
+ emit_trace(level, &message, location, file, line, &kv);
+
+ log::logger().log(&builder.args(format_args!("{message}")).build());
+}
+
+// Target becomes default and location is added as a parameter
+#[cfg(feature = "tracing")]
+fn emit_trace(
+ level: log::Level,
+ message: &String,
+ location: Option<&str>,
+ file: Option<&str>,
+ line: Option,
+ kv: &HashMap<&str, &str>,
+) {
+ macro_rules! emit_event {
+ ($level:expr) => {
+ tracing::event!(
+ target: WEBVIEW_TARGET,
+ $level,
+ message = %message,
+ location = location,
+ file,
+ line,
+ ?kv
+ )
+ };
+ }
+ match level {
+ log::Level::Error => emit_event!(tracing::Level::ERROR),
+ log::Level::Warn => emit_event!(tracing::Level::WARN),
+ log::Level::Info => emit_event!(tracing::Level::INFO),
+ log::Level::Debug => emit_event!(tracing::Level::DEBUG),
+ log::Level::Trace => emit_event!(tracing::Level::TRACE),
+ }
+}
diff --git a/plugins/log/src/lib.rs b/plugins/log/src/lib.rs
index de5c5d54e..c0642d417 100644
--- a/plugins/log/src/lib.rs
+++ b/plugins/log/src/lib.rs
@@ -10,12 +10,10 @@
)]
use fern::{Filter, FormatCallback};
-use log::{logger, RecordBuilder};
use log::{LevelFilter, Record};
use serde::Serialize;
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::borrow::Cow;
-use std::collections::HashMap;
use std::{
fmt::Arguments,
fs::{self, File},
@@ -30,6 +28,9 @@ use tauri::{AppHandle, Emitter};
use time::{macros::format_description, OffsetDateTime};
pub use fern;
+pub use log;
+
+mod commands;
pub const WEBVIEW_TARGET: &str = "webview";
@@ -206,70 +207,6 @@ impl Target {
}
}
-// Target becomes default and location is added as a parameter
-#[cfg(feature = "tracing")]
-fn emit_trace(
- level: log::Level,
- message: &String,
- location: Option<&str>,
- file: Option<&str>,
- line: Option,
- kv: &HashMap<&str, &str>,
-) {
- macro_rules! emit_event {
- ($level:expr) => {
- tracing::event!(
- target: WEBVIEW_TARGET,
- $level,
- message = %message,
- location = location,
- file,
- line,
- ?kv
- )
- };
- }
- match level {
- log::Level::Error => emit_event!(tracing::Level::ERROR),
- log::Level::Warn => emit_event!(tracing::Level::WARN),
- log::Level::Info => emit_event!(tracing::Level::INFO),
- log::Level::Debug => emit_event!(tracing::Level::DEBUG),
- log::Level::Trace => emit_event!(tracing::Level::TRACE),
- }
-}
-
-#[tauri::command]
-fn log(
- level: LogLevel,
- message: String,
- location: Option<&str>,
- file: Option<&str>,
- line: Option,
- key_values: Option>,
-) {
- let level = log::Level::from(level);
-
- let target = if let Some(location) = location {
- format!("{WEBVIEW_TARGET}:{location}")
- } else {
- WEBVIEW_TARGET.to_string()
- };
-
- let mut builder = RecordBuilder::new();
- builder.level(level).target(&target).file(file).line(line);
-
- let key_values = key_values.unwrap_or_default();
- let mut kv = HashMap::new();
- for (k, v) in key_values.iter() {
- kv.insert(k.as_str(), v.as_str());
- }
- builder.key_values(&kv);
- #[cfg(feature = "tracing")]
- emit_trace(level, &message, location, file, line, &kv);
-
- logger().log(&builder.args(format_args!("{message}")).build());
-}
-
pub struct Builder {
dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy,
@@ -528,7 +465,7 @@ impl Builder {
}
fn plugin_builder() -> plugin::Builder {
- plugin::Builder::new("log").invoke_handler(tauri::generate_handler![log])
+ plugin::Builder::new("log").invoke_handler(tauri::generate_handler![commands::log])
}
#[allow(clippy::type_complexity)]
diff --git a/plugins/nfc/CHANGELOG.md b/plugins/nfc/CHANGELOG.md
index 23faf01a4..70a9b3f07 100644
--- a/plugins/nfc/CHANGELOG.md
+++ b/plugins/nfc/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## \[2.3.1]
+
+- [`fe23a5e0`](https://github.com/tauri-apps/plugins-workspace/commit/fe23a5e01399a6ad61426bf8a94a6bb97227cf88) ([#2885](https://github.com/tauri-apps/plugins-workspace/pull/2885) by [@zaphim12](https://github.com/tauri-apps/plugins-workspace/../../zaphim12)) On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
+
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/nfc/Cargo.toml b/plugins/nfc/Cargo.toml
index 8111239c7..1cf4f020f 100644
--- a/plugins/nfc/Cargo.toml
+++ b/plugins/nfc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-nfc"
-version = "2.3.0"
+version = "2.3.1"
description = "Read and write NFC tags on Android and iOS."
edition = { workspace = true }
authors = { workspace = true }
diff --git a/plugins/nfc/package.json b/plugins/nfc/package.json
index 97ee99782..423f45e20 100644
--- a/plugins/nfc/package.json
+++ b/plugins/nfc/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-nfc",
- "version": "2.3.0",
+ "version": "2.3.1",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/notification/CHANGELOG.md b/plugins/notification/CHANGELOG.md
index dd4dcdee3..3adc96e50 100644
--- a/plugins/notification/CHANGELOG.md
+++ b/plugins/notification/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## \[2.3.1]
+
+- [`8abb31ee`](https://github.com/tauri-apps/plugins-workspace/commit/8abb31ee59c68197102c0aa699d690b34646ec3c) ([#2905](https://github.com/tauri-apps/plugins-workspace/pull/2905) by [@ChristianPavilonis](https://github.com/tauri-apps/plugins-workspace/../../ChristianPavilonis)) Fix notification scheduling on iOS.
+- [`2d03e2ea`](https://github.com/tauri-apps/plugins-workspace/commit/2d03e2eac2c19ad997d81d23836ab6a219252ffb) ([#2678](https://github.com/tauri-apps/plugins-workspace/pull/2678) by [@Keerthi421](https://github.com/tauri-apps/plugins-workspace/../../Keerthi421)) Added sound support for desktop notifications which was previously only available on mobile platforms.
+
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/notification/Cargo.toml b/plugins/notification/Cargo.toml
index 10ef6267a..ad51b2655 100644
--- a/plugins/notification/Cargo.toml
+++ b/plugins/notification/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-notification"
-version = "2.3.0"
+version = "2.3.1"
description = "Send desktop and mobile notifications on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
diff --git a/plugins/notification/README.md b/plugins/notification/README.md
index b42d6f2f1..e17efe028 100644
--- a/plugins/notification/README.md
+++ b/plugins/notification/README.md
@@ -95,6 +95,45 @@ export async function enqueueNotification(title, body) {
}
```
+### Notification with Sound
+
+You can add sound to your notifications on all platforms (desktop and mobile):
+
+```javascript
+import { sendNotification } from '@tauri-apps/plugin-notification'
+import { platform } from '@tauri-apps/api/os'
+
+// Basic notification with sound
+sendNotification({
+ title: 'New Message',
+ body: 'You have a new message',
+ sound: 'notification.wav' // Path to sound file
+})
+
+// Platform-specific sounds
+async function sendPlatformSpecificNotification() {
+ const platformName = platform()
+
+ let soundPath
+ if (platformName === 'darwin') {
+ // On macOS: use system sounds or sound files in the app bundle
+ soundPath = 'Ping' // macOS system sound
+ } else if (platformName === 'linux') {
+ // On Linux: use XDG theme sounds or file paths
+ soundPath = 'message-new-instant' // XDG theme sound
+ } else {
+ // On Windows: use file paths
+ soundPath = 'notification.wav'
+ }
+
+ sendNotification({
+ title: 'Platform-specific Notification',
+ body: 'This notification uses platform-specific sound',
+ sound: soundPath
+ })
+}
+```
+
## Contributing
PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
diff --git a/plugins/notification/guest-js/index.ts b/plugins/notification/guest-js/index.ts
index 9f81a1e18..685c60c20 100644
--- a/plugins/notification/guest-js/index.ts
+++ b/plugins/notification/guest-js/index.ts
@@ -71,7 +71,13 @@ interface Options {
*/
groupSummary?: boolean
/**
- * The sound resource name. Only available on mobile.
+ * The sound resource name or file path for the notification.
+ *
+ * Platform specific behavior:
+ * - On macOS: use system sounds (e.g., "Ping", "Blow") or sound files in the app bundle
+ * - On Linux: use XDG theme sounds (e.g., "message-new-instant") or file paths
+ * - On Windows: use file paths to sound files (.wav format)
+ * - On Mobile: use resource names
*/
sound?: string
/**
diff --git a/plugins/notification/ios/Sources/Notification.swift b/plugins/notification/ios/Sources/Notification.swift
index adba05ec2..259399b8d 100644
--- a/plugins/notification/ios/Sources/Notification.swift
+++ b/plugins/notification/ios/Sources/Notification.swift
@@ -38,10 +38,17 @@ func makeNotificationContent(_ notification: Notification) throws -> UNNotificat
arguments: nil)
}
- content.userInfo = [
- "__EXTRA__": notification.extra as Any,
- "__SCHEDULE__": notification.schedule as Any,
- ]
+ var userInfo: [String: Any] = [:]
+
+ if let extra = notification.extra {
+ userInfo["__EXTRA__"] = extra
+ }
+
+ if let schedule = notification.schedule {
+ userInfo["__SCHEDULE__"] = scheduleToDictionary(schedule)
+ }
+
+ content.userInfo = userInfo
if let actionTypeId = notification.actionTypeId {
content.categoryIdentifier = actionTypeId
@@ -66,6 +73,56 @@ func makeNotificationContent(_ notification: Notification) throws -> UNNotificat
return content
}
+func scheduleToDictionary(_ schedule: NotificationSchedule) -> [String: Any] {
+ switch schedule {
+ case .at(let date, let repeating):
+ return [
+ "type": "at",
+ "date": date,
+ "repeating": repeating
+ ]
+ case .interval(let interval):
+ return [
+ "type": "interval",
+ "interval": scheduleIntervalToDictionary(interval)
+ ]
+ case .every(let interval, let count):
+ return [
+ "type": "every",
+ "interval": interval.rawValue,
+ "count": count
+ ]
+ }
+}
+
+func scheduleIntervalToDictionary(_ interval: ScheduleInterval) -> [String: Any] {
+ var dict: [String: Any] = [:]
+
+ if let year = interval.year {
+ dict["year"] = year
+ }
+ if let month = interval.month {
+ dict["month"] = month
+ }
+ if let day = interval.day {
+ dict["day"] = day
+ }
+ if let weekday = interval.weekday {
+ dict["weekday"] = weekday
+ }
+ if let hour = interval.hour {
+ dict["hour"] = hour
+ }
+ if let minute = interval.minute {
+ dict["minute"] = minute
+ }
+ if let second = interval.second {
+ dict["second"] = second
+ }
+
+ return dict
+}
+
func makeAttachments(_ attachments: [NotificationAttachment]) throws -> [UNNotificationAttachment] {
var createdAttachments = [UNNotificationAttachment]()
diff --git a/plugins/notification/package.json b/plugins/notification/package.json
index d1908ebbf..52a7ac604 100644
--- a/plugins/notification/package.json
+++ b/plugins/notification/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-notification",
- "version": "2.3.0",
+ "version": "2.3.1",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/notification/src/desktop.rs b/plugins/notification/src/desktop.rs
index 47279225a..4ceb83088 100644
--- a/plugins/notification/src/desktop.rs
+++ b/plugins/notification/src/desktop.rs
@@ -39,6 +39,9 @@ impl crate::NotificationBuilder {
if let Some(icon) = self.data.icon {
notification = notification.icon(icon);
}
+ if let Some(sound) = self.data.sound {
+ notification = notification.sound(sound);
+ }
#[cfg(feature = "windows7-compat")]
{
notification.notify(&self.app)?;
@@ -102,6 +105,8 @@ mod imp {
title: Option,
/// The notification icon.
icon: Option,
+ /// The notification sound.
+ sound: Option,
/// The notification identifier
identifier: String,
}
@@ -136,6 +141,13 @@ mod imp {
self
}
+ /// Sets the notification sound file.
+ #[must_use]
+ pub fn sound(mut self, sound: impl Into) -> Self {
+ self.sound = Some(sound.into());
+ self
+ }
+
/// Shows the notification.
///
/// # Examples
@@ -177,6 +189,9 @@ mod imp {
} else {
notification.auto_icon();
}
+ if let Some(sound) = self.sound {
+ notification.sound_name(&sound);
+ }
#[cfg(windows)]
{
let exe = tauri::utils::platform::current_exe()?;
@@ -250,6 +265,7 @@ mod imp {
}
}
+ /// Shows the notification on Windows 7.
#[cfg(all(windows, feature = "windows7-compat"))]
fn notify_win7(self, app: &tauri::AppHandle) -> crate::Result<()> {
let app_ = app.clone();
diff --git a/plugins/notification/src/lib.rs b/plugins/notification/src/lib.rs
index 9ca33d63a..8b79c8730 100644
--- a/plugins/notification/src/lib.rs
+++ b/plugins/notification/src/lib.rs
@@ -132,7 +132,7 @@ impl NotificationBuilder {
self
}
- /// The sound resource name. Only available on mobile.
+ /// The sound resource name for the notification.
pub fn sound(mut self, sound: impl Into) -> Self {
self.data.sound.replace(sound.into());
self
diff --git a/plugins/opener/CHANGELOG.md b/plugins/opener/CHANGELOG.md
index 6244fa1ce..94c5c1bc0 100644
--- a/plugins/opener/CHANGELOG.md
+++ b/plugins/opener/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## \[2.5.0]
+
+### enhance
+
+- [`b8056f48`](https://github.com/tauri-apps/plugins-workspace/commit/b8056f484c7144af095d4d6ded1e8adbb9b8a865) ([#2897](https://github.com/tauri-apps/plugins-workspace/pull/2897) by [@petersamokhin](https://github.com/tauri-apps/plugins-workspace/../../petersamokhin)) Allow reveal multiple items in the file explorer.
+
## \[2.4.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/opener/Cargo.toml b/plugins/opener/Cargo.toml
index 323914e5e..bccbcec75 100644
--- a/plugins/opener/Cargo.toml
+++ b/plugins/opener/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-opener"
-version = "2.4.0"
+version = "2.5.0"
description = "Open files and URLs using their default application."
edition = { workspace = true }
authors = { workspace = true }
@@ -35,8 +35,6 @@ tauri = { workspace = true }
thiserror = { workspace = true }
open = { version = "5", features = ["shellexecute-on-windows"] }
glob = { workspace = true }
-
-[target."cfg(windows)".dependencies]
dunce = { workspace = true }
[target."cfg(windows)".dependencies.windows]
diff --git a/plugins/opener/README.md b/plugins/opener/README.md
index 02050e932..bcb9265ca 100644
--- a/plugins/opener/README.md
+++ b/plugins/opener/README.md
@@ -75,6 +75,10 @@ await openPath('/path/to/file', 'firefox')
// Reveal a path with the system's default explorer
await revealItemInDir('/path/to/file')
+
+// Reveal multiple paths with the system's default explorer
+// Note: will be renamed to `revealItemsInDir` in the next major version
+await revealItemInDir(['/path/to/file', '/path/to/another/file'])
```
### Usage from Rust
@@ -102,6 +106,9 @@ fn main() {
// Reveal a path with the system's default explorer
opener.reveal_item_in_dir("/path/to/file")?;
+
+ // Reveal multiple paths with the system's default explorer
+ opener.reveal_items_in_dir(["/path/to/file"])?;
Ok(())
})
.run(tauri::generate_context!())
diff --git a/plugins/opener/api-iife.js b/plugins/opener/api-iife.js
index 30415a61e..dd976e576 100644
--- a/plugins/opener/api-iife.js
+++ b/plugins/opener/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},_){return window.__TAURI_INTERNALS__.invoke(n,e,_)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,_){await e("plugin:opener|open_path",{path:n,with:_})},n.openUrl=async function(n,_){await e("plugin:opener|open_url",{url:n,with:_})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{path:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},r){return window.__TAURI_INTERNALS__.invoke(n,e,r)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,r){await e("plugin:opener|open_path",{path:n,with:r})},n.openUrl=async function(n,r){await e("plugin:opener|open_url",{url:n,with:r})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{paths:"string"==typeof n?[n]:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
diff --git a/plugins/opener/guest-js/index.ts b/plugins/opener/guest-js/index.ts
index b73ef5381..6b40da19c 100644
--- a/plugins/opener/guest-js/index.ts
+++ b/plugins/opener/guest-js/index.ts
@@ -86,12 +86,14 @@ export async function openPath(path: string, openWith?: string): Promise {
* ```typescript
* import { revealItemInDir } from '@tauri-apps/plugin-opener';
* await revealItemInDir('/path/to/file');
+ * await revealItemInDir([ '/path/to/file', '/path/to/another/file' ]);
* ```
*
* @param path The path to reveal.
*
* @since 2.0.0
*/
-export async function revealItemInDir(path: string) {
- return invoke('plugin:opener|reveal_item_in_dir', { path })
+export async function revealItemInDir(path: string | string[]): Promise {
+ const paths = typeof path === 'string' ? [path] : path
+ return invoke('plugin:opener|reveal_item_in_dir', { paths })
}
diff --git a/plugins/opener/guest-js/init.ts b/plugins/opener/guest-js/init.ts
index 6f81141a2..4c2b631fb 100644
--- a/plugins/opener/guest-js/init.ts
+++ b/plugins/opener/guest-js/init.ts
@@ -46,10 +46,8 @@ window.addEventListener('click', function (evt) {
// return early if
if (
- // same origin (internal navigation)
- url.origin === window.location.origin
// not default protocols
- || ['http:', 'https:', 'mailto:', 'tel:'].every((p) => url.protocol !== p)
+ ['http:', 'https:', 'mailto:', 'tel:'].every((p) => url.protocol !== p)
)
return
diff --git a/plugins/opener/package.json b/plugins/opener/package.json
index 274c5e031..b979726d0 100644
--- a/plugins/opener/package.json
+++ b/plugins/opener/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-opener",
- "version": "2.4.0",
+ "version": "2.5.0",
"description": "Open files and URLs using their default application.",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/opener/src/commands.rs b/plugins/opener/src/commands.rs
index b00d5306e..e22b41d41 100644
--- a/plugins/opener/src/commands.rs
+++ b/plugins/opener/src/commands.rs
@@ -69,7 +69,8 @@ pub async fn open_path(
}
}
+/// TODO: in the next major version, rename to `reveal_items_in_dir`
#[tauri::command]
-pub async fn reveal_item_in_dir(path: PathBuf) -> crate::Result<()> {
- crate::reveal_item_in_dir(path)
+pub async fn reveal_item_in_dir(paths: Vec) -> crate::Result<()> {
+ crate::reveal_items_in_dir(&paths)
}
diff --git a/plugins/opener/src/error.rs b/plugins/opener/src/error.rs
index 157922fc6..c5a4dde3c 100644
--- a/plugins/opener/src/error.rs
+++ b/plugins/opener/src/error.rs
@@ -31,6 +31,9 @@ pub enum Error {
Win32Error(#[from] windows::core::Error),
#[error("Path doesn't have a parent: {0}")]
NoParent(PathBuf),
+ #[cfg(windows)]
+ #[error("Failed to convert path '{0}' to ITEMIDLIST")]
+ FailedToConvertPathToItemIdList(PathBuf),
#[error("Failed to convert path to file:// url")]
FailedToConvertPathToFileUrl,
#[error(transparent)]
diff --git a/plugins/opener/src/init-iife.js b/plugins/opener/src/init-iife.js
index 51f6f0684..3a9f95b7e 100644
--- a/plugins/opener/src/init-iife.js
+++ b/plugins/opener/src/init-iife.js
@@ -1 +1 @@
-!function(){"use strict";"function"==typeof SuppressedError&&SuppressedError,window.addEventListener("click",(function(e){if(e.defaultPrevented||0!==e.button||e.metaKey||e.altKey)return;const t=e.composedPath().find((e=>e instanceof Node&&"A"===e.nodeName.toUpperCase()));if(!t||!t.href||"_blank"!==t.target&&!e.ctrlKey&&!e.shiftKey)return;const n=new URL(t.href);n.origin===window.location.origin||["http:","https:","mailto:","tel:"].every((e=>n.protocol!==e))||(e.preventDefault(),async function(e,t={},n){window.__TAURI_INTERNALS__.invoke(e,t,n)}("plugin:opener|open_url",{url:n}))}))}();
+!function(){"use strict";"function"==typeof SuppressedError&&SuppressedError,window.addEventListener("click",(function(e){if(e.defaultPrevented||0!==e.button||e.metaKey||e.altKey)return;const t=e.composedPath().find((e=>e instanceof Node&&"A"===e.nodeName.toUpperCase()));if(!t||!t.href||"_blank"!==t.target&&!e.ctrlKey&&!e.shiftKey)return;const n=new URL(t.href);["http:","https:","mailto:","tel:"].every((e=>n.protocol!==e))||(e.preventDefault(),async function(e,t={},n){window.__TAURI_INTERNALS__.invoke(e,t,n)}("plugin:opener|open_url",{url:n}))}))}();
diff --git a/plugins/opener/src/lib.rs b/plugins/opener/src/lib.rs
index 6bf0e5b28..343e91558 100644
--- a/plugins/opener/src/lib.rs
+++ b/plugins/opener/src/lib.rs
@@ -25,7 +25,7 @@ pub use error::Error;
type Result = std::result::Result;
pub use open::{open_path, open_url};
-pub use reveal_item_in_dir::reveal_item_in_dir;
+pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir};
pub struct Opener {
// we use `fn() -> R` to silence the unused generic error
@@ -146,7 +146,15 @@ impl Opener {
}
pub fn reveal_item_in_dir>(&self, p: P) -> Result<()> {
- crate::reveal_item_in_dir::reveal_item_in_dir(p)
+ reveal_item_in_dir(p)
+ }
+
+ pub fn reveal_items_in_dir(&self, paths: I) -> Result<()>
+ where
+ I: IntoIterator- ,
+ P: AsRef
,
+ {
+ reveal_items_in_dir(paths)
}
}
@@ -213,7 +221,7 @@ impl Builder {
.invoke_handler(tauri::generate_handler![
commands::open_url,
commands::open_path,
- commands::reveal_item_in_dir
+ commands::reveal_item_in_dir,
]);
if self.open_js_links_on_click {
diff --git a/plugins/opener/src/reveal_item_in_dir.rs b/plugins/opener/src/reveal_item_in_dir.rs
index 6e3dfc2c8..6112fb8b1 100644
--- a/plugins/opener/src/reveal_item_in_dir.rs
+++ b/plugins/opener/src/reveal_item_in_dir.rs
@@ -10,7 +10,7 @@ use std::path::Path;
///
/// - **Android / iOS:** Unsupported.
pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> {
- let path = path.as_ref().canonicalize()?;
+ let path = dunce::canonicalize(path.as_ref())?;
#[cfg(any(
windows,
@@ -21,7 +21,47 @@ pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> {
target_os = "netbsd",
target_os = "openbsd"
))]
- return imp::reveal_item_in_dir(&path);
+ return imp::reveal_items_in_dir(&[path]);
+
+ #[cfg(not(any(
+ windows,
+ target_os = "macos",
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ )))]
+ Err(crate::Error::UnsupportedPlatform)
+}
+
+/// Reveal the paths the system's default explorer.
+///
+/// ## Platform-specific:
+///
+/// - **Android / iOS:** Unsupported.
+pub fn reveal_items_in_dir(paths: I) -> crate::Result<()>
+where
+ I: IntoIterator- ,
+ P: AsRef
,
+{
+ let mut canonicalized = vec![];
+
+ for path in paths {
+ let path = dunce::canonicalize(path.as_ref())?;
+ canonicalized.push(path);
+ }
+
+ #[cfg(any(
+ windows,
+ target_os = "macos",
+ target_os = "linux",
+ target_os = "dragonfly",
+ target_os = "freebsd",
+ target_os = "netbsd",
+ target_os = "openbsd"
+ ))]
+ return imp::reveal_items_in_dir(&canonicalized);
#[cfg(not(any(
windows,
@@ -37,8 +77,10 @@ pub fn reveal_item_in_dir>(path: P) -> crate::Result<()> {
#[cfg(windows)]
mod imp {
- use super::*;
+ use std::collections::HashMap;
+ use std::path::{Path, PathBuf};
+ use windows::Win32::UI::Shell::Common::ITEMIDLIST;
use windows::{
core::{w, HSTRING, PCWSTR},
Win32::{
@@ -54,57 +96,98 @@ mod imp {
},
};
- pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
- let file = dunce::simplified(path);
+ pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
+ if paths.is_empty() {
+ return Ok(());
+ }
+
+ let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new();
+ for path in paths {
+ let parent = path
+ .parent()
+ .ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?;
+ grouped_paths.entry(parent).or_default().push(path);
+ }
let _ = unsafe { CoInitialize(None) };
- let dir = file
- .parent()
- .ok_or_else(|| crate::Error::NoParent(file.to_path_buf()))?;
-
- let dir = HSTRING::from(dir);
- let dir_item = unsafe { ILCreateFromPathW(&dir) };
-
- let file_h = HSTRING::from(file);
- let file_item = unsafe { ILCreateFromPathW(&file_h) };
-
- unsafe {
- if let Err(e) = SHOpenFolderAndSelectItems(dir_item, Some(&[file_item]), 0) {
+ for (parent, to_reveals) in grouped_paths {
+ let parent_item_id_list = OwnedItemIdList::new(parent)?;
+ let to_reveals_item_id_list = to_reveals
+ .iter()
+ .map(|to_reveal| OwnedItemIdList::new(*to_reveal))
+ .collect::>>()?;
+ if let Err(e) = unsafe {
+ SHOpenFolderAndSelectItems(
+ parent_item_id_list.item,
+ Some(
+ &to_reveals_item_id_list
+ .iter()
+ .map(|item| item.item)
+ .collect::>(),
+ ),
+ 0,
+ )
+ } {
// from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
// On some systems, the above call mysteriously fails with "file not
// found" even though the file is there. In these cases, ShellExecute()
// seems to work as a fallback (although it won't select the file).
+ //
+ // Note: we only handle the first file here if multiple of are present
if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
- let is_dir = file.is_dir();
+ let first_path = to_reveals[0];
+ let is_dir = first_path.is_dir();
let mut info = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::() as _,
nShow: SW_SHOWNORMAL.0,
- lpFile: PCWSTR(dir.as_ptr()),
+ lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()),
lpClass: if is_dir { w!("folder") } else { PCWSTR::null() },
lpVerb: if is_dir {
w!("explore")
} else {
PCWSTR::null()
},
- ..std::mem::zeroed()
+ ..Default::default()
};
- ShellExecuteExW(&mut info).inspect_err(|_| {
- ILFree(Some(dir_item));
- ILFree(Some(file_item));
- })?;
+ unsafe { ShellExecuteExW(&mut info) }?;
}
}
}
- unsafe {
- ILFree(Some(dir_item));
- ILFree(Some(file_item));
- }
-
Ok(())
}
+
+ struct OwnedItemIdList {
+ hstring: HSTRING,
+ item: *const ITEMIDLIST,
+ }
+
+ impl OwnedItemIdList {
+ fn new(path: &Path) -> crate::Result {
+ let path_hstring = HSTRING::from(path);
+ let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) };
+ if item_id_list.is_null() {
+ Err(crate::Error::FailedToConvertPathToItemIdList(
+ path.to_owned(),
+ ))
+ } else {
+ Ok(Self {
+ hstring: path_hstring,
+ item: item_id_list,
+ })
+ }
+ }
+ }
+
+ impl Drop for OwnedItemIdList {
+ fn drop(&mut self) {
+ if !self.item.is_null() {
+ unsafe { ILFree(Some(self.item)) };
+ }
+ }
+ }
}
#[cfg(any(
@@ -115,24 +198,36 @@ mod imp {
target_os = "openbsd"
))]
mod imp {
-
- use std::collections::HashMap;
-
use super::*;
+ use std::collections::HashMap;
+ use std::path::PathBuf;
- pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
+ pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
let connection = zbus::blocking::Connection::session()?;
- reveal_with_filemanager1(path, &connection)
- .or_else(|_| reveal_with_open_uri_portal(path, &connection))
+ reveal_with_filemanager1(paths, &connection).or_else(|e| {
+ // Fallback to opening the directory of the first item if revealing multiple items fails.
+ if let Some(first_path) = paths.first() {
+ reveal_with_open_uri_portal(first_path, &connection)
+ } else {
+ Err(e)
+ }
+ })
}
fn reveal_with_filemanager1(
- path: &Path,
+ paths: &[PathBuf],
connection: &zbus::blocking::Connection,
) -> crate::Result<()> {
- let uri = url::Url::from_file_path(path)
- .map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?;
+ let uris: Result, _> = paths
+ .iter()
+ .map(|path| {
+ url::Url::from_file_path(path)
+ .map_err(|_| crate::Error::FailedToConvertPathToFileUrl)
+ })
+ .collect();
+ let uris = uris?;
+ let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect();
#[zbus::proxy(
interface = "org.freedesktop.FileManager1",
@@ -145,7 +240,7 @@ mod imp {
let proxy = FileManager1ProxyBlocking::new(connection)?;
- proxy.ShowItems(vec![uri.as_str()], "")
+ proxy.ShowItems(uri_strs, "")
}
fn reveal_with_open_uri_portal(
@@ -177,14 +272,22 @@ mod imp {
#[cfg(target_os = "macos")]
mod imp {
- use super::*;
use objc2_app_kit::NSWorkspace;
use objc2_foundation::{NSArray, NSString, NSURL};
- pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
+ use std::path::PathBuf;
+
+ pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
unsafe {
- let path = path.to_string_lossy();
- let path = NSString::from_str(&path);
- let urls = vec![NSURL::fileURLWithPath(&path)];
+ let mut urls = Vec::new();
+
+ for path in paths {
+ let path = path.to_string_lossy();
+ let path = NSString::from_str(&path);
+ let url = NSURL::fileURLWithPath(&path);
+
+ urls.push(url);
+ }
+
let urls = NSArray::from_retained_slice(&urls);
let workspace = NSWorkspace::new();
diff --git a/plugins/os/CHANGELOG.md b/plugins/os/CHANGELOG.md
index 207832aba..0bce65325 100644
--- a/plugins/os/CHANGELOG.md
+++ b/plugins/os/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## \[2.3.1]
+
+- [`d3d290ab`](https://github.com/tauri-apps/plugins-workspace/commit/d3d290ab8a8913981a98e2eb7f2c5d4aba3bc36c) ([#2912](https://github.com/tauri-apps/plugins-workspace/pull/2912) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Unlocked version of `serialize-to-javascript` from `=0.1.1` to `^0.1.1` for compatibility with Tauri's upcoming version `2.8`.
+
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/os/Cargo.toml b/plugins/os/Cargo.toml
index c9be33304..09ee3d905 100644
--- a/plugins/os/Cargo.toml
+++ b/plugins/os/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-os"
-version = "2.3.0"
+version = "2.3.1"
description = "Read information about the operating system."
edition = { workspace = true }
authors = { workspace = true }
@@ -32,4 +32,4 @@ thiserror = { workspace = true }
os_info = "3"
sys-locale = "0.3"
gethostname = "1.0"
-serialize-to-javascript = "=0.1.1"
+serialize-to-javascript = "0.1.1"
diff --git a/plugins/os/package.json b/plugins/os/package.json
index cf4897e31..4e332a377 100644
--- a/plugins/os/package.json
+++ b/plugins/os/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-os",
- "version": "2.3.0",
+ "version": "2.3.1",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/persisted-scope/CHANGELOG.md b/plugins/persisted-scope/CHANGELOG.md
index 3b239419b..ca40e97a7 100644
--- a/plugins/persisted-scope/CHANGELOG.md
+++ b/plugins/persisted-scope/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## \[2.3.2]
+
+### Dependencies
+
+- Upgraded to `fs@2.4.2`
+
## \[2.3.1]
### Dependencies
diff --git a/plugins/persisted-scope/Cargo.toml b/plugins/persisted-scope/Cargo.toml
index eccff8662..5c8f08cd2 100644
--- a/plugins/persisted-scope/Cargo.toml
+++ b/plugins/persisted-scope/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-persisted-scope"
-version = "2.3.1"
+version = "2.3.2"
description = "Save filesystem and asset scopes and restore them when the app is reopened."
authors = { workspace = true }
license = { workspace = true }
@@ -27,7 +27,7 @@ log = { workspace = true }
thiserror = { workspace = true }
aho-corasick = "1"
bincode = "1"
-tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
+tauri-plugin-fs = { path = "../fs", version = "2.4.2" }
[features]
protocol-asset = ["tauri/protocol-asset"]
diff --git a/plugins/positioner/package.json b/plugins/positioner/package.json
index b3ecae5d1..dd75462a4 100644
--- a/plugins/positioner/package.json
+++ b/plugins/positioner/package.json
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/process/package.json b/plugins/process/package.json
index 1864ad5cb..ba40619e3 100644
--- a/plugins/process/package.json
+++ b/plugins/process/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/shell/CHANGELOG.md b/plugins/shell/CHANGELOG.md
index 39afdc9c3..5bbd3d8ea 100644
--- a/plugins/shell/CHANGELOG.md
+++ b/plugins/shell/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## \[2.3.1]
+
+- [`d865ed47`](https://github.com/tauri-apps/plugins-workspace/commit/d865ed47685c3923e894f7d10ee4c037507037e6) ([#2950](https://github.com/tauri-apps/plugins-workspace/pull/2950) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix sidecar with dots in the filename not working on Windows.
+
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/shell/Cargo.toml b/plugins/shell/Cargo.toml
index 24d27ff36..fee2ce2c3 100644
--- a/plugins/shell/Cargo.toml
+++ b/plugins/shell/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-shell"
-version = "2.3.0"
+version = "2.3.1"
description = "Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application."
edition = { workspace = true }
authors = { workspace = true }
diff --git a/plugins/shell/package.json b/plugins/shell/package.json
index 95d720d9f..839c86b62 100644
--- a/plugins/shell/package.json
+++ b/plugins/shell/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-shell",
- "version": "2.3.0",
+ "version": "2.3.1",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/shell/src/commands.rs b/plugins/shell/src/commands.rs
index 5275e9b9c..0facce719 100644
--- a/plugins/shell/src/commands.rs
+++ b/plugins/shell/src/commands.rs
@@ -115,7 +115,12 @@ fn prepare_cmd(
let mut command = if options.sidecar {
let program = PathBuf::from(program);
let program_as_string = program.display().to_string();
- let program_no_ext_as_string = program.with_extension("").display().to_string();
+ let has_extension = program.extension().is_some_and(|ext| ext == "exe");
+ let program_no_ext_as_string = if has_extension {
+ program.with_extension("").display().to_string()
+ } else {
+ program_as_string.clone()
+ };
let configured_sidecar = window
.config()
.bundle
diff --git a/plugins/shell/src/process/mod.rs b/plugins/shell/src/process/mod.rs
index 44f037b01..1384bbb8b 100644
--- a/plugins/shell/src/process/mod.rs
+++ b/plugins/shell/src/process/mod.rs
@@ -118,9 +118,23 @@ pub struct Output {
fn relative_command_path(command: &Path) -> crate::Result {
match platform::current_exe()?.parent() {
#[cfg(windows)]
- Some(exe_dir) => Ok(exe_dir.join(command).with_extension("exe")),
+ Some(exe_dir) => {
+ let mut command_path = exe_dir.join(command);
+ let already_exe = command_path.extension().is_some_and(|ext| ext == "exe");
+ if !already_exe {
+ // do not use with_extension to retain dots in the command filename
+ command_path.as_mut_os_string().push(".exe");
+ }
+ Ok(command_path)
+ }
#[cfg(not(windows))]
- Some(exe_dir) => Ok(exe_dir.join(command)),
+ Some(exe_dir) => {
+ let mut command_path = exe_dir.join(command);
+ if command_path.extension().is_some_and(|ext| ext == "exe") {
+ command_path.set_extension("");
+ }
+ Ok(command_path)
+ }
None => Err(crate::Error::CurrentExeHasNoParent),
}
}
@@ -133,6 +147,10 @@ impl From for StdCommand {
impl Command {
pub(crate) fn new>(program: S) -> Self {
+ log::debug!(
+ "Creating sidecar {}",
+ program.as_ref().to_str().unwrap_or("")
+ );
let mut command = StdCommand::new(program);
command.stdout(Stdio::piped());
@@ -451,9 +469,33 @@ fn spawn_pipe_reader) -> CommandEvent + Send + Copy + 'static>(
// tests for the commands functions.
#[cfg(test)]
mod tests {
- #[cfg(not(windows))]
use super::*;
+ #[test]
+ fn relative_command_path_resolves() {
+ let cwd_parent = platform::current_exe()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .to_owned();
+ assert_eq!(
+ relative_command_path(Path::new("Tauri.Example")).unwrap(),
+ cwd_parent.join(if cfg!(windows) {
+ "Tauri.Example.exe"
+ } else {
+ "Tauri.Example"
+ })
+ );
+ assert_eq!(
+ relative_command_path(Path::new("Tauri.Example.exe")).unwrap(),
+ cwd_parent.join(if cfg!(windows) {
+ "Tauri.Example.exe"
+ } else {
+ "Tauri.Example"
+ })
+ );
+ }
+
#[cfg(not(windows))]
#[test]
fn test_cmd_spawn_output() {
diff --git a/plugins/single-instance/CHANGELOG.md b/plugins/single-instance/CHANGELOG.md
index f2ac4031f..e0c65a475 100644
--- a/plugins/single-instance/CHANGELOG.md
+++ b/plugins/single-instance/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## \[2.3.4]
+
+### Dependencies
+
+- Upgraded to `deep-link@2.4.3`
+
+## \[2.3.3]
+
+### Dependencies
+
+- Upgraded to `deep-link@2.4.2`
+
## \[2.3.2]
### Dependencies
diff --git a/plugins/single-instance/Cargo.toml b/plugins/single-instance/Cargo.toml
index 24ff572bd..5486fd642 100644
--- a/plugins/single-instance/Cargo.toml
+++ b/plugins/single-instance/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-single-instance"
-version = "2.3.2"
+version = "2.3.4"
description = "Ensure a single instance of your tauri app is running."
authors = { workspace = true }
license = { workspace = true }
@@ -26,7 +26,7 @@ serde_json = { workspace = true }
tauri = { workspace = true }
tracing = { workspace = true }
thiserror = { workspace = true }
-tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.1", optional = true }
+tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.3", optional = true }
semver = { version = "1", optional = true }
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
diff --git a/plugins/single-instance/examples/vanilla/package.json b/plugins/single-instance/examples/vanilla/package.json
index b7d778ccb..fe9120dd2 100644
--- a/plugins/single-instance/examples/vanilla/package.json
+++ b/plugins/single-instance/examples/vanilla/package.json
@@ -9,6 +9,6 @@
"author": "",
"license": "MIT",
"devDependencies": {
- "@tauri-apps/cli": "2.7.1"
+ "@tauri-apps/cli": "2.8.4"
}
}
diff --git a/plugins/sql/package.json b/plugins/sql/package.json
index be6d89ed2..afbb6bd99 100644
--- a/plugins/sql/package.json
+++ b/plugins/sql/package.json
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/store/CHANGELOG.md b/plugins/store/CHANGELOG.md
index f9c466e4a..7e7d5de4f 100644
--- a/plugins/store/CHANGELOG.md
+++ b/plugins/store/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## \[2.4.0]
+
+- [`5ac8fbb1`](https://github.com/tauri-apps/plugins-workspace/commit/5ac8fbb1fa76714aa8cc9c0d74e0aebab12f9755) ([#2857](https://github.com/tauri-apps/plugins-workspace/pull/2857) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Allow setting defaults from the JavaScript API
+- [`5ac8fbb1`](https://github.com/tauri-apps/plugins-workspace/commit/5ac8fbb1fa76714aa8cc9c0d74e0aebab12f9755) ([#2857](https://github.com/tauri-apps/plugins-workspace/pull/2857) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
+
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
diff --git a/plugins/store/Cargo.toml b/plugins/store/Cargo.toml
index 4b27a8e47..b82ffa33d 100644
--- a/plugins/store/Cargo.toml
+++ b/plugins/store/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-store"
-version = "2.3.0"
+version = "2.4.0"
description = "Simple, persistent key-value store."
authors = { workspace = true }
license = { workspace = true }
diff --git a/plugins/store/examples/AppSettingsManager/package.json b/plugins/store/examples/AppSettingsManager/package.json
index fab6f1aa4..0b74f6801 100644
--- a/plugins/store/examples/AppSettingsManager/package.json
+++ b/plugins/store/examples/AppSettingsManager/package.json
@@ -8,8 +8,8 @@
"tauri": "tauri"
},
"devDependencies": {
- "@tauri-apps/cli": "2.7.1",
+ "@tauri-apps/cli": "2.8.4",
"typescript": "^5.7.3",
- "vite": "^7.0.4"
+ "vite": "^7.0.7"
}
}
diff --git a/plugins/store/guest-js/index.ts b/plugins/store/guest-js/index.ts
index 49853f11e..776894a1b 100644
--- a/plugins/store/guest-js/index.ts
+++ b/plugins/store/guest-js/index.ts
@@ -407,7 +407,7 @@ interface IStore {
* Note:
* - This method loads the data and merges it with the current store,
* this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
- * to fully match the store with the on-disk state, set {@linkcode ReloadOptions.ignoreDefaults} to `true`
+ * to fully match the store with the on-disk state, set {@linkcode ReloadOptions | ignoreDefaults} to `true`
* - This method does not emit change events.
*
* @returns
diff --git a/plugins/store/package.json b/plugins/store/package.json
index d26973d16..288e1495c 100644
--- a/plugins/store/package.json
+++ b/plugins/store/package.json
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-store",
- "version": "2.3.0",
+ "version": "2.4.0",
"description": "Simple, persistent key-value store.",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/stronghold/package.json b/plugins/stronghold/package.json
index 58d474648..f5f01aa1d 100644
--- a/plugins/stronghold/package.json
+++ b/plugins/stronghold/package.json
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/updater/package.json b/plugins/updater/package.json
index 3da537828..d3f314d42 100644
--- a/plugins/updater/package.json
+++ b/plugins/updater/package.json
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
- "@tauri-apps/api": "^2.6.0"
+ "@tauri-apps/api": "^2.8.0"
}
}
diff --git a/plugins/updater/src/error.rs b/plugins/updater/src/error.rs
index b82e7d551..c44f98627 100644
--- a/plugins/updater/src/error.rs
+++ b/plugins/updater/src/error.rs
@@ -39,9 +39,14 @@ pub enum Error {
/// `reqwest` crate errors.
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
- /// The platform was not found on the updater JSON response.
- #[error("the platform `{0}` was not found on the response `platforms` object")]
+ /// The platform was not found in the updater JSON response.
+ #[error("the platform `{0}` was not found in the response `platforms` object")]
TargetNotFound(String),
+ /// Neither the platform nor the fallback platform was found in the updater JSON response.
+ #[error(
+ "None of the fallback platforms `{0:?}` were found in the response `platforms` object"
+ )]
+ TargetsNotFound(Vec),
/// Download failed
#[error("`{0}`")]
Network(String),
@@ -69,6 +74,8 @@ pub enum Error {
AuthenticationFailed,
#[error("Failed to install .deb package")]
DebInstallFailed,
+ #[error("Failed to install package")]
+ PackageInstallFailed,
#[error("invalid updater binary format")]
InvalidUpdaterFormat,
#[error(transparent)]
diff --git a/plugins/updater/src/updater.rs b/plugins/updater/src/updater.rs
index 707c14895..28c420ca8 100644
--- a/plugins/updater/src/updater.rs
+++ b/plugins/updater/src/updater.rs
@@ -26,7 +26,13 @@ use reqwest::{
};
use semver::Version;
use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize};
-use tauri::{utils::platform::current_exe, AppHandle, Resource, Runtime};
+use tauri::{
+ utils::{
+ config::BundleType,
+ platform::{bundle_type, current_exe},
+ },
+ AppHandle, Resource, Runtime,
+};
use time::OffsetDateTime;
use url::Url;
@@ -37,6 +43,31 @@ use crate::{
const UPDATER_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
+#[derive(Copy, Clone)]
+pub enum Installer {
+ AppImage,
+ Deb,
+ Rpm,
+
+ App,
+
+ Msi,
+ Nsis,
+}
+
+impl Installer {
+ fn name(self) -> &'static str {
+ match self {
+ Self::AppImage => "appimage",
+ Self::Deb => "deb",
+ Self::Rpm => "rpm",
+ Self::App => "app",
+ Self::Msi => "msi",
+ Self::Nsis => "nsis",
+ }
+ }
+}
+
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct ReleaseManifestPlatform {
/// Download URL for the platform
@@ -265,13 +296,7 @@ impl UpdaterBuilder {
return Err(Error::EmptyEndpoints);
};
- let arch = get_updater_arch().ok_or(Error::UnsupportedArch)?;
- let (target, json_target) = if let Some(target) = self.target {
- (target.clone(), target)
- } else {
- let target = get_updater_target().ok_or(Error::UnsupportedOs)?;
- (target.to_string(), format!("{target}-{arch}"))
- };
+ let arch = updater_arch().ok_or(Error::UnsupportedArch)?;
let executable_path = self.executable_path.clone().unwrap_or(current_exe()?);
@@ -294,8 +319,7 @@ impl UpdaterBuilder {
installer_args: self.installer_args,
current_exe_args: self.current_exe_args,
arch,
- target,
- json_target,
+ target: self.target,
headers: self.headers,
extract_path,
on_before_exit: self.on_before_exit,
@@ -327,10 +351,9 @@ pub struct Updater {
proxy: Option,
endpoints: Vec,
arch: &'static str,
- // The `{{target}}` variable we replace in the endpoint
- target: String,
- // The value we search if the updater server returns a JSON with the `platforms` object
- json_target: String,
+ // The `{{target}}` variable we replace in the endpoint and serach for in the JSON,
+ // this is either the user provided target or the current operating system by default
+ target: Option,
headers: HeaderMap,
extract_path: PathBuf,
on_before_exit: Option