Compare commits

..

32 Commits

Author SHA1 Message Date
Lucas Nogueira 57f6e45b27 Merge branch 'v2' into feat/setup-single-instance-manually 2026-02-05 10:29:41 -03:00
Lucas Nogueira 35aa52f45e fix(single-instance): dbus_path should start with /, can't include - 2026-02-05 10:28:58 -03:00
Lucas Nogueira b65a193e6d feat(single-instance): add setup() function to run flow separately 2026-02-05 10:28:15 -03:00
renovate[bot] bbc177150f chore(deps): update dependency prettier to v3.8.1 (#3235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:37:55 +08:00
renovate[bot] e6e2edca11 chore(deps): update dependency typescript-eslint to v8.54.0 (#3232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:08:54 +08:00
renovate[bot] 7ecd19da51 chore(deps): update dependency rollup to v4.57.1 (#3230)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-04 23:08:24 +08:00
dependabot[bot] a0b6c8ff3b chore(deps): bump bytes (#3248)
Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.7.1 to 1.11.1.
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/bytes/compare/v1.7.1...v1.11.1)

---
updated-dependencies:
- dependency-name: bytes
  dependency-version: 1.11.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 23:07:49 +08:00
renovate[bot] c12ea9306a chore(deps): update rust crate bytes to v1.11.1 [security] (#3247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 23:53:44 +01:00
Demir Yerli 98e2c11eef fix(single-instance): unconventional dbus names (fixes #3184) (#3194)
Co-authored-by: FabianLars <github@fabianlars.de>
Co-authored-by: Amr Bashir <github@amrbashir.me>
2026-02-03 23:36:48 +01:00
github-actions[bot] 50b159f668 Publish New Versions (v2) (#3211)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-03 12:18:37 +01:00
Fabian-Lars 305c4f6b4e ci: add workflow_dipatch trigger to integration tests 2026-02-03 10:55:10 +01:00
Lucas Fernandes Nogueira 89f3e17952 chore: bump tauri to 2.10 (#3246)
* chore: bump tauri to 2.10

* pnpm i

* audit fix
2026-02-02 21:48:08 -03:00
Ale Sanchez d4613ff002 fix(shell): Make sidecars work in tests (fix #13767) (#3234) 2026-01-26 17:12:42 +01:00
Tony 69a9d5771c chore(updater): use keep from tempfile 3.20 (#3229) 2026-01-22 11:40:43 +08:00
Del 61e9b0ab64 fix(http): ensure body resources are released on stream cancellation (#3228)
Co-authored-by: Amr Bashir <github@amrbashir.me>
2026-01-22 05:20:29 +02:00
dependabot[bot] 25ad21beff chore(deps): bump diff from 5.2.0 to 5.2.2 (#3226)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: FabianLars <github@fabianlars.de>
2026-01-20 20:30:35 +01:00
Madhusudhan 2dc3f3f039 fix(sql): uuid columns returning null in postgres query results (#2039) (#3144)
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-19 17:30:28 +01:00
Lucas Fernandes Nogueira c27af9128d fix(barcode-scanner): check if iOS platform supports scanning (#3222) 2026-01-19 13:16:31 -03:00
Lucas Fernandes Nogueira b60dd88702 fix(barcode-scanner): start capture session on a separate thread (#3223)
fixes the given warning:

Thread Performance Checker: -[AVCaptureSession startRunning] should be called from background thread. Calling it on the main thread can lead to UI unresponsiveness

which might cause a UI freeze
2026-01-19 13:16:16 -03:00
renovate[bot] a97033bcd9 chore(deps): update dependency rollup to v4.55.1 (#3201)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 16:44:07 +01:00
miaoshengkun 4375c98bed feat(updater): add no_proxy config to disable system proxy (#3073)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Fabian-Lars <github@fabianlars.de>
2026-01-19 16:17:37 +01:00
Fabian-Lars 82fbb0c790 fix(barcode-scanner): early return scanner if missing permissions (#3221) 2026-01-19 10:21:33 -03:00
Tony de6bf68585 docs(store): fix outdated fn link (#3220) 2026-01-19 12:21:55 +01:00
renovate[bot] 734c627084 chore(deps): update dependency prettier to v3.8.0 (#3217) 2026-01-18 10:54:16 +08:00
Tony 05c5da072b chore(deps): update reqwest to 0.13 for updater (#3213) 2026-01-16 18:37:20 +08:00
renovate[bot] 0d126ff0ad chore(deps): update dependency typescript-eslint to v8.53.0 (v2) (#3189)
* chore(deps): update dependency typescript-eslint to v8.53.0

* Update api-iife.js
2026-01-16 18:36:41 +08:00
FabianLars f122ee98c6 Revert "temp: delete updater changefiles"
This reverts commit 82c404635b.
2026-01-14 20:48:59 +01:00
github-actions[bot] 2308f2299d publish new versions (#3203)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-14 20:48:22 +01:00
FabianLars 82c404635b temp: delete updater changefiles 2026-01-14 20:30:54 +01:00
Fabian-Lars b1dbee2c55 fix(http): manually set origin header to tauri://localhost (#3210) 2026-01-14 20:26:17 +01:00
Andrew de Waal d7a0bb325d feat(dialog) - Support fileAccessMode for open dialog (#3030) (#3136)
* feat(dialog) - Support fileAccessMode for open dialog (#3030)

On iOS, when trying to access a file that exists outside of the app sandbox, one of 2 things need to happen to be able to perform any operations on said file:

* A copy of the file needs to be made to the internal app sandbox
* The method startAccessingSecurityScopedResource needs to be called.

Previously, a copy of the file was always being made when a file was selected through the picker dialog.

While this did ensure there were no file access exceptions when reading from the file, it does not scale well for large files.

To resolve this, we now support `fileAccessMode`, which allows a file handle to be returned without copying the file to the app sandbox.

This MR only supports this change for iOS; MacOS has a different set of needs for security scoped resources.

See discussion in #3716 for more discussion of the difference between iOS and MacOS.
See MR #3185 to see how these scoped files will be accessible using security scoping.

* fmt, clippy

* use enum

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
2026-01-14 08:35:06 -03:00
FabianLars f3d75f7abb Revert "temp: remove updater changefiles"
This reverts commit 9a2c98f450.
2026-01-08 15:54:33 +01:00
78 changed files with 1425 additions and 938 deletions
@@ -0,0 +1,9 @@
---
"single-instance": minor:fix
---
**Breaking Change:** On Linux, the DBus ID/name will now be `<bundle-id>.SingleInstance` instead of `org.<bundle_id_underscores>.SingleInstance` to follow DBus specifications.
This will break the single-instance mechanism across different app versions if the app was installed multiple times.
Added `dbus_id` builder method, which can be used to restore previous behavior. For a bundle identifier of `com.tauri.my-example` this would be `dbus_id("org.com_tauri_my_example")`.
@@ -0,0 +1,5 @@
---
"single-instance": patch
---
Add `setup` function to run the single instance initialization manually, without waiting for the tauri setup hook to run.
+1
View File
@@ -5,6 +5,7 @@
name: integration tests
on:
workflow_dispatch:
push:
branches:
- v1
+1
View File
@@ -21,6 +21,7 @@ target/
package-lock.json
yarn.lock
bun.lockb
bun.lock
# rust compiled folders
target/
Generated
+204 -74
View File
@@ -207,7 +207,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "api"
version = "2.0.39"
version = "2.0.41"
dependencies = [
"log",
"serde",
@@ -810,9 +810,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.10.1"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
dependencies = [
"serde",
]
@@ -2304,7 +2304,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e"
dependencies = [
"rustix 1.0.5",
"rustix 1.1.3",
"windows-targets 0.52.6",
]
@@ -2714,13 +2714,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "1.6.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-util",
"futures-core",
"h2",
"http",
"http-body",
@@ -2728,6 +2729,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"pin-utils",
"smallvec",
"tokio",
"want",
@@ -2770,17 +2772,21 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.11"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"tokio",
@@ -2814,9 +2820,9 @@ dependencies = [
[[package]]
name = "ico"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98"
checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
dependencies = [
"byteorder",
"png",
@@ -3099,6 +3105,16 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is-docker"
version = "0.2.0"
@@ -3320,9 +3336,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.172"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libflate"
@@ -3411,9 +3427,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
@@ -5084,6 +5100,48 @@ dependencies = [
"windows-registry 0.4.0",
]
[[package]]
name = "reqwest"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
dependencies = [
"base64 0.22.1",
"bytes",
"futures-core",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pki-types",
"rustls-platform-verifier",
"serde",
"serde_json",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
]
[[package]]
name = "rfc6979"
version = "0.4.0"
@@ -5276,22 +5334,22 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.5"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
"linux-raw-sys 0.11.0",
"windows-sys 0.60.2",
]
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
dependencies = [
"once_cell",
"ring",
@@ -5310,7 +5368,7 @@ dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework 3.2.0",
"security-framework 3.5.1",
]
[[package]]
@@ -5332,10 +5390,37 @@ dependencies = [
]
[[package]]
name = "rustls-webpki"
version = "0.103.1"
name = "rustls-platform-verifier"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
dependencies = [
"core-foundation 0.10.0",
"core-foundation-sys",
"jni",
"log",
"once_cell",
"rustls",
"rustls-native-certs",
"rustls-platform-verifier-android",
"rustls-webpki",
"security-framework 3.5.1",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.60.2",
]
[[package]]
name = "rustls-platform-verifier-android"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
@@ -5478,9 +5563,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "3.2.0"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
dependencies = [
"bitflags 2.9.0",
"core-foundation 0.10.0",
@@ -5491,9 +5576,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.14.0"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
@@ -5528,10 +5613,11 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.219"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
@@ -5547,10 +5633,19 @@ dependencies = [
]
[[package]]
name = "serde_derive"
version = "1.0.219"
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -5947,6 +6042,7 @@ dependencies = [
"tokio-stream",
"tracing",
"url",
"uuid",
"webpki-roots",
]
@@ -6029,6 +6125,7 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tracing",
"uuid",
"whoami",
]
@@ -6067,6 +6164,7 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tracing",
"uuid",
"whoami",
]
@@ -6093,6 +6191,7 @@ dependencies = [
"time",
"tracing",
"url",
"uuid",
]
[[package]]
@@ -6385,9 +6484,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.9.4"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15524fc7959bfcaa051ba6d0b3fb1ef18e978de2176c7c6acb977f7fd14d35c7"
checksum = "e19299a5f2ccb6bcb2a38b1eb7999e8bbdbd5bdd3cf74a4f8ab412726cbd65e7"
dependencies = [
"anyhow",
"bytes",
@@ -6415,7 +6514,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
"reqwest",
"reqwest 0.13.1",
"serde",
"serde_json",
"serde_repr",
@@ -6440,9 +6539,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.5.3"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
checksum = "76809f63061c8b25537b87f46b8733b31397cf419706dc874e1602be6479ba90"
dependencies = [
"anyhow",
"cargo_toml",
@@ -6464,9 +6563,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.5.2"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf"
checksum = "8b2ebe49d690ccaea93aa81fff99277d4f445968f085ba13be67859151e9e4b8"
dependencies = [
"base64 0.22.1",
"ico",
@@ -6490,9 +6589,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.5.2"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde"
checksum = "1119f651b0187c686c0fc72c66bba311e560e1b5f61869086ce788d43be6cf41"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -6504,9 +6603,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.4.0"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f"
dependencies = [
"anyhow",
"glob",
@@ -6533,7 +6632,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-barcode-scanner"
version = "2.4.3"
version = "2.4.4"
dependencies = [
"log",
"serde",
@@ -6603,7 +6702,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.5.0"
version = "2.6.0"
dependencies = [
"log",
"raw-window-handle",
@@ -6680,14 +6779,14 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.5.5"
version = "2.5.7"
dependencies = [
"bytes",
"cookie_store",
"data-url",
"http",
"regex",
"reqwest",
"reqwest 0.12.15",
"schemars",
"serde",
"serde_json",
@@ -6843,7 +6942,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-shell"
version = "2.3.4"
version = "2.3.5"
dependencies = [
"encoding_rs",
"log",
@@ -6877,7 +6976,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-sql"
version = "2.3.1"
version = "2.3.2"
dependencies = [
"futures-core",
"indexmap 2.9.0",
@@ -6890,6 +6989,7 @@ dependencies = [
"thiserror 2.0.12",
"time",
"tokio",
"uuid",
]
[[package]]
@@ -6929,7 +7029,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-updater"
version = "2.9.0"
version = "2.10.0"
dependencies = [
"base64 0.22.1",
"dirs 6.0.0",
@@ -6941,7 +7041,8 @@ dependencies = [
"minisign-verify",
"osakit",
"percent-encoding",
"reqwest",
"reqwest 0.13.1",
"rustls",
"semver",
"serde",
"serde_json",
@@ -6965,7 +7066,7 @@ dependencies = [
"log",
"mockito",
"read-progress-stream",
"reqwest",
"reqwest 0.12.15",
"serde",
"serde_json",
"tauri",
@@ -7008,9 +7109,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.9.2"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892"
checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651"
dependencies = [
"cookie",
"dpi",
@@ -7033,9 +7134,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.9.2"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7950f3bde6bcca6655bc5e76d3d6ec587ceb81032851ab4ddbe1f508bdea2729"
checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314"
dependencies = [
"gtk",
"http",
@@ -7060,9 +7161,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.8.1"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490"
checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e"
dependencies = [
"aes-gcm",
"anyhow",
@@ -7122,15 +7223,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.19.1"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.2",
"once_cell",
"rustix 1.0.5",
"windows-sys 0.59.0",
"rustix 1.1.3",
"windows-sys 0.60.2",
]
[[package]]
@@ -7498,6 +7599,24 @@ dependencies = [
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags 2.9.0",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
@@ -7815,12 +7934,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.16.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.3.2",
"serde",
"js-sys",
"serde_core",
"wasm-bindgen",
]
[[package]]
@@ -8101,9 +8222,9 @@ dependencies = [
[[package]]
name = "webkit2gtk"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
dependencies = [
"bitflags 1.3.2",
"cairo-rs",
@@ -8125,9 +8246,9 @@ dependencies = [
[[package]]
name = "webkit2gtk-sys"
version = "2.0.1"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
dependencies = [
"bitflags 1.3.2",
"cairo-sys-rs",
@@ -8143,6 +8264,15 @@ dependencies = [
"system-deps",
]
[[package]]
name = "webpki-root-certs"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "webpki-roots"
version = "0.26.8"
@@ -8824,9 +8954,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wry"
version = "0.53.5"
version = "0.54.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2"
checksum = "5ed1a195b0375491dd15a7066a10251be217ce743cf4bbbbdcf5391d6473bee0"
dependencies = [
"base64 0.22.1",
"block2 0.6.2",
@@ -8932,7 +9062,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
dependencies = [
"libc",
"rustix 1.0.5",
"rustix 1.1.3",
]
[[package]]
+4 -4
View File
@@ -12,10 +12,10 @@ resolver = "2"
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
log = "0.4"
tauri = { version = "2.9.3", default-features = false }
tauri-build = "2.4"
tauri-plugin = "2.4"
tauri-utils = "2.7"
tauri = { version = "2.10", default-features = false }
tauri-build = "2.5"
tauri-plugin = "2.5"
tauri-utils = "2.8"
serde_json = "1"
thiserror = "2"
url = "2"
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## \[2.0.37]
### Dependencies
- Upgraded to `updater-js@2.10.0`
- Upgraded to `barcode-scanner-js@2.4.4`
- Upgraded to `http-js@2.5.7`
- Upgraded to `shell-js@2.3.5`
## \[2.0.36]
### Dependencies
- Upgraded to `http-js@2.5.6`
- Upgraded to `dialog-js@2.6.0`
## \[2.0.35]
### Dependencies
+8 -8
View File
@@ -1,7 +1,7 @@
{
"name": "api",
"private": true,
"version": "2.0.35",
"version": "2.0.37",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
@@ -10,25 +10,25 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "2.9.1",
"@tauri-apps/plugin-barcode-scanner": "^2.4.3",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-barcode-scanner": "^2.4.4",
"@tauri-apps/plugin-biometric": "^2.3.2",
"@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.5.0",
"@tauri-apps/plugin-dialog": "^2.6.0",
"@tauri-apps/plugin-fs": "^2.4.5",
"@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-haptics": "^2.2.0",
"@tauri-apps/plugin-http": "^2.5.5",
"@tauri-apps/plugin-http": "^2.5.7",
"@tauri-apps/plugin-nfc": "^2.3.4",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.4",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "^2.4.2",
"@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-updater": "^2.10.0",
"@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11"
},
@@ -36,7 +36,7 @@
"@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tauri-apps/cli": "2.9.6",
"@tauri-apps/cli": "2.10.0",
"@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4",
"unocss": "^66.3.3",
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## \[2.0.41]
### Dependencies
- Upgraded to `updater@2.10.0`
- Upgraded to `barcode-scanner@2.4.4`
- Upgraded to `http@2.5.7`
- Upgraded to `shell@2.3.5`
## \[2.0.40]
### Dependencies
- Upgraded to `http@2.5.6`
- Upgraded to `dialog@2.6.0`
## \[2.0.39]
### Dependencies
+6 -6
View File
@@ -1,7 +1,7 @@
[package]
name = "api"
publish = false
version = "2.0.39"
version = "2.0.41"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = { workspace = true }
@@ -25,18 +25,18 @@ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.5", features =
"watch",
] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.5.0" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.6.0" }
tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart",
"cookies",
], version = "2.5.5" }
], version = "2.5.7" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.3", features = [
"windows7-compat",
] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.2" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.1" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.3" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.4" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.5" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.2" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
@@ -57,11 +57,11 @@ features = [
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.10.0" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.3" }
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.4" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.4" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.2" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.2" }
+62 -48
View File
@@ -8,7 +8,8 @@
let filter = null;
let multiple = false;
let directory = false;
let pickerMode = "";
let pickerMode = "document";
let fileAccessMode = "scoped";
function arrayBufferToBase64(buffer, callback) {
var blob = new Blob([buffer], {
@@ -52,54 +53,60 @@
.catch(onMessage);
}
function openDialog() {
open({
title: "My wonderful open dialog",
defaultPath,
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
pickerMode: pickerMode === "" ? undefined : pickerMode,
})
.then(function (res) {
if (Array.isArray(res)) {
onMessage(res);
} else {
var pathToRead = res;
var isFile = pathToRead.match(/\S+\.\S+$/g);
readFile(pathToRead)
.then(function (response) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(response),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
} else {
onMessage(res);
}
} else {
onMessage(res);
}
})
.catch(onMessage);
}
async function openDialog() {
try {
var result = await open({
title: "My wonderful open dialog",
defaultPath,
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
pickerMode,
fileAccessMode,
})
.catch(onMessage);
if (Array.isArray(result)) {
onMessage(result);
} else {
var pathToRead = result;
var isFile = pathToRead.match(/\S+\.\S+$/g);
await readFile(pathToRead)
.then(function (res) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(res),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
} else {
// Convert byte array to UTF-8 string
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(new Uint8Array(res));
onMessage(text);
}
} else {
onMessage(res);
}
})
}
} catch(exception) {
onMessage(exception)
}
}
function saveDialog() {
@@ -154,6 +161,13 @@
<option value="document">Document</option>
</select>
</div>
<div>
<label for="dialog-file-access-mode">File Access Mode:</label>
<select id="dialog-file-access-mode" bind:value={fileAccessMode}>
<option value="copy">Copy</option>
<option value="scoped">Scoped</option>
</select>
</div>
<br />
<div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
+5 -4
View File
@@ -19,16 +19,17 @@
"eslint": "9.39.2",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "3.0.1",
"prettier": "3.7.4",
"rollup": "4.54.0",
"prettier": "3.8.1",
"rollup": "4.57.1",
"tslib": "2.8.1",
"typescript": "5.9.3",
"typescript-eslint": "8.50.1"
"typescript-eslint": "8.54.0"
},
"minimumReleaseAge": 4320,
"pnpm": {
"overrides": {
"esbuild@<0.25.0": ">=0.25.0"
"esbuild@<0.25.0": ">=0.25.0",
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23"
},
"onlyBuiltDependencies": [
"esbuild"
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.4.4]
- [`82fbb0c7`](https://github.com/tauri-apps/plugins-workspace/commit/82fbb0c790288eca72af9ade13828ded7700ff90) ([#3221](https://github.com/tauri-apps/plugins-workspace/pull/3221)) On iOS, fixed an application crash happening when the scanner was started when user denied permission before.
- [`b60dd887`](https://github.com/tauri-apps/plugins-workspace/commit/b60dd88702a2be2af942a3d104d728970a4c42d6) ([#3223](https://github.com/tauri-apps/plugins-workspace/pull/3223)) On iOS, start the scanning session on a separate thread to fix performance issues.
- [`c27af912`](https://github.com/tauri-apps/plugins-workspace/commit/c27af9128d6cc7a2424ec49e98005e68b2d0eb1a) ([#3222](https://github.com/tauri-apps/plugins-workspace/pull/3222)) On iOS, fixed an application crash happening when the scanner was started on the iOS Simulator (no camera available).
## \[2.4.3]
- [`d8bfe61f`](https://github.com/tauri-apps/plugins-workspace/commit/d8bfe61f20f235314bad93a9c50d8b7f3eade734) ([#3121](https://github.com/tauri-apps/plugins-workspace/pull/3121) by [@NKIPSC](https://github.com/tauri-apps/plugins-workspace/../../NKIPSC)) Remove unnecessary checks on Android when requesting camera permission.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-barcode-scanner"
version = "2.4.3"
version = "2.4.4"
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
edition = { workspace = true }
authors = { workspace = true }
@@ -261,27 +261,34 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
}
private func runScanner(_ invoke: Invoke, args: ScanOptions) {
if getPermissionState() != "granted" {
invoke.reject("Camera permission denied or not yet requested")
return
}
scanFormats = [AVMetadataObject.ObjectType]()
(args.formats ?? []).forEach { format in
if let formatValue = format.value {
scanFormats.append(formatValue)
} else {
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
return
}
if let formatValue = format.value {
scanFormats.append(formatValue)
} else {
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
return
}
}
if scanFormats.isEmpty {
for supportedFormat in SupportedFormat.allCases {
if let formatValue = supportedFormat.value {
scanFormats.append(formatValue)
}
for supportedFormat in SupportedFormat.allCases {
if let formatValue = supportedFormat.value {
scanFormats.append(formatValue)
}
}
}
self.metaOutput!.metadataObjectTypes = self.scanFormats
self.captureSession!.startRunning()
DispatchQueue.main.async {
self.captureSession!.startRunning()
}
self.isScanning = true
}
@@ -298,6 +305,13 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
return
}
// Check if camera is available on this platform (iOS simulator doesn't have cameras)
let availableVideoDevices = discoverCaptureDevices()
if availableVideoDevices.isEmpty {
invoke.reject("No camera available on this device (e.g., iOS Simulator)")
return
}
var iOS14min: Bool = false
if #available(iOS 14.0, *) { iOS14min = true }
if !iOS14min && self.getPermissionState() != "granted" {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-barcode-scanner",
"version": "2.4.3",
"version": "2.4.4",
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+3 -3
View File
@@ -10,12 +10,12 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "2.9.1",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-deep-link": "2.4.6"
},
"devDependencies": {
"@tauri-apps/cli": "2.9.6",
"@tauri-apps/cli": "2.10.0",
"typescript": "^5.7.3",
"vite": "^7.0.7"
"vite": "^7.3.1"
}
}
+2 -2
View File
@@ -25,9 +25,9 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
},
"devDependencies": {
"@tauri-apps/cli": "2.9.6"
"@tauri-apps/cli": "2.10.0"
}
}
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.6.0]
- [`d7a0bb32`](https://github.com/tauri-apps/plugins-workspace/commit/d7a0bb325dad919d6cc132eb3898c33540de77c4) ([#3136](https://github.com/tauri-apps/plugins-workspace/pull/3136) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `fileAccessMode` option to file picker.
## \[2.5.0]
- [`dff6fa98`](https://github.com/tauri-apps/plugins-workspace/commit/dff6fa986a9a05ba98b6ca660fea78ae97251fc2) ([#3034](https://github.com/tauri-apps/plugins-workspace/pull/3034) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `pickerMode` option to file picker (currently only used on iOS)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-dialog"
version = "2.5.0"
version = "2.6.0"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
+35
View File
@@ -76,6 +76,31 @@ interface OpenDialogOptions {
* On desktop, this option is ignored.
*/
pickerMode?: PickerMode
/**
* The file access mode of the dialog.
* If not provided, `copy` is used, which matches the behavior of the {@linkcode open} method before the introduction of this option.
*
* **Usage**
* If a file is opened with {@linkcode fileAccessMode: 'copy'}, it will be copied to the app's sandbox.
* This means the file can be read, edited, deleted, copied, or any other operation without any issues, since the file
* now belongs to the app.
* This also means that the caller has responsibility of deleting the file if this file is not meant to be retained
* in the app sandbox.
*
* If a file is opened with {@linkcode fileAccessMode: 'scoped'}, the file will remain in its original location
* and security-scoped access will be automatically managed by the system.
*
* **Note**
* This is specifically meant for document pickers on iOS or MacOS, in conjunction with [security scoped resources](https://developer.apple.com/documentation/foundation/nsurl/startaccessingsecurityscopedresource()).
*
* Why only document pickers, and not image or video pickers?
* The image and video pickers on iOS behave differently from the document pickers, and return [NSItemProvider](https://developer.apple.com/documentation/foundation/nsitemprovider) objects instead of file URLs.
* These are meant to be ephemeral (only available within the callback of the picker), and are not accessible outside of the callback.
* So for image and video pickers, the only way to access the file is to copy it to the app's sandbox, and this is the URL that is returned from this API.
* This means there is no provision for using `scoped` mode with image or video pickers.
* If an image or video picker is used, `copy` is always used.
*/
fileAccessMode?: FileAccessMode
}
/**
@@ -111,6 +136,16 @@ interface SaveDialogOptions {
*/
export type PickerMode = 'document' | 'media' | 'image' | 'video'
/**
* The file access mode of the dialog.
*
* - `copy`: copy/move the picked file to the app sandbox; no scoped access required.
* - `scoped`: keep file in place; security-scoped access is automatically managed.
*
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
*/
export type FileAccessMode = 'copy' | 'scoped'
/**
* Default buttons for a message dialog.
*
+26 -13
View File
@@ -34,6 +34,7 @@ struct FilePickerOptions: Decodable {
var filters: [Filter]?
var defaultPath: String?
var pickerMode: PickerMode?
var fileAccessMode: FileAccessMode?
}
struct SaveFileDialogOptions: Decodable {
@@ -41,6 +42,11 @@ struct SaveFileDialogOptions: Decodable {
var defaultPath: String?
}
enum FileAccessMode: String, Decodable {
case copy
case scoped
}
enum PickerMode: String, Decodable {
case document
case media
@@ -56,6 +62,7 @@ class DialogPlugin: Plugin {
override init() {
super.init()
filePickerController = FilePickerController(self)
}
@objc public func showFilePicker(_ invoke: Invoke) throws {
@@ -70,12 +77,13 @@ class DialogPlugin: Plugin {
case .error(let error):
invoke.reject(error)
}
}
}
if #available(iOS 14, *) {
let parsedTypes = parseFiltersOption(args.filters ?? [])
let mimeKinds = Set(parsedTypes.compactMap { $0.preferredMIMEType?.components(separatedBy: "/")[0] })
let mimeKinds = Set(
parsedTypes.compactMap { $0.preferredMIMEType?.components(separatedBy: "/")[0] })
let filtersIncludeImage = mimeKinds.contains("image")
let filtersIncludeVideo = mimeKinds.contains("video")
let filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
@@ -85,7 +93,8 @@ class DialogPlugin: Plugin {
if args.pickerMode == .media
|| args.pickerMode == .image
|| args.pickerMode == .video
|| (!filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo)) {
|| (!filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo))
{
DispatchQueue.main.async {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
@@ -107,8 +116,10 @@ class DialogPlugin: Plugin {
DispatchQueue.main.async {
// The UTType.item is the catch-all, allowing for any file type to be selected.
let contentTypes = parsedTypes.isEmpty ? [UTType.item] : parsedTypes
let picker: UIDocumentPickerViewController = UIDocumentPickerViewController(forOpeningContentTypes: contentTypes, asCopy: true)
let picker: UIDocumentPickerViewController = UIDocumentPickerViewController(
forOpeningContentTypes: contentTypes,
asCopy: args.fileAccessMode == .scoped ? false : true)
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
}
@@ -181,7 +192,7 @@ class DialogPlugin: Plugin {
}
}
}
return parsedTypes
}
@@ -203,14 +214,14 @@ class DialogPlugin: Plugin {
if !filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo) {
DispatchQueue.main.async {
let picker = UIImagePickerController()
picker.delegate = self.filePickerController
picker.delegate = self.filePickerController
if filtersIncludeImage && !filtersIncludeVideo {
picker.sourceType = .photoLibrary
}
if filtersIncludeImage && !filtersIncludeVideo {
picker.sourceType = .photoLibrary
}
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
} else {
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
@@ -234,7 +245,8 @@ class DialogPlugin: Plugin {
for filter in filters {
for ext in filter.extensions ?? [] {
guard
let utType: String = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String?
let utType: String = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassMIMEType, ext as CFString, nil)?.takeRetainedValue() as String?
else {
continue
}
@@ -292,6 +304,7 @@ class DialogPlugin: Plugin {
manager.viewController?.present(alert, animated: true, completion: nil)
}
}
}
@_cdecl("init_plugin_dialog")
@@ -95,16 +95,33 @@ public class FilePickerController: NSObject {
return nil
}
}
/// ## In which cases do we need to save a copy of a file selected by a user to the app sandbox?
/// In short, only when the file is **not** selected using UIDocumentPickerDelegate.
/// For the rest of the cases, we need to write a copy of the file to the app sandbox.
///
/// For PHPicker (used for photos and videos), `NSItemProvider.loadFileRepresentation` returns a temporary file URL that is deleted after the completion handler.
/// The recommendation is to [Persist](https://developer.apple.com/documentation/foundation/nsitemprovider/2888338-loadfilerepresentation) the file by moving/copying
/// it to your apps directory within the completion handler.
///
/// If available, `loadInPlaceFileRepresentation` can open a file in place; Photos assets typically do not support true in-place access,
/// so fall back to persisting a local file.
/// Ref: https://developer.apple.com/documentation/foundation/nsitemprovider/2888335-loadinplacefilerepresentation
///
/// For UIDocumentPicker, prefer "open in place" and avoid copying when possible.
/// Ref: https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
directory = cachesDirectory
}
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do {
try deleteFile(targetUrl)
}
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl
}
@@ -119,8 +136,7 @@ public class FilePickerController: NSObject {
extension FilePickerController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
let temporaryUrls = try urls.map { try saveTemporaryFile($0) }
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
self.plugin.onFilePickerEvent(.selected(urls))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
@@ -191,6 +207,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return
}
do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
@@ -212,6 +230,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return
}
do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-dialog",
"version": "2.5.0",
"version": "2.6.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+10 -2
View File
@@ -9,8 +9,9 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt;
use crate::{
Dialog, FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogButtons,
MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO, OK, YES,
Dialog, FileAccessMode, FileDialogBuilder, FilePath, MessageDialogBuilder,
MessageDialogButtons, MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO,
OK, YES,
};
#[derive(Serialize)]
@@ -63,6 +64,10 @@ pub struct OpenDialogOptions {
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
picker_mode: Option<PickerMode>,
/// The file access mode of the dialog.
#[serde(default)]
#[cfg_attr(mobile, allow(dead_code))]
file_access_mode: Option<FileAccessMode>,
}
/// The options for the save dialog API.
@@ -141,6 +146,9 @@ pub(crate) async fn open<R: Runtime>(
let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
}
if let Some(file_access_mode) = options.file_access_mode {
dialog_builder = dialog_builder.set_file_access_mode(file_access_mode);
}
let res = if options.directory {
#[cfg(desktop)]
+20 -1
View File
@@ -53,6 +53,13 @@ pub enum PickerMode {
Video,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum FileAccessMode {
Copy,
Scoped,
}
pub(crate) const OK: &str = "Ok";
pub(crate) const CANCEL: &str = "Cancel";
pub(crate) const YES: &str = "Yes";
@@ -191,7 +198,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
commands::save,
commands::message,
commands::ask,
commands::confirm
commands::confirm,
])
.setup(|app, api| {
#[cfg(mobile)]
@@ -379,6 +386,7 @@ pub struct FileDialogBuilder<R: Runtime> {
pub(crate) title: Option<String>,
pub(crate) can_create_directories: Option<bool>,
pub(crate) picker_mode: Option<PickerMode>,
pub(crate) file_access_mode: Option<FileAccessMode>,
#[cfg(desktop)]
pub(crate) parent: Option<crate::desktop::WindowHandle>,
}
@@ -391,6 +399,7 @@ pub(crate) struct FileDialogPayload<'a> {
filters: &'a Vec<Filter>,
multiple: bool,
picker_mode: &'a Option<PickerMode>,
file_access_mode: &'a Option<FileAccessMode>,
}
// raw window handle :(
@@ -407,6 +416,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
title: None,
can_create_directories: None,
picker_mode: None,
file_access_mode: None,
#[cfg(desktop)]
parent: None,
}
@@ -419,6 +429,7 @@ impl<R: Runtime> FileDialogBuilder<R> {
filters: &self.filters,
multiple,
picker_mode: &self.picker_mode,
file_access_mode: &self.file_access_mode,
}
}
@@ -488,6 +499,14 @@ impl<R: Runtime> FileDialogBuilder<R> {
self
}
/// Set the file access mode of the dialog.
/// This is only used on iOS.
/// On desktop and Android, this option is ignored.
pub fn set_file_access_mode(mut self, mode: FileAccessMode) -> Self {
self.file_access_mode.replace(mode);
self
}
/// Shows the dialog to select a single file.
///
/// This is not a blocking operation,
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+8
View File
@@ -1,5 +1,13 @@
# Changelog
## \[2.5.7]
- [`61e9b0ab`](https://github.com/tauri-apps/plugins-workspace/commit/61e9b0ab64c56cc5f7a5cb3b92b386671da6e0a2) ([#3228](https://github.com/tauri-apps/plugins-workspace/pull/3228)) Cleanup resource when the returned `ReadableStream.cancel` is called to avoid memory leaks
## \[2.5.6]
- [`b1dbee2c`](https://github.com/tauri-apps/plugins-workspace/commit/b1dbee2c55c1aa7b64732c4cc9f8cb20520b8666) ([#3210](https://github.com/tauri-apps/plugins-workspace/pull/3210) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the Origin header to always be `null` on macOS, iOS and Linux.
## \[2.5.5]
- [`e8915f17`](https://github.com/tauri-apps/plugins-workspace/commit/e8915f17e418138f0776870353cd6ce7254b0473) ([#2562](https://github.com/tauri-apps/plugins-workspace/pull/2562) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix aborting a request in the middle of a streaming response.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-http"
version = "2.5.5"
version = "2.5.7"
description = "Access an HTTP client written in Rust."
edition = { workspace = true }
authors = { workspace = true }
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r="Request cancelled";return e.fetch=async function(e,n){const a=n?.signal;if(a?.aborted)throw new Error(r);const o=n?.maxRedirections,s=n?.connectTimeout,i=n?.proxy,d=n?.danger;n&&(delete n.maxRedirections,delete n.connectTimeout,delete n.proxy,delete n.danger);const c=n?.headers?n.headers instanceof Headers?n.headers:new Headers(n.headers):new Headers,u=new Request(e,n),l=await u.arrayBuffer(),_=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of u.headers)c.get(e)||c.set(e,t);const h=(c instanceof Headers?Array.from(c.entries()):Array.isArray(c)?c:Object.entries(c)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(a?.aborted)throw new Error(r);const f=await t("plugin:http|fetch",{clientConfig:{method:u.method,url:u.url,headers:h,data:_,maxRedirections:o,connectTimeout:s,proxy:i,danger:d}}),p=()=>t("plugin:http|fetch_cancel",{rid:f});if(a?.aborted)throw p(),new Error(r);a?.addEventListener("abort",(()=>{p()}));const{status:w,statusText:y,url:g,headers:b,rid:T}=await t("plugin:http|fetch_send",{rid:f}),R=()=>t("plugin:http|fetch_cancel_body",{rid:T}),m=[101,103,204,205,304].includes(w)?null:new ReadableStream({start:e=>{a?.addEventListener("abort",(()=>{e.error(r),R()}))},pull:e=>(async e=>{let r;try{r=await t("plugin:http|fetch_read_body",{rid:T})}catch(t){return e.error(t),void R()}const n=new Uint8Array(r),a=n[n.byteLength-1],o=n.slice(0,n.byteLength-1);1!==a?e.enqueue(o):e.close()})(e)}),A=new Response(m,{status:w,statusText:y});return Object.defineProperty(A,"url",{value:g}),Object.defineProperty(A,"headers",{value:new Headers(b)}),A},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r="Request cancelled";return e.fetch=async function(e,n){const a=n?.signal;if(a?.aborted)throw new Error(r);const o=n?.maxRedirections,s=n?.connectTimeout,i=n?.proxy,d=n?.danger;n&&(delete n.maxRedirections,delete n.connectTimeout,delete n.proxy,delete n.danger);const c=n?.headers?n.headers instanceof Headers?n.headers:new Headers(n.headers):new Headers,u=new Request(e,n),l=await u.arrayBuffer(),_=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of u.headers)c.get(e)||c.set(e,t);const h=(c instanceof Headers?Array.from(c.entries()):Array.isArray(c)?c:Object.entries(c)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(a?.aborted)throw new Error(r);const f=await t("plugin:http|fetch",{clientConfig:{method:u.method,url:u.url,headers:h,data:_,maxRedirections:o,connectTimeout:s,proxy:i,danger:d}}),p=()=>t("plugin:http|fetch_cancel",{rid:f});if(a?.aborted)throw p(),new Error(r);a?.addEventListener("abort",(()=>{p()}));const{status:w,statusText:y,url:g,headers:b,rid:T}=await t("plugin:http|fetch_send",{rid:f}),R=()=>t("plugin:http|fetch_cancel_body",{rid:T}),m=[101,103,204,205,304].includes(w)?null:new ReadableStream({start:e=>{a?.addEventListener("abort",(()=>{e.error(r),R()}))},pull:e=>(async e=>{let r;try{r=await t("plugin:http|fetch_read_body",{rid:T})}catch(t){return e.error(t),void R()}const n=new Uint8Array(r),a=n[n.byteLength-1],o=n.slice(0,n.byteLength-1);1!==a?e.enqueue(o):e.close()})(e),cancel:()=>{R()}}),A=new Response(m,{status:w,statusText:y});return Object.defineProperty(A,"url",{value:g}),Object.defineProperty(A,"headers",{value:new Headers(b)}),A},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}
+5 -1
View File
@@ -275,7 +275,11 @@ export async function fetch(
void dropBody()
})
},
pull: (controller) => readChunk(controller)
pull: (controller) => readChunk(controller),
cancel: () => {
// Ensure body resources are released on stream cancellation
void dropBody()
}
})
const res = new Response(body, {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-http",
"version": "2.5.5",
"version": "2.5.7",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+8 -5
View File
@@ -279,7 +279,7 @@ pub async fn fetch<R: Runtime>(
if headers.contains_key(header::RANGE) {
// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 18
// If httpRequests header list contains `Range`, then append (`Accept-Encoding`, `identity`)
// If httpRequest's header list contains `Range`, then append (`Accept-Encoding`, `identity`)
headers.append(header::ACCEPT_ENCODING, HeaderValue::from_str("identity")?);
}
@@ -290,10 +290,13 @@ pub async fn fetch<R: Runtime>(
// ensure we have an Origin header set
if cfg!(not(feature = "unsafe-headers")) || !headers.contains_key(header::ORIGIN) {
if let Ok(url) = webview.url() {
headers.append(
header::ORIGIN,
HeaderValue::from_str(&url.origin().ascii_serialization())?,
);
// The url crate returns OpaqueOrigin for tauri://localhost which serializes to "null"
let origin = if url.scheme() == "tauri" {
"tauri://localhost".to_string()
} else {
url.origin().ascii_serialization()
};
headers.append(header::ORIGIN, HeaderValue::from_str(&origin)?);
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## \[2.3.5]
### bug
- [`d4613ff0`](https://github.com/tauri-apps/plugins-workspace/commit/d4613ff0029ea65f5c6895c3a6f8f5668dd1b55e) ([#3234](https://github.com/tauri-apps/plugins-workspace/pull/3234)) Make sidecars work in tests. Executable resolution is aware of the "deps" directory.
## \[2.3.4]
- [`31415eff`](https://github.com/tauri-apps/plugins-workspace/commit/31415effdf5a9ced19934a681cb044a732174088) ([#3183](https://github.com/tauri-apps/plugins-workspace/pull/3183) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Docs on example to Encoding usage in `Command::spawn`. No user facing changes.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-shell"
version = "2.3.4"
version = "2.3.5"
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 }
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-shell",
"version": "2.3.4",
"version": "2.3.5",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+32 -19
View File
@@ -118,27 +118,38 @@ pub struct Output {
}
fn relative_command_path(command: &Path) -> crate::Result<PathBuf> {
match platform::current_exe()?.parent() {
#[cfg(windows)]
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)
let exe_path = platform::current_exe()?;
let exe_dir = exe_path
.parent()
.ok_or(crate::Error::CurrentExeHasNoParent)?;
// If a test is being run, the executable is in the "deps" directory, so we need to go up one level.
let base_dir = if exe_dir.ends_with("deps") {
exe_dir.parent().unwrap_or(exe_dir)
} else {
exe_dir
};
let mut command_path = base_dir.join(command);
#[cfg(windows)]
{
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");
}
#[cfg(not(windows))]
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),
}
#[cfg(not(windows))]
{
if command_path.extension().is_some_and(|ext| ext == "exe") {
command_path.set_extension("");
}
}
Ok(command_path)
}
impl From<Command> for StdCommand {
@@ -508,6 +519,8 @@ mod tests {
.unwrap()
.parent()
.unwrap()
.parent() // Go up once more to get out of the "deps" directory
.unwrap()
.to_owned();
assert_eq!(
relative_command_path(Path::new("Tauri.Example")).unwrap(),
+4
View File
@@ -59,6 +59,10 @@ fn main() {
Note that currently, plugins run in the order they were added in to the builder, so make sure that this plugin is registered first.
## Usage with Flatpak/Snap
If you use Flatpak/Snap to publish your package and your Tauri identifier doesn't match the package id, set the `DBUS_ID` variable using the builder for the plugin, look at example.
## Contributing
PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
@@ -9,6 +9,6 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@tauri-apps/cli": "2.9.6"
"@tauri-apps/cli": "2.10.0"
}
}
@@ -9,9 +9,14 @@
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
println!("{}, {argv:?}, {cwd}", app.package_info().name);
}))
.plugin(
tauri_plugin_single_instance::Builder::new()
.callback(move |app, argv, cwd| {
println!("{}, {argv:?}, {cwd}", app.package_info().name);
})
.dbus_id("org.Tauri.SIExampleApp".to_owned())
.build(),
)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
+81 -9
View File
@@ -10,7 +10,7 @@
)]
#![cfg(not(any(target_os = "android", target_os = "ios")))]
use tauri::{plugin::TauriPlugin, AppHandle, Manager, Runtime};
use tauri::{plugin, plugin::TauriPlugin, AppHandle, Manager, RunEvent, Runtime};
#[cfg(target_os = "windows")]
#[path = "platform_impl/windows.rs"]
@@ -29,17 +29,89 @@ pub(crate) type SingleInstanceCallback<R> =
dyn FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static;
pub fn init<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
mut f: F,
f: F,
) -> TauriPlugin<R> {
platform_impl::init(Box::new(move |app, args, cwd| {
#[cfg(feature = "deep-link")]
if let Some(deep_link) = app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
deep_link.handle_cli_arguments(args.iter());
}
f(app, args, cwd)
}))
Builder::new().callback(f).build()
}
/// Runs the single-instance setup flow with the given app and callback.
/// Use this when you need to run single-instance from your own plugin or app setup.
/// On Linux, pass `dbus_id` (e.g. `Some("com.mycompany.myapp".into())`); on other platforms it is ignored.
pub fn setup<R: Runtime, F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
app: &AppHandle<R>,
callback: F,
dbus_id: Option<String>,
) -> Result<(), ()> {
platform_impl::setup_single_instance(app, Box::new(callback), dbus_id)
}
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
platform_impl::destroy(manager)
}
pub struct Builder<R: Runtime> {
callback: Box<SingleInstanceCallback<R>>,
dbus_id: Option<String>,
}
impl<R: Runtime> Default for Builder<R> {
fn default() -> Self {
Self {
callback: Box::new(move |_app, _args, _| {
#[cfg(feature = "deep-link")]
if let Some(deep_link) = _app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
deep_link.handle_cli_arguments(_args.iter());
}
}),
dbus_id: None,
}
}
}
impl<R: Runtime> Builder<R> {
pub fn new() -> Self {
Default::default()
}
/// Function to call when a secondary instance was opened by the user and killed by the plugin.
/// If the `deep-link` feature is enabled, the plugin triggers the deep-link plugin before executing the callback.
pub fn callback<F: FnMut(&AppHandle<R>, Vec<String>, String) + Send + Sync + 'static>(
mut self,
mut f: F,
) -> Self {
self.callback = Box::new(move |app, args, cwd| {
#[cfg(feature = "deep-link")]
if let Some(deep_link) = app.try_state::<tauri_plugin_deep_link::DeepLink<R>>() {
deep_link.handle_cli_arguments(args.iter());
}
f(app, args, cwd)
});
self
}
/// Set a custom D-Bus ID, used on Linux. The plugin will append a `.SingleInstance` subname.
/// For example `com.mycompany.myapp` will result in the plugin registering its D-Bus service on `com.mycompany.myapp.SingleInstance`.
/// Usually you want the same base ID across all components in your app.
///
/// Defaults to the app's bundle identifier set in tauri.conf.json.
pub fn dbus_id(mut self, dbus_id: impl Into<String>) -> Self {
self.dbus_id = Some(dbus_id.into());
self
}
pub fn build(self) -> TauriPlugin<R> {
let callback = self.callback;
let dbus_id = self.dbus_id;
plugin::Builder::new("single-instance")
.setup(move |app, _api| {
let _ = platform_impl::setup_single_instance(app, callback, dbus_id);
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
}
}
@@ -6,14 +6,8 @@
use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback;
use tauri::{
plugin::{self, TauriPlugin},
AppHandle, Config, Manager, RunEvent, Runtime,
};
use zbus::{
blocking::{connection::Builder, Connection},
interface,
};
use tauri::{AppHandle, Manager, Runtime};
use zbus::{blocking::Connection, interface, names::WellKnownName};
struct ConnectionHandle(Connection);
@@ -29,90 +23,80 @@ impl<R: Runtime> SingleInstanceDBus<R> {
}
}
#[cfg(feature = "semver")]
fn dbus_id(config: &Config, version: semver::Version) -> String {
let mut id = config.identifier.replace(['.', '-'], "_");
id.push('_');
id.push_str(semver_compat_string(version).as_str());
id
}
struct DBusName(String);
#[cfg(not(feature = "semver"))]
fn dbus_id(config: &Config) -> String {
config.identifier.replace(['.', '-'], "_")
}
pub fn setup_single_instance<R: Runtime>(
app: &AppHandle<R>,
callback: Box<SingleInstanceCallback<R>>,
dbus_id: Option<String>,
) -> Result<(), ()> {
let mut dbus_name = dbus_id.unwrap_or_else(|| app.config().identifier.clone());
pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
plugin::Builder::new("single-instance")
.setup(|app, _api| {
#[cfg(feature = "semver")]
let id = dbus_id(app.config(), app.package_info().version.clone());
#[cfg(not(feature = "semver"))]
let id = dbus_id(app.config());
#[cfg(feature = "semver")]
{
dbus_name.push('_');
dbus_name.push_str(semver_compat_string(&app.package_info().version).as_str());
}
let single_instance_dbus = SingleInstanceDBus {
callback: f,
app_handle: app.clone(),
};
let dbus_name = format!("org.{id}.SingleInstance");
let dbus_path = format!("/org/{id}/SingleInstance");
dbus_name.push_str(".SingleInstance");
match Builder::session()
.unwrap()
.name(dbus_name.as_str())
.unwrap()
.replace_existing_names(false)
.allow_name_replacements(false)
.serve_at(dbus_path.as_str(), single_instance_dbus)
.unwrap()
.build()
{
Ok(connection) => {
app.manage(ConnectionHandle(connection));
}
Err(zbus::Error::NameTaken) => {
if let Ok(connection) = Connection::session() {
let _ = connection.call_method(
Some(dbus_name.as_str()),
dbus_path.as_str(),
Some("org.SingleInstance.DBus"),
"ExecuteCallback",
&(
std::env::args().collect::<Vec<String>>(),
std::env::current_dir()
.unwrap_or_default()
.to_str()
.unwrap_or_default(),
),
);
}
app.cleanup_before_exit();
std::process::exit(0);
}
_ => {}
}
let mut dbus_path = dbus_name.replace('.', "/").replace('-', "_");
if !dbus_path.starts_with('/') {
dbus_path = format!("/{dbus_path}");
}
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
let single_instance_dbus = SingleInstanceDBus {
callback,
app_handle: app.clone(),
};
match zbus::blocking::connection::Builder::session()
.unwrap()
.name(dbus_name.as_str())
.unwrap()
.replace_existing_names(false)
.allow_name_replacements(false)
.serve_at(dbus_path.as_str(), single_instance_dbus)
.unwrap()
.build()
{
Ok(connection) => {
app.manage(ConnectionHandle(connection));
}
Err(zbus::Error::NameTaken) => {
if let Ok(connection) = Connection::session() {
let _ = connection.call_method(
Some(dbus_name.as_str()),
dbus_path.as_str(),
Some("org.SingleInstance.DBus"),
"ExecuteCallback",
&(
std::env::args().collect::<Vec<String>>(),
std::env::current_dir()
.unwrap_or_default()
.to_str()
.unwrap_or_default(),
),
);
}
app.cleanup_before_exit();
std::process::exit(0);
}
_ => {}
}
app.manage(DBusName(dbus_name));
Ok(())
}
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
if let Some(connection) = manager.try_state::<ConnectionHandle>() {
#[cfg(feature = "semver")]
let id = dbus_id(
manager.config(),
manager.app_handle().package_info().version.clone(),
);
#[cfg(not(feature = "semver"))]
let id = dbus_id(manager.config());
let dbus_name = format!("org.{id}.SingleInstance",);
let _ = connection.0.release_name(dbus_name);
if let Some(dbus_name) = manager
.try_state::<DBusName>()
.and_then(|name| WellKnownName::try_from(name.0.clone()).ok())
{
let _ = connection.0.release_name(dbus_name);
}
}
}
@@ -11,45 +11,37 @@ use std::{
#[cfg(feature = "semver")]
use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback;
use tauri::{
plugin::{self, TauriPlugin},
AppHandle, Config, Manager, RunEvent, Runtime,
};
use tauri::{AppHandle, Config, Manager, Runtime};
pub fn init<R: Runtime>(cb: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
plugin::Builder::new("single-instance")
.setup(|app, _api| {
let socket = socket_path(app.config(), app.package_info());
pub fn setup_single_instance<R: Runtime>(
app: &AppHandle<R>,
cb: Box<SingleInstanceCallback<R>>,
_dbus_id: Option<String>,
) -> Result<(), ()> {
let socket = socket_path(app.config(), app.package_info());
// Notify the singleton which may or may not exist.
match notify_singleton(&socket) {
Ok(_) => {
std::process::exit(0);
// Notify the singleton which may or may not exist.
match notify_singleton(&socket) {
Ok(_) => {
std::process::exit(0);
}
Err(e) => {
match e.kind() {
ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
// This process claims itself as singleton as likely none exists
socket_cleanup(&socket);
listen_for_other_instances(&socket, app.clone(), cb);
}
Err(e) => {
match e.kind() {
ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
// This process claims itself as singleton as likely none exists
socket_cleanup(&socket);
listen_for_other_instances(&socket, app.clone(), cb);
}
_ => {
tracing::debug!(
"single_instance failed to notify - launching normally: {}",
e
);
}
}
_ => {
tracing::debug!(
"single_instance failed to notify - launching normally: {}",
e
);
}
}
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
}
})
.build()
}
}
Ok(())
}
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
@@ -63,7 +55,7 @@ fn socket_path(config: &Config, _package_info: &tauri::PackageInfo) -> PathBuf {
#[cfg(feature = "semver")]
let identifier = format!(
"{identifier}_{}",
semver_compat_string(_package_info.version.clone()),
semver_compat_string(&_package_info.version),
);
// Use /tmp as socket path must be shorter than 100 chars.
@@ -7,10 +7,7 @@ use crate::semver_compat::semver_compat_string;
use crate::SingleInstanceCallback;
use std::ffi::CStr;
use tauri::{
plugin::{self, TauriPlugin},
AppHandle, Manager, RunEvent, Runtime,
};
use tauri::{AppHandle, Manager, Runtime};
use windows_sys::Win32::{
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HWND, LPARAM, LRESULT, WPARAM},
System::{
@@ -51,69 +48,63 @@ impl<R: Runtime> UserData<R> {
}
}
pub fn init<R: Runtime>(callback: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
plugin::Builder::new("single-instance")
.setup(|app, _api| {
#[allow(unused_mut)]
let mut id = app.config().identifier.clone();
#[cfg(feature = "semver")]
{
id.push('_');
id.push_str(semver_compat_string(app.package_info().version.clone()).as_str());
}
pub fn setup_single_instance<R: Runtime>(
app: &AppHandle<R>,
callback: Box<SingleInstanceCallback<R>>,
_dbus_id: Option<String>,
) -> Result<(), ()> {
#[allow(unused_mut)]
let mut id = app.config().identifier.clone();
#[cfg(feature = "semver")]
{
id.push('_');
id.push_str(semver_compat_string(&app.package_info().version).as_str());
}
let class_name = encode_wide(format!("{id}-sic"));
let window_name = encode_wide(format!("{id}-siw"));
let mutex_name = encode_wide(format!("{id}-sim"));
let class_name = encode_wide(format!("{id}-sic"));
let window_name = encode_wide(format!("{id}-siw"));
let mutex_name = encode_wide(format!("{id}-sim"));
let hmutex =
unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
let hmutex = unsafe { CreateMutexW(std::ptr::null(), true.into(), mutex_name.as_ptr()) };
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe {
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());
if unsafe { GetLastError() } == ERROR_ALREADY_EXISTS {
unsafe {
let hwnd = FindWindowW(class_name.as_ptr(), window_name.as_ptr());
if !hwnd.is_null() {
let cwd = std::env::current_dir().unwrap_or_default();
let cwd = cwd.to_str().unwrap_or_default();
if !hwnd.is_null() {
let cwd = std::env::current_dir().unwrap_or_default();
let cwd = cwd.to_str().unwrap_or_default();
let args = std::env::args().collect::<Vec<String>>().join("|");
let args = std::env::args().collect::<Vec<String>>().join("|");
let data = format!("{cwd}|{args}\0",);
let data = format!("{cwd}|{args}\0",);
let bytes = data.as_bytes();
let cds = COPYDATASTRUCT {
dwData: WMCOPYDATA_SINGLE_INSTANCE_DATA,
cbData: bytes.len() as _,
lpData: bytes.as_ptr() as _,
};
SendMessageW(hwnd, WM_COPYDATA, 0, &cds as *const _ as _);
app.cleanup_before_exit();
std::process::exit(0);
}
}
} else {
app.manage(MutexHandle(hmutex as _));
let userdata = UserData {
app: app.clone(),
callback,
let bytes = data.as_bytes();
let cds = COPYDATASTRUCT {
dwData: WMCOPYDATA_SINGLE_INSTANCE_DATA,
cbData: bytes.len() as _,
lpData: bytes.as_ptr() as _,
};
let userdata = Box::into_raw(Box::new(userdata));
let hwnd = create_event_target_window::<R>(&class_name, &window_name, userdata);
app.manage(TargetWindowHandle(hwnd as _));
}
Ok(())
})
.on_event(|app, event| {
if let RunEvent::Exit = event {
destroy(app);
SendMessageW(hwnd, WM_COPYDATA, 0, &cds as *const _ as _);
app.cleanup_before_exit();
std::process::exit(0);
}
})
.build()
}
} else {
app.manage(MutexHandle(hmutex as _));
let userdata = UserData {
app: app.clone(),
callback,
};
let userdata = Box::into_raw(Box::new(userdata));
let hwnd = create_event_target_window::<R>(&class_name, &window_name, userdata);
app.manage(TargetWindowHandle(hwnd as _));
}
Ok(())
}
pub fn destroy<R: Runtime, M: Manager<R>>(manager: &M) {
+1 -1
View File
@@ -4,7 +4,7 @@
/// Takes a version and spits out a String with trailing _x, thus only considering the digits
/// relevant regarding semver compatibility
pub fn semver_compat_string(version: semver::Version) -> String {
pub fn semver_compat_string(version: &semver::Version) -> String {
// for pre-release always treat each version separately
if !version.pre.is_empty() {
return version.to_string().replace(['.', '-'], "_");
+4
View File
@@ -1,5 +1,9 @@
# Changelog
## \[2.3.2]
- [`2dc3f3f0`](https://github.com/tauri-apps/plugins-workspace/commit/2dc3f3f0397fa91b1f3ffbc4b42ca2b4ae55bbe0) ([#3144](https://github.com/tauri-apps/plugins-workspace/pull/3144)) Fixes issue with UUIDs returned as null in sql query results.
## \[2.3.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-sql"
version = "2.3.1"
version = "2.3.2"
description = "Interface with SQL databases."
authors = { workspace = true }
license = { workspace = true }
@@ -29,10 +29,11 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
futures-core = "0.3"
sqlx = { version = "0.8", features = ["json", "time"] }
sqlx = { version = "0.8", features = ["json", "time", "uuid"] }
time = "0.3"
tokio = { version = "1", features = ["sync"] }
indexmap = { version = "2", features = ["serde"] }
uuid = "1"
[features]
sqlite = ["sqlx/sqlite", "sqlx/runtime-tokio"]
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-sql",
"version": "2.3.1",
"version": "2.3.2",
"description": "Interface with SQL databases",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+9 -1
View File
@@ -5,6 +5,7 @@
use serde_json::Value as JsonValue;
use sqlx::{postgres::PgValueRef, TypeInfo, Value, ValueRef};
use time::{Date, OffsetDateTime, PrimitiveDateTime, Time};
use uuid::Uuid;
use crate::Error;
@@ -14,13 +15,20 @@ pub(crate) fn to_json(v: PgValueRef) -> Result<JsonValue, Error> {
}
let res = match v.type_info().name() {
"CHAR" | "VARCHAR" | "TEXT" | "NAME" | "UUID" => {
"CHAR" | "VARCHAR" | "TEXT" | "NAME" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode() {
JsonValue::String(v)
} else {
JsonValue::Null
}
}
"UUID" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<Uuid>() {
JsonValue::String(v.to_string())
} else {
JsonValue::Null
}
}
"FLOAT4" => {
if let Ok(v) = ValueRef::to_owned(&v).try_decode::<f32>() {
JsonValue::from(v)
@@ -8,8 +8,8 @@
"tauri": "tauri"
},
"devDependencies": {
"@tauri-apps/cli": "2.9.6",
"@tauri-apps/cli": "2.10.0",
"typescript": "^5.7.3",
"vite": "^7.0.7"
"vite": "^7.3.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -528,7 +528,7 @@ impl<R: Runtime> Store<R> {
/// - 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,
/// use [`reload_override_defaults`](Self::reload_override_defaults) instead
/// use [`reload_ignore_defaults`](Self::reload_ignore_defaults) instead
/// - This method does not emit change events
pub fn reload(&self) -> crate::Result<()> {
self.store.lock().unwrap().load()
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## \[2.10.0]
- [`4375c98b`](https://github.com/tauri-apps/plugins-workspace/commit/4375c98bed1769a1fc1be26eded4db9f7c095d95) ([#3073](https://github.com/tauri-apps/plugins-workspace/pull/3073)) Add no_proxy config to disable system proxy for updater plugin.
- [`f122ee98`](https://github.com/tauri-apps/plugins-workspace/commit/f122ee98c6954075891ac3cd530f5e29da117b39) Allow configuring the updater client to accept invalid TLS certificates and hostnames for internal/self-signed update servers. These options are available via the plugin config (`dangerousAcceptInvalidCerts`, `dangerousAcceptInvalidHostnames`) and via the `UpdaterBuilder` (`dangerous_accept_invalid_certs`, `dangerous_accept_invalid_hostnames`).
- [`f122ee98`](https://github.com/tauri-apps/plugins-workspace/commit/f122ee98c6954075891ac3cd530f5e29da117b39) Updater plugin now supports all bundle types: Deb, Rpm and AppImage for Linux; NSiS, MSI for Windows. This was added in https://github.com/tauri-apps/plugins-workspace/pull/2624
- [`05c5da07`](https://github.com/tauri-apps/plugins-workspace/commit/05c5da072b6e3254c19ee69024f80f4dfe1b779b) ([#3213](https://github.com/tauri-apps/plugins-workspace/pull/3213)) Updated `reqwest` to 0.13, make sure to update your `reqwest` dependency if you're using `UpdaterBuilder::configure_client`
## \[2.9.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
+7 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-updater"
version = "2.9.0"
version = "2.10.0"
description = "In-app updates for Tauri applications."
edition = { workspace = true }
authors = { workspace = true }
@@ -30,10 +30,13 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
log = { workspace = true }
tokio = "1"
reqwest = { version = "0.12", default-features = false, features = [
reqwest = { version = "0.13", default-features = false, features = [
"json",
"stream",
] }
rustls = { version = "0.23", default-features = false, features = [
"ring",
], optional = true }
url = { workspace = true }
http = "1"
minisign-verify = "0.2"
@@ -41,7 +44,7 @@ time = { version = "0.3", features = ["parsing", "formatting"] }
base64 = "0.22"
semver = { version = "1", features = ["serde"] }
futures-util = "0.3"
tempfile = "3"
tempfile = "3.20"
infer = "0.19"
percent-encoding = "2.3"
@@ -68,4 +71,4 @@ default = ["rustls-tls", "zip"]
zip = ["dep:zip", "dep:tar", "dep:flate2"]
native-tls = ["reqwest/native-tls"]
native-tls-vendored = ["reqwest/native-tls-vendored"]
rustls-tls = ["reqwest/rustls-tls"]
rustls-tls = ["reqwest/rustls-no-provider", "dep:rustls"]
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-updater",
"version": "2.9.0",
"version": "2.10.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+27 -3
View File
@@ -146,6 +146,7 @@ pub struct UpdaterBuilder {
headers: HeaderMap,
timeout: Option<Duration>,
proxy: Option<Url>,
no_proxy: bool,
installer_args: Vec<OsString>,
current_exe_args: Vec<OsString>,
on_before_exit: Option<OnBeforeExit>,
@@ -174,6 +175,7 @@ impl UpdaterBuilder {
headers: Default::default(),
timeout: None,
proxy: None,
no_proxy: false,
on_before_exit: None,
configure_client: None,
}
@@ -242,6 +244,12 @@ impl UpdaterBuilder {
self
}
/// Clear all proxies. See [`reqwest::ClientBuilder::no_proxy`](https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.no_proxy).
pub fn no_proxy(mut self) -> Self {
self.no_proxy = true;
self
}
pub fn pubkey<S: Into<String>>(mut self, pubkey: S) -> Self {
self.config.pubkey = pubkey.into();
self
@@ -323,6 +331,7 @@ impl UpdaterBuilder {
version_comparator: self.version_comparator,
timeout: self.timeout,
proxy: self.proxy,
no_proxy: self.no_proxy,
endpoints,
installer_args: self.installer_args,
current_exe_args: self.current_exe_args,
@@ -357,6 +366,7 @@ pub struct Updater {
version_comparator: Option<VersionComparator>,
timeout: Option<Duration>,
proxy: Option<Url>,
no_proxy: bool,
endpoints: Vec<Url>,
arch: &'static str,
// The `{{target}}` variable we replace in the endpoint and serach for in the JSON,
@@ -432,6 +442,12 @@ impl Updater {
log::debug!("checking for updates {url}");
#[cfg(feature = "rustls-tls")]
if rustls::crypto::CryptoProvider::get_default().is_none() {
// This can only fail if there is already a default provider which we checked for already.
let _ = rustls::crypto::ring::default_provider().install_default();
}
let mut request = ClientBuilder::new().user_agent(UPDATER_USER_AGENT);
if self.config.dangerous_accept_invalid_certs {
request = request.danger_accept_invalid_certs(true);
@@ -442,7 +458,10 @@ impl Updater {
if let Some(timeout) = self.timeout {
request = request.timeout(timeout);
}
if let Some(ref proxy) = self.proxy {
if self.no_proxy {
log::debug!("disabling proxy");
request = request.no_proxy();
} else if let Some(ref proxy) = self.proxy {
log::debug!("using proxy {proxy}");
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
@@ -533,6 +552,7 @@ impl Updater {
raw_json: raw_json.unwrap(),
timeout: None,
proxy: self.proxy.clone(),
no_proxy: self.no_proxy,
headers: self.headers.clone(),
installer_args: self.installer_args.clone(),
current_exe_args: self.current_exe_args.clone(),
@@ -606,6 +626,8 @@ pub struct Update {
pub timeout: Option<Duration>,
/// Request proxy
pub proxy: Option<Url>,
/// Disable system proxy
pub no_proxy: bool,
/// Request headers
pub headers: HeaderMap,
/// Extract path
@@ -648,7 +670,9 @@ impl Update {
if let Some(timeout) = self.timeout {
request = request.timeout(timeout);
}
if let Some(ref proxy) = self.proxy {
if self.no_proxy {
request = request.no_proxy();
} else if let Some(ref proxy) = self.proxy {
let proxy = reqwest::Proxy::all(proxy.as_str())?;
request = request.proxy(proxy);
}
@@ -868,7 +892,7 @@ impl Update {
Ok(tempfile::Builder::new()
.prefix(&format!("{}-{}-updater-", self.app_name, self.version))
.tempdir()?
.into_path())
.keep())
}
#[cfg(feature = "zip")]
+2 -2
View File
@@ -220,9 +220,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
dependencies = [
"serde",
]
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
@@ -9,9 +9,9 @@
"preview": "vite preview"
},
"devDependencies": {
"@tauri-apps/cli": "2.9.6",
"@tauri-apps/cli": "2.10.0",
"typescript": "^5.7.3",
"vite": "^7.0.7"
"vite": "^7.3.1"
},
"dependencies": {
"tauri-plugin-websocket-api": "link:..\\.."
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.8.0"
"@tauri-apps/api": "^2.10.1"
}
}
+536 -490
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.6.0"
"@tauri-apps/api": "^2.10.1"
}
}