From 937e6a5be61f6d8d2e3a3e6c3eeb3d4d84e3454d Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Fri, 12 May 2023 13:16:50 -0700 Subject: [PATCH] feat: use tauri `next` branch, fix tests, MSRV 1.65 (#354) --- .github/workflows/lint-rust.yml | 4 + .github/workflows/msrv-check.yml | 6 +- .prettierignore | 3 +- Cargo.lock | 466 ++++++++++++++++-- Cargo.toml | 17 +- README.md | 2 +- examples/api/src-tauri/Cargo.toml | 24 +- examples/api/src-tauri/build.rs | 12 +- plugins/app/README.md | 2 +- plugins/authenticator/README.md | 2 +- plugins/autostart/README.md | 2 +- plugins/autostart/src/lib.rs | 2 +- plugins/cli/README.md | 2 +- plugins/cli/src/parser.rs | 4 +- plugins/clipboard/README.md | 2 +- plugins/dialog/README.md | 2 +- plugins/dialog/src/lib.rs | 70 +-- plugins/dialog/test/tauri.conf.json | 22 + plugins/fs-watch/README.md | 2 +- plugins/fs-watch/guest-js/index.ts | 62 ++- plugins/fs-watch/package.json | 3 +- plugins/fs-watch/src/lib.rs | 19 +- plugins/fs/Cargo.toml | 20 +- plugins/fs/README.md | 2 +- plugins/global-shortcut/README.md | 2 +- plugins/http/Cargo.toml | 21 +- plugins/http/README.md | 2 +- plugins/http/src/commands/mod.rs | 5 +- plugins/http/src/scope.rs | 24 +- plugins/localhost/README.md | 2 +- plugins/log/README.md | 2 +- plugins/notification/README.md | 2 +- plugins/notification/src/desktop.rs | 40 +- plugins/notification/test/tauri.conf.json | 22 + plugins/os/README.md | 2 +- plugins/persisted-scope/README.md | 2 +- plugins/positioner/README.md | 2 +- plugins/process/README.md | 2 +- plugins/shell/README.md | 2 +- plugins/shell/src/process/mod.rs | 77 +-- plugins/shell/test/test.txt | 1 + plugins/single-instance/README.md | 2 +- plugins/sql/README.md | 2 +- plugins/store/README.md | 2 +- plugins/store/src/lib.rs | 63 ++- plugins/store/src/store.rs | 39 +- plugins/stronghold/README.md | 2 +- plugins/updater/Cargo.toml | 19 +- plugins/updater/README.md | 2 +- plugins/updater/tests/app-updater/Cargo.toml | 11 +- plugins/updater/tests/app-updater/build.rs | 2 +- plugins/updater/tests/app-updater/src/main.rs | 78 +-- .../updater/tests/app-updater/tests/update.rs | 2 +- plugins/upload/README.md | 2 +- plugins/upload/guest-js/index.ts | 58 ++- plugins/upload/package.json | 3 +- plugins/upload/src/lib.rs | 43 +- plugins/websocket/README.md | 2 +- plugins/window-state/README.md | 2 +- plugins/window-state/guest-js/index.ts | 18 +- plugins/window-state/package.json | 3 +- plugins/window/README.md | 2 +- pnpm-lock.yaml | 9 - shared/template/README.md | 2 +- 64 files changed, 867 insertions(+), 463 deletions(-) create mode 100644 plugins/dialog/test/tauri.conf.json create mode 100644 plugins/notification/test/tauri.conf.json create mode 100644 plugins/shell/test/test.txt diff --git a/.github/workflows/lint-rust.yml b/.github/workflows/lint-rust.yml index f724e838..af0863e7 100644 --- a/.github/workflows/lint-rust.yml +++ b/.github/workflows/lint-rust.yml @@ -43,6 +43,10 @@ jobs: - uses: Swatinem/rust-cache@v2 + - name: create dummy dist + working-directory: examples/api + run: mkdir dist + - name: clippy run: cargo clippy --workspace --exclude 'tauri-plugin-sql' --all-targets --all-features -- -D warnings diff --git a/.github/workflows/msrv-check.yml b/.github/workflows/msrv-check.yml index 807bbcad..ef8fd15c 100644 --- a/.github/workflows/msrv-check.yml +++ b/.github/workflows/msrv-check.yml @@ -38,10 +38,14 @@ jobs: sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libudev-dev - - uses: dtolnay/rust-toolchain@1.64.0 + - uses: dtolnay/rust-toolchain@1.65.0 - uses: Swatinem/rust-cache@v2 + - name: create dummy dist + working-directory: examples/api + run: mkdir dist + - name: build run: cargo build --workspace --exclude 'tauri-plugin-sql' --all-targets --all-features diff --git a/.prettierignore b/.prettierignore index 13134b1f..7435af0c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,5 @@ target node_modules dist pnpm-lock.yaml -Cargo.lock \ No newline at end of file +Cargo.lock +.build \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7abf65a6..cfda4c2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.7.5" @@ -24,22 +34,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug", ] +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + [[package]] name = "aes-gcm" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash 0.4.4", + "subtle", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead 0.5.2", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.0", "subtle", ] @@ -169,6 +204,46 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +[[package]] +name = "api" +version = "0.1.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-app", + "tauri-plugin-cli", + "tauri-plugin-clipboard", + "tauri-plugin-dialog", + "tauri-plugin-fs", + "tauri-plugin-global-shortcut", + "tauri-plugin-http", + "tauri-plugin-log", + "tauri-plugin-notification", + "tauri-plugin-os", + "tauri-plugin-process", + "tauri-plugin-shell", + "tauri-plugin-updater", + "tauri-plugin-window", + "tiny_http 0.11.0", + "window-shadows", +] + +[[package]] +name = "app-updater" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-updater", + "time 0.3.20", + "tiny_http 0.11.0", +] + [[package]] name = "arboard" version = "3.2.0" @@ -207,6 +282,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "async-broadcast" version = "0.5.1" @@ -294,6 +379,28 @@ dependencies = [ "syn 2.0.13", ] +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.13", +] + [[package]] name = "async-task" version = "4.4.0" @@ -604,12 +711,12 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.14.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfbc36312494041e2cdd5f06697b7e89d4b76f42773a0b5556ac290ff22acc2" +checksum = "7f83bc2e401ed041b7057345ebc488c005efa0341d5541ce7004d30458d0090b" dependencies = [ "serde", - "toml 0.5.11", + "toml 0.7.3", ] [[package]] @@ -657,7 +764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if", - "cipher", + "cipher 0.3.0", "cpufeatures", "zeroize", ] @@ -668,9 +775,9 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ - "aead", + "aead 0.4.3", "chacha20", - "cipher", + "cipher 0.3.0", "poly1305", "zeroize", ] @@ -685,6 +792,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", + "serde", "time 0.1.45", "wasm-bindgen", "winapi", @@ -705,6 +813,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.2.2" @@ -808,6 +926,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "combine" version = "4.6.6" @@ -971,6 +1100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1017,7 +1147,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher", + "cipher 0.3.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", ] [[package]] @@ -1079,9 +1218,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -1089,9 +1228,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", @@ -1103,9 +1242,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", @@ -1369,7 +1508,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" dependencies = [ - "colored", + "colored 1.9.3", "log", ] @@ -1740,7 +1879,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug", - "polyval", + "polyval 0.5.3", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval 0.6.0", ] [[package]] @@ -2103,6 +2252,19 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -2216,6 +2378,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -2256,6 +2419,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2282,9 +2454,9 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e04d492224bff6e97142f033d0a4383bcbc05918be1ff7b3abd2c1cc85205a2" dependencies = [ - "aead", - "aes", - "aes-gcm", + "aead 0.4.3", + "aes 0.7.5", + "aes-gcm 0.9.4", "autocfg", "blake2", "chacha20poly1305", @@ -2456,9 +2628,9 @@ dependencies = [ [[package]] name = "json-patch" -version = "0.3.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e712e62827c382a77b87f590532febb1f8b2fdbc3eefa1ee37fe7281687075ef" +checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" dependencies = [ "serde", "serde_json", @@ -2771,12 +2943,28 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -2798,6 +2986,24 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "mockito" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f9fece9bd97ab74339fe19f4bcaf52b76dcc18e5364c7977c1838f76b38de9" +dependencies = [ + "assert-json-diff", + "colored 2.0.0", + "httparse", + "lazy_static", + "log", + "rand 0.8.5", + "regex", + "serde_json", + "serde_urlencoded", + "similar", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -3148,6 +3354,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "111.25.3+1.1.1t" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924757a6a226bf60da5f7dd0311a34d2b52283dd82ddeb103208ddc66362f80c" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.84" @@ -3156,6 +3371,7 @@ checksum = "3a20eace9dc2d82904039cb76dcf50fb1a0bba071cfd1629720b5d6f1ddba0fa" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -3521,7 +3737,7 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] @@ -3533,7 +3749,19 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", - "universal-hash", + "universal-hash 0.4.1", +] + +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash 0.5.0", ] [[package]] @@ -3798,20 +4026,25 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -3819,6 +4052,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg", ] @@ -4137,19 +4371,25 @@ dependencies = [ [[package]] name = "serde_with" -version = "1.14.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "331bb8c3bf9b92457ab7abecf07078c13f7d270ba490103e84e8b014490cd0b0" dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap", "serde", + "serde_json", "serde_with_macros", + "time 0.3.20", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "859011bddcc11f289f07f467cc1fe01c7a941daa4d8f6c40d4d1c92eb6d9319c" dependencies = [ "darling", "proc-macro2", @@ -4243,6 +4483,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "similar" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" + [[package]] name = "siphasher" version = "0.3.10" @@ -4642,9 +4888,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.18.3" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f2340617d383561b0ea25358b97ec2c2ba04db48c458ce71dd1b38d7fd09ac5" +checksum = "746ae5d0ca57ae275a792f109f6e992e0b41a443abdf3f5c6eff179ef5b3443a" dependencies = [ "bitflags 1.3.2", "cairo-rs", @@ -4714,8 +4960,7 @@ dependencies = [ [[package]] name = "tauri" version = "2.0.0-alpha.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25eefe4ca0a396a73fd0309f778eeb22a19953a3169bf316b893abadc2118fb" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "anyhow", "bytes 1.4.0", @@ -4758,29 +5003,32 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", + "time 0.3.20", "tokio", "url", "uuid", "webkit2gtk", "webview2-com", "windows 0.44.0", + "zip", ] [[package]] name = "tauri-build" version = "2.0.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c492211c72b95f8866e5c1fbc0a915080a5ebb9f03f9b250a1c936534b680a76" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "anyhow", "cargo_toml", "filetime", "heck 0.4.1", "json-patch", + "quote", "semver", "serde", "serde_json", "swift-rs", + "tauri-codegen", "tauri-utils", "tauri-winres", "walkdir", @@ -4789,8 +5037,7 @@ dependencies = [ [[package]] name = "tauri-codegen" version = "2.0.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818c570932ebc2ff6d498be89d93494b89ff142131937a7e56d7cfb9c8ef0ad0" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "base64 0.21.0", "brotli", @@ -4815,8 +5062,7 @@ dependencies = [ [[package]] name = "tauri-macros" version = "2.0.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b596485d89003d2d7869469b2830e9a846de9ac2eecd69bc7c24890234aefc" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -4904,6 +5150,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-fs" +version = "0.0.0" +dependencies = [ + "anyhow", + "serde", + "tauri", + "thiserror", +] + [[package]] name = "tauri-plugin-fs-watch" version = "0.0.0" @@ -4929,6 +5185,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-http" +version = "0.0.0" +dependencies = [ + "bytes 1.4.0", + "glob", + "http", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "tauri", + "thiserror", +] + [[package]] name = "tauri-plugin-localhost" version = "0.1.0" @@ -4939,7 +5211,7 @@ dependencies = [ "serde_json", "tauri", "thiserror", - "tiny_http", + "tiny_http 0.12.0", ] [[package]] @@ -5095,6 +5367,30 @@ dependencies = [ "zeroize", ] +[[package]] +name = "tauri-plugin-updater" +version = "0.0.0" +dependencies = [ + "base64 0.21.0", + "dirs-next", + "futures-util", + "http", + "minisign-verify", + "mockito", + "percent-encoding", + "reqwest", + "semver", + "serde", + "serde_json", + "tauri", + "tempfile", + "thiserror", + "time 0.3.20", + "tokio", + "tokio-test", + "url", +] + [[package]] name = "tauri-plugin-upload" version = "0.0.0" @@ -5151,8 +5447,7 @@ dependencies = [ [[package]] name = "tauri-runtime" version = "0.13.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "404367cd32a5a8d33368448aab7da54bb2187b6a632526f1019de3fd13591cc2" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "gtk", "http", @@ -5173,8 +5468,7 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" version = "0.13.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203764d673b440877dea87b972772be4091ee0ab25141748008646ca774a20dc" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ "cocoa", "gtk", @@ -5194,11 +5488,12 @@ dependencies = [ [[package]] name = "tauri-utils" version = "2.0.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fa79bc56f04ece491268a64273de945f65627bcda30d9e8ecc8708b89bca26" +source = "git+https://github.com/tauri-apps/tauri?branch=next#9a79dc085870e0c1a5df13481ff271b8c6cc3b78" dependencies = [ + "aes-gcm 0.10.1", "brotli", "ctor", + "getrandom 0.2.9", "glob", "heck 0.4.1", "html5ever", @@ -5213,6 +5508,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "serialize-to-javascript", "thiserror", "url", "walkdir", @@ -5360,6 +5656,19 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny_http" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d6ef4e10d23c1efb862eecad25c5054429a71958b4eeef85eb5e7170b477ca" +dependencies = [ + "ascii", + "chunked_transfer", + "log", + "time 0.3.20", + "url", +] + [[package]] name = "tiny_http" version = "0.12.0" @@ -5435,6 +5744,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" +dependencies = [ + "async-stream", + "bytes 1.4.0", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-tungstenite" version = "0.18.0" @@ -5642,6 +5964,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -5691,6 +6022,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -6079,6 +6420,18 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "window-shadows" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d" +dependencies = [ + "cocoa", + "objc", + "raw-window-handle", + "windows-sys 0.42.0", +] + [[package]] name = "windows" version = "0.36.1" @@ -6409,9 +6762,9 @@ dependencies = [ [[package]] name = "wry" -version = "0.27.3" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf0dbfa7ccbd2e3832a3098b19d4b552360ea00a40b244a99caef46bffd84f" +checksum = "7d15f9f827d537cefe6d047be3930f5d89b238dfb85e08ba6a319153217635aa" dependencies = [ "base64 0.13.1", "block", @@ -6593,6 +6946,17 @@ dependencies = [ "syn 2.0.13", ] +[[package]] +name = "zip" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", +] + [[package]] name = "zvariant" version = "3.12.0" diff --git a/Cargo.toml b/Cargo.toml index acfc6598..c70e53f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,12 @@ [workspace] -members = ["plugins/*", "examples/*/src-tauri"] -exclude = ["plugins/fs", "plugins/http", "plugins/updater", "examples/api/src-tauri"] +members = ["plugins/*", "plugins/*/tests/*", "examples/*/src-tauri"] resolver = "2" [workspace.dependencies] serde = { version = "1", features = ["derive"] } log = "0.4" -tauri = "2.0.0-alpha.8" -tauri-build = "2.0.0-alpha.4" +tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } +tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next" } serde_json = "1" thiserror = "1" @@ -15,4 +14,12 @@ thiserror = "1" edition = "2021" authors = [ "Tauri Programme within The Commons Conservancy" ] license = "Apache-2.0 OR MIT" -rust-version = "1.64" +rust-version = "1.65" + +# default to small, optimized release binaries +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +incremental = false +opt-level = "s" diff --git a/README.md b/README.md index cc7541ae..edeb0ac5 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,4 @@ | [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? | | [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? | -_This repo and all plugins require a Rust version of at least **1.64**_ +_This repo and all plugins require a Rust version of at least **1.65**_ diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index fe8c70fe..e64d84b8 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -3,20 +3,20 @@ name = "api" version = "0.1.0" description = "An example Tauri Application showcasing the api" edition = "2021" -rust-version = "1.64" +rust-version = "1.65" license = "Apache-2.0 OR MIT" [lib] crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2.0.0-alpha.4", features = ["codegen", "isolation"] } +tauri-build = { workspace = true, features = ["codegen", "isolation"] } [dependencies] -serde_json = "1.0" -serde = { version = "1.0", features = [ "derive" ] } +serde_json.workspace = true +serde.workspace = true tiny_http = "0.11" -log = "0.4" +log.workspace = true tauri-plugin-app = { path = "../../../plugins/app" } tauri-plugin-log = { path = "../../../plugins/log" } tauri-plugin-fs = { path = "../../../plugins/fs" } @@ -30,12 +30,8 @@ tauri-plugin-shell = { path = "../../../plugins/shell" } tauri-plugin-updater = { path = "../../../plugins/updater" } tauri-plugin-window = { path = "../../../plugins/window" } -[patch.crates-io] -tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } -tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next" } - [dependencies.tauri] -version = "2.0.0-alpha.8" +workspace = true features = [ "api-all", "icon-ico", @@ -55,11 +51,3 @@ window-shadows = "0.2" [features] custom-protocol = [ "tauri/custom-protocol" ] - -# default to small, optimized release binaries -[profile.release] -panic = "abort" -codegen-units = 1 -lto = true -incremental = false -opt-level = "s" diff --git a/examples/api/src-tauri/build.rs b/examples/api/src-tauri/build.rs index 2154ff35..e9f99b29 100644 --- a/examples/api/src-tauri/build.rs +++ b/examples/api/src-tauri/build.rs @@ -3,10 +3,10 @@ // SPDX-License-Identifier: MIT fn main() { - let mut codegen = tauri_build::CodegenContext::new(); - if !cfg!(feature = "custom-protocol") { - codegen = codegen.dev(); - } - codegen.build(); - tauri_build::build(); + let mut codegen = tauri_build::CodegenContext::new(); + if !cfg!(feature = "custom-protocol") { + codegen = codegen.dev(); + } + codegen.build(); + tauri_build::build(); } diff --git a/plugins/app/README.md b/plugins/app/README.md index fe981bc1..9c07789a 100644 --- a/plugins/app/README.md +++ b/plugins/app/README.md @@ -4,7 +4,7 @@ This plugin provides APIs to read application metadata and macOS app visibility ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/authenticator/README.md b/plugins/authenticator/README.md index c64fecdf..490d6255 100644 --- a/plugins/authenticator/README.md +++ b/plugins/authenticator/README.md @@ -4,7 +4,7 @@ Use hardware security-keys in your Tauri App. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/autostart/README.md b/plugins/autostart/README.md index fa787275..f07bd5c6 100644 --- a/plugins/autostart/README.md +++ b/plugins/autostart/README.md @@ -4,7 +4,7 @@ Automatically launch your application at startup. Supports Windows, Mac (via App ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/autostart/src/lib.rs b/plugins/autostart/src/lib.rs index dc9f38bb..3a927782 100644 --- a/plugins/autostart/src/lib.rs +++ b/plugins/autostart/src/lib.rs @@ -120,7 +120,7 @@ pub fn init( let exe_path = current_exe.canonicalize()?.display().to_string(); let parts: Vec<&str> = exe_path.split(".app/").collect(); let app_path = if parts.len() == 2 { - format!("{}.app", parts.get(0).unwrap().to_string()) + format!("{}.app", parts.first().unwrap()) } else { exe_path }; diff --git a/plugins/cli/README.md b/plugins/cli/README.md index 720e7147..983cf803 100644 --- a/plugins/cli/README.md +++ b/plugins/cli/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/cli/src/parser.rs b/plugins/cli/src/parser.rs index d68fd10a..69a926c1 100644 --- a/plugins/cli/src/parser.rs +++ b/plugins/cli/src/parser.rs @@ -72,10 +72,10 @@ impl Matches { /// # Examples /// /// ```rust,no_run -/// use tauri_plugin_cli::get_matches; +/// use tauri_plugin_cli::CliExt; /// tauri::Builder::default() /// .setup(|app| { -/// let matches = get_matches(app.config().tauri.cli.as_ref().unwrap(), app.package_info())?; +/// let matches = app.cli().matches()?; /// Ok(()) /// }); /// ``` diff --git a/plugins/clipboard/README.md b/plugins/clipboard/README.md index 2879c77b..2b5df3e4 100644 --- a/plugins/clipboard/README.md +++ b/plugins/clipboard/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/dialog/README.md b/plugins/dialog/README.md index 24a5b214..de31bcb8 100644 --- a/plugins/dialog/README.md +++ b/plugins/dialog/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/dialog/src/lib.rs b/plugins/dialog/src/lib.rs index 93969d71..85518fef 100644 --- a/plugins/dialog/src/lib.rs +++ b/plugins/dialog/src/lib.rs @@ -324,12 +324,12 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_file(|file_path| { + /// .run(|app, _event| { + /// app.dialog().file().pick_file(|file_path| { /// // do something with the optional file path here /// // the file path is `None` if the user closed the dialog /// }) @@ -348,12 +348,12 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_files(|file_paths| { + /// .run(|app, _event| { + /// app.dialog().file().pick_files(|file_paths| { /// // do something with the optional file paths here /// // the file paths value is `None` if the user closed the dialog /// }) @@ -378,12 +378,12 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_folder(|folder_path| { + /// .run(|app, _event| { + /// app.dialog().file().pick_folder(|folder_path| { /// // do something with the optional folder path here /// // the folder path is `None` if the user closed the dialog /// }) @@ -401,12 +401,12 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().pick_folders(|file_paths| { + /// .run(|app, _event| { + /// app.dialog().file().pick_folders(|file_paths| { /// // do something with the optional folder paths here /// // the folder paths value is `None` if the user closed the dialog /// }) @@ -425,12 +425,12 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// tauri::Builder::default() - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("failed to build tauri app") - /// .run(|_app, _event| { - /// FileDialogBuilder::new().save_file(|file_path| { + /// .run(|app, _event| { + /// app.dialog().file().save_file(|file_path| { /// // do something with the optional file path here /// // the file path is `None` if the user closed the dialog /// }) @@ -451,10 +451,10 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// #[tauri::command] - /// async fn my_command() { - /// let file_path = FileDialogBuilder::new().pick_file(); + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_pick_file(); /// // do something with the optional file path here /// // the file path is `None` if the user closed the dialog /// } @@ -470,10 +470,10 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// #[tauri::command] - /// async fn my_command() { - /// let file_path = FileDialogBuilder::new().pick_files(); + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_pick_files(); /// // do something with the optional file paths here /// // the file paths value is `None` if the user closed the dialog /// } @@ -489,10 +489,10 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// #[tauri::command] - /// async fn my_command() { - /// let folder_path = FileDialogBuilder::new().pick_folder(); + /// async fn my_command(app: tauri::AppHandle) { + /// let folder_path = app.dialog().file().blocking_pick_folder(); /// // do something with the optional folder path here /// // the folder path is `None` if the user closed the dialog /// } @@ -509,10 +509,10 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// #[tauri::command] - /// async fn my_command() { - /// let folder_paths = FileDialogBuilder::new().pick_folders(); + /// async fn my_command(app: tauri::AppHandle) { + /// let folder_paths = app.dialog().file().blocking_pick_folders(); /// // do something with the optional folder paths here /// // the folder paths value is `None` if the user closed the dialog /// } @@ -529,10 +529,10 @@ impl FileDialogBuilder { /// # Examples /// /// ```rust,no_run - /// use tauri::api::dialog::blocking::FileDialogBuilder; + /// use tauri_plugin_dialog::DialogExt; /// #[tauri::command] - /// async fn my_command() { - /// let file_path = FileDialogBuilder::new().save_file(); + /// async fn my_command(app: tauri::AppHandle) { + /// let file_path = app.dialog().file().blocking_save_file(); /// // do something with the optional file path here /// // the file path is `None` if the user closed the dialog /// } diff --git a/plugins/dialog/test/tauri.conf.json b/plugins/dialog/test/tauri.conf.json new file mode 100644 index 00000000..8a9c0108 --- /dev/null +++ b/plugins/dialog/test/tauri.conf.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.8/node_modules/@tauri-apps/cli/schema.json", + "build": { + "distDir": ".", + "devPath": "http://localhost:4000" + }, + "tauri": { + "bundle": { + "identifier": "studio.tauri.example", + "active": true, + "icon": ["../../../examples/api/src-tauri/icons/icon.png"] + }, + "windows": [ + { + "title": "Tauri App" + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'" + } + } +} diff --git a/plugins/fs-watch/README.md b/plugins/fs-watch/README.md index c410cf12..12cb0d1c 100644 --- a/plugins/fs-watch/README.md +++ b/plugins/fs-watch/README.md @@ -4,7 +4,7 @@ Watch files and directories for changes using [notify](https://github.com/notify ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/fs-watch/guest-js/index.ts b/plugins/fs-watch/guest-js/index.ts index 9d25096f..2aa58b0c 100644 --- a/plugins/fs-watch/guest-js/index.ts +++ b/plugins/fs-watch/guest-js/index.ts @@ -1,8 +1,4 @@ -import { invoke } from "@tauri-apps/api/tauri"; -import { UnlistenFn } from "@tauri-apps/api/event"; -import { appWindow, WebviewWindow } from "tauri-plugin-window-api"; - -const w: WebviewWindow = appWindow; +import { invoke, transformCallback } from "@tauri-apps/api/tauri"; export interface WatchOptions { recursive?: boolean; @@ -42,11 +38,39 @@ async function unwatch(id: number): Promise { await invoke("plugin:fs-watch|unwatch", { id }); } +// TODO: use channel from @tauri-apps/api on v2 +class Channel { + id: number; + // @ts-expect-error field used by the IPC serializer + private readonly __TAURI_CHANNEL_MARKER__ = true; + #onmessage: (response: T) => void = () => { + // no-op + }; + + constructor() { + this.id = transformCallback((response: T) => { + this.#onmessage(response); + }); + } + + set onmessage(handler: (response: T) => void) { + this.#onmessage = handler; + } + + get onmessage(): (response: T) => void { + return this.#onmessage; + } + + toJSON(): string { + return `__CHANNEL__:${this.id}`; + } +} + export async function watch( paths: string | string[], cb: (event: DebouncedEvent) => void, options: DebouncedWatchOptions = {} -): Promise { +): Promise<() => void> { const opts = { recursive: false, delayMs: 2000, @@ -61,22 +85,18 @@ export async function watch( const id = window.crypto.getRandomValues(new Uint32Array(1))[0]; + const onEvent = new Channel(); + onEvent.onmessage = cb; + await invoke("plugin:fs-watch|watch", { id, paths: watchPaths, options: opts, + onEvent, }); - const unlisten = await w.listen( - `watcher://debounced-event/${id}`, - (event) => { - cb(event.payload); - } - ); - return () => { void unwatch(id); - unlisten(); }; } @@ -84,7 +104,7 @@ export async function watchImmediate( paths: string | string[], cb: (event: RawEvent) => void, options: WatchOptions = {} -): Promise { +): Promise<() => void> { const opts = { recursive: false, ...options, @@ -99,21 +119,17 @@ export async function watchImmediate( const id = window.crypto.getRandomValues(new Uint32Array(1))[0]; + const onEvent = new Channel(); + onEvent.onmessage = cb; + await invoke("plugin:fs-watch|watch", { id, paths: watchPaths, options: opts, + onEvent, }); - const unlisten = await w.listen( - `watcher://raw-event/${id}`, - (event) => { - cb(event.payload); - } - ); - return () => { void unwatch(id); - unlisten(); }; } diff --git a/plugins/fs-watch/package.json b/plugins/fs-watch/package.json index 61c5cf71..4ac45241 100644 --- a/plugins/fs-watch/package.json +++ b/plugins/fs-watch/package.json @@ -28,7 +28,6 @@ "tslib": "^2.5.0" }, "dependencies": { - "@tauri-apps/api": "^1.2.0", - "tauri-plugin-window-api": "0.0.0" + "@tauri-apps/api": "^1.2.0" } } diff --git a/plugins/fs-watch/src/lib.rs b/plugins/fs-watch/src/lib.rs index 3185d148..d0a9f783 100644 --- a/plugins/fs-watch/src/lib.rs +++ b/plugins/fs-watch/src/lib.rs @@ -2,9 +2,10 @@ use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; use serde::{ser::Serializer, Deserialize, Serialize}; use tauri::{ + api::ipc::Channel, command, plugin::{Builder as PluginBuilder, TauriPlugin}, - Manager, Runtime, State, Window, + Manager, Runtime, State, }; use std::{ @@ -44,25 +45,23 @@ enum WatcherKind { Watcher(RecommendedWatcher), } -fn watch_raw(window: Window, rx: Receiver>, id: Id) { +fn watch_raw(on_event: Channel, rx: Receiver>) { spawn(move || { - let event_name = format!("watcher://raw-event/{id}"); while let Ok(event) = rx.recv() { if let Ok(event) = event { // TODO: Should errors be emitted too? - let _ = window.emit(&event_name, event); + let _ = on_event.send(&event); } } }); } -fn watch_debounced(window: Window, rx: Receiver, id: Id) { +fn watch_debounced(on_event: Channel, rx: Receiver) { spawn(move || { - let event_name = format!("watcher://debounced-event/{id}"); while let Ok(event) = rx.recv() { if let Ok(event) = event { // TODO: Should errors be emitted too? - let _ = window.emit(&event_name, event); + let _ = on_event.send(&event); } } }); @@ -77,11 +76,11 @@ struct WatchOptions { #[command] async fn watch( - window: Window, watchers: State<'_, WatcherCollection>, id: Id, paths: Vec, options: WatchOptions, + on_event: Channel, ) -> Result<()> { let mode = if options.recursive { RecursiveMode::Recursive @@ -96,7 +95,7 @@ async fn watch( for path in &paths { watcher.watch(path, mode)?; } - watch_debounced(window, rx, id); + watch_debounced(on_event, rx); WatcherKind::Debouncer(debouncer) } else { let (tx, rx) = channel(); @@ -104,7 +103,7 @@ async fn watch( for path in &paths { watcher.watch(path, mode)?; } - watch_raw(window, rx, id); + watch_raw(on_event, rx); WatcherKind::Watcher(watcher) }; diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml index 6e2add71..a9549994 100644 --- a/plugins/fs/Cargo.toml +++ b/plugins/fs/Cargo.toml @@ -2,21 +2,15 @@ name = "tauri-plugin-fs" version = "0.0.0" description = "Access the file system." -edition = "2021" -#authors.workspace = true -#license.workspace = true -#edition.workspace = true -#rust-version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -#serde.workspace = true -#serde_json.workspace = true -#tauri.workspace = true -#log.workspace = true -#thiserror.workspace = true -serde = "1" -thiserror = "1" -tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } +serde.workspace = true +tauri.workspace = true +thiserror.workspace = true anyhow = "1" diff --git a/plugins/fs/README.md b/plugins/fs/README.md index 55756f5b..c59990d8 100644 --- a/plugins/fs/README.md +++ b/plugins/fs/README.md @@ -4,7 +4,7 @@ Access the file system. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/global-shortcut/README.md b/plugins/global-shortcut/README.md index f1611804..c2362ae4 100644 --- a/plugins/global-shortcut/README.md +++ b/plugins/global-shortcut/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/http/Cargo.toml b/plugins/http/Cargo.toml index ef5741c6..3272ad39 100644 --- a/plugins/http/Cargo.toml +++ b/plugins/http/Cargo.toml @@ -1,22 +1,15 @@ [package] name = "tauri-plugin-http" version = "0.0.0" -edition = "2021" -#edition.workspace = true -#authors.workspace = true -#license.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true [dependencies] -#serde.workspace = true -#serde_json.workspace = true -#tauri.workspace = true -#log.workspace = true -#thiserror.workspace = true -tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } -serde = "1" -serde_json = "1" -thiserror = "1" - +serde.workspace = true +serde_json.workspace = true +tauri.workspace = true +thiserror.workspace = true glob = "0.3" rand = "0.8" bytes = { version = "1", features = [ "serde" ] } diff --git a/plugins/http/README.md b/plugins/http/README.md index 0bace1a5..de55e8dd 100644 --- a/plugins/http/README.md +++ b/plugins/http/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/http/src/commands/mod.rs b/plugins/http/src/commands/mod.rs index 24acf09e..f38d6eac 100644 --- a/plugins/http/src/commands/mod.rs +++ b/plugins/http/src/commands/mod.rs @@ -1,9 +1,6 @@ use tauri::{path::SafePathBuf, AppHandle, Runtime, State}; -use crate::{ - - ClientId, Http, -}; +use crate::{ClientId, Http}; mod client; use client::{Body, ClientBuilder, FilePart, FormPart, HttpRequestBuilder, ResponseData}; diff --git a/plugins/http/src/scope.rs b/plugins/http/src/scope.rs index 115ba78a..241c2816 100644 --- a/plugins/http/src/scope.rs +++ b/plugins/http/src/scope.rs @@ -38,12 +38,12 @@ impl Scope { #[cfg(test)] mod tests { - use tauri_utils::config::HttpAllowlistScope; + use tauri::utils::config::HttpAllowlistScope; #[test] fn is_allowed() { // plain URL - let scope = super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080" + let scope = super::Scope::new(&HttpAllowlistScope(vec!["http://localhost:8080" .parse() .unwrap()])); assert!(scope.is_allowed(&"http://localhost:8080".parse().unwrap())); @@ -56,10 +56,9 @@ mod tests { assert!(!scope.is_allowed(&"http://local:8080".parse().unwrap())); // URL with fixed path - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080/file.png" - .parse() - .unwrap()])); + let scope = super::Scope::new(&HttpAllowlistScope(vec!["http://localhost:8080/file.png" + .parse() + .unwrap()])); assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); @@ -68,25 +67,22 @@ mod tests { assert!(!scope.is_allowed(&"http://localhost:8080/file.png/other.jpg".parse().unwrap())); // URL with glob pattern - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://localhost:8080/*.png" - .parse() - .unwrap()])); + let scope = super::Scope::new(&HttpAllowlistScope(vec!["http://localhost:8080/*.png" + .parse() + .unwrap()])); assert!(scope.is_allowed(&"http://localhost:8080/file.png".parse().unwrap())); assert!(scope.is_allowed(&"http://localhost:8080/assets/file.png".parse().unwrap())); assert!(!scope.is_allowed(&"http://localhost:8080/file.jpeg".parse().unwrap())); - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://*".parse().unwrap()])); + let scope = super::Scope::new(&HttpAllowlistScope(vec!["http://*".parse().unwrap()])); assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); assert!(!scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); assert!(!scope.is_allowed(&"https://something.else".parse().unwrap())); - let scope = - super::Scope::for_http_api(&HttpAllowlistScope(vec!["http://**".parse().unwrap()])); + let scope = super::Scope::new(&HttpAllowlistScope(vec!["http://**".parse().unwrap()])); assert!(scope.is_allowed(&"http://something.else".parse().unwrap())); assert!(scope.is_allowed(&"http://something.else/path/to/file".parse().unwrap())); diff --git a/plugins/localhost/README.md b/plugins/localhost/README.md index 2a0e9fc3..87919843 100644 --- a/plugins/localhost/README.md +++ b/plugins/localhost/README.md @@ -6,7 +6,7 @@ Expose your apps assets through a localhost server instead of the default custom ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/log/README.md b/plugins/log/README.md index 779f9056..7785ae66 100644 --- a/plugins/log/README.md +++ b/plugins/log/README.md @@ -4,7 +4,7 @@ Configurable logging for your Tauri app. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/notification/README.md b/plugins/notification/README.md index ed5545a2..1387df60 100644 --- a/plugins/notification/README.md +++ b/plugins/notification/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/notification/src/desktop.rs b/plugins/notification/src/desktop.rs index 47be71b5..81473489 100644 --- a/plugins/notification/src/desktop.rs +++ b/plugins/notification/src/desktop.rs @@ -72,15 +72,16 @@ mod imp { /// /// # Examples /// ```rust,no_run - /// use tauri::api::notification::Notification; + /// use tauri_plugin_notification::NotificationExt; /// // first we build the application to access the Tauri configuration /// let app = tauri::Builder::default() /// // on an actual app, remove the string argument - /// .build(tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json")) + /// .build(tauri::generate_context!("test/tauri.conf.json")) /// .expect("error while building tauri application"); /// /// // shows a notification with the given title and body - /// Notification::new(&app.config().tauri.bundle.identifier) + /// app.notification() + /// .builder() /// .title("New message") /// .body("You've got a new message.") /// .show(); @@ -136,15 +137,20 @@ mod imp { /// # Examples /// /// ```no_run - /// use tauri::api::notification::Notification; + /// use tauri_plugin_notification::NotificationExt; /// - /// // on an actual app, remove the string argument - /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"); - /// Notification::new(&context.config().tauri.bundle.identifier) - /// .title("Tauri") - /// .body("Tauri is awesome!") - /// .show() - /// .unwrap(); + /// tauri::Builder::default() + /// .setup(|app| { + /// app.notification() + /// .builder() + /// .title("Tauri") + /// .body("Tauri is awesome!") + /// .show() + /// .unwrap(); + /// Ok(()) + /// }) + /// .run(tauri::generate_context!("test/tauri.conf.json")) + /// .expect("error while running tauri application"); /// ``` /// /// ## Platform-specific @@ -200,22 +206,18 @@ mod imp { /// # Examples /// /// ```no_run - /// use tauri::api::notification::Notification; - /// - /// // on an actual app, remove the string argument - /// let context = tauri::generate_context!("test/fixture/src-tauri/tauri.conf.json"); - /// let identifier = context.config().tauri.bundle.identifier.clone(); + /// use tauri_plugin_notification::NotificationExt; /// /// tauri::Builder::default() /// .setup(move |app| { - /// Notification::new(&identifier) + /// app.notification().builder() /// .title("Tauri") /// .body("Tauri is awesome!") - /// .notify(&app.handle()) + /// .show() /// .unwrap(); /// Ok(()) /// }) - /// .run(context) + /// .run(tauri::generate_context!("test/tauri.conf.json")) /// .expect("error while running tauri application"); /// ``` #[cfg(feature = "windows7-compat")] diff --git a/plugins/notification/test/tauri.conf.json b/plugins/notification/test/tauri.conf.json new file mode 100644 index 00000000..8a9c0108 --- /dev/null +++ b/plugins/notification/test/tauri.conf.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.8/node_modules/@tauri-apps/cli/schema.json", + "build": { + "distDir": ".", + "devPath": "http://localhost:4000" + }, + "tauri": { + "bundle": { + "identifier": "studio.tauri.example", + "active": true, + "icon": ["../../../examples/api/src-tauri/icons/icon.png"] + }, + "windows": [ + { + "title": "Tauri App" + } + ], + "security": { + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'" + } + } +} diff --git a/plugins/os/README.md b/plugins/os/README.md index eb32d944..124a1f30 100644 --- a/plugins/os/README.md +++ b/plugins/os/README.md @@ -4,7 +4,7 @@ Read information about the operating system. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/persisted-scope/README.md b/plugins/persisted-scope/README.md index 2dd60751..01ac82b4 100644 --- a/plugins/persisted-scope/README.md +++ b/plugins/persisted-scope/README.md @@ -4,7 +4,7 @@ Save filesystem and asset scopes and restore them when the app is reopened. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/positioner/README.md b/plugins/positioner/README.md index ac2a152f..26787b0e 100644 --- a/plugins/positioner/README.md +++ b/plugins/positioner/README.md @@ -6,7 +6,7 @@ This plugin is a port of [electron-positioner](https://github.com/jenslind/elect ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/process/README.md b/plugins/process/README.md index 790af6f2..55b83071 100644 --- a/plugins/process/README.md +++ b/plugins/process/README.md @@ -4,7 +4,7 @@ This plugin provides APIs to access the current process. To spawn child processe ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/shell/README.md b/plugins/shell/README.md index 36385bf4..80400eaa 100644 --- a/plugins/shell/README.md +++ b/plugins/shell/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/shell/src/process/mod.rs b/plugins/shell/src/process/mod.rs index 1b3befd7..d677d636 100644 --- a/plugins/shell/src/process/mod.rs +++ b/plugins/shell/src/process/mod.rs @@ -200,24 +200,29 @@ impl Command { /// # Examples /// /// ```rust,no_run - /// use tauri::api::process::{Command, CommandEvent}; - /// tauri::async_runtime::spawn(async move { - /// let (mut rx, mut child) = Command::new("cargo") - /// .args(["tauri", "dev"]) - /// .spawn() - /// .expect("Failed to spawn cargo"); + /// use tauri_plugin_shell::{process::CommandEvent, ShellExt}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let handle = app.handle(); + /// tauri::async_runtime::spawn(async move { + /// let (mut rx, mut child) = handle.shell().command("cargo") + /// .args(["tauri", "dev"]) + /// .spawn() + /// .expect("Failed to spawn cargo"); /// - /// let mut i = 0; - /// while let Some(event) = rx.recv().await { - /// if let CommandEvent::Stdout(line) = event { - /// println!("got: {}", String::from_utf8(line).unwrap()); - /// i += 1; - /// if i == 4 { - /// child.write("message from Rust\n".as_bytes()).unwrap(); - /// i = 0; + /// let mut i = 0; + /// while let Some(event) = rx.recv().await { + /// if let CommandEvent::Stdout(line) = event { + /// println!("got: {}", String::from_utf8(line).unwrap()); + /// i += 1; + /// if i == 4 { + /// child.write("message from Rust\n".as_bytes()).unwrap(); + /// i = 0; + /// } + /// } /// } - /// } - /// } + /// }); + /// Ok(()) /// }); /// ``` pub fn spawn(self) -> crate::Result<(Receiver, CommandChild)> { @@ -288,9 +293,13 @@ impl Command { /// /// # Examples /// ```rust,no_run - /// use tauri::api::process::Command; - /// let status = Command::new("which").args(["ls"]).status().unwrap(); - /// println!("`which` finished with status: {:?}", status.code()); + /// use tauri_plugin_shell::ShellExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// let status = tauri::async_runtime::block_on(async move { app.shell().command("which").args(["ls"]).status().await.unwrap() }); + /// println!("`which` finished with status: {:?}", status.code()); + /// Ok(()) + /// }); /// ``` pub async fn status(self) -> crate::Result { let (mut rx, _child) = self.spawn()?; @@ -310,10 +319,14 @@ impl Command { /// # Examples /// /// ```rust,no_run - /// use tauri::api::process::Command; - /// let output = Command::new("echo").args(["TAURI"]).output().unwrap(); - /// assert!(output.status.success()); - /// assert_eq!(String::from_utf8(output.stdout).unwrap(), "TAURI"); + /// use tauri_plugin_shell::ShellExt; + /// tauri::Builder::default() + /// .setup(|app| { + /// let output = tauri::async_runtime::block_on(async move { app.shell().command("echo").args(["TAURI"]).output().await.unwrap() }); + /// assert!(output.status.success()); + /// assert_eq!(String::from_utf8(output.stdout).unwrap(), "TAURI"); + /// Ok(()) + /// }); /// ``` pub async fn output(self) -> crate::Result { let (mut rx, _child) = self.spawn()?; @@ -387,7 +400,7 @@ mod tests { #[cfg(not(windows))] #[test] fn test_cmd_spawn_output() { - let cmd = Command::new("cat").args(["test/api/test.txt"]); + let cmd = Command::new("cat").args(["test/test.txt"]); let (mut rx, _) = cmd.spawn().unwrap(); tauri::async_runtime::block_on(async move { @@ -408,7 +421,7 @@ mod tests { #[cfg(not(windows))] #[test] fn test_cmd_spawn_raw_output() { - let cmd = Command::new("cat").args(["test/api/test.txt"]); + let cmd = Command::new("cat").args(["test/test.txt"]); let (mut rx, _) = cmd.spawn().unwrap(); tauri::async_runtime::block_on(async move { @@ -430,7 +443,7 @@ mod tests { #[test] // test the failure case fn test_cmd_spawn_fail() { - let cmd = Command::new("cat").args(["test/api/"]); + let cmd = Command::new("cat").args(["test/"]); let (mut rx, _) = cmd.spawn().unwrap(); tauri::async_runtime::block_on(async move { @@ -442,7 +455,7 @@ mod tests { CommandEvent::Stderr(line) => { assert_eq!( String::from_utf8(line).unwrap(), - "cat: test/api/: Is a directory" + "cat: test/: Is a directory" ); } _ => {} @@ -455,7 +468,7 @@ mod tests { #[test] // test the failure case (raw encoding) fn test_cmd_spawn_raw_fail() { - let cmd = Command::new("cat").args(["test/api/"]); + let cmd = Command::new("cat").args(["test/"]); let (mut rx, _) = cmd.spawn().unwrap(); tauri::async_runtime::block_on(async move { @@ -467,7 +480,7 @@ mod tests { CommandEvent::Stderr(line) => { assert_eq!( String::from_utf8(line).unwrap(), - "cat: test/api/: Is a directory" + "cat: test/: Is a directory" ); } _ => {} @@ -479,7 +492,7 @@ mod tests { #[cfg(not(windows))] #[test] fn test_cmd_output_output() { - let cmd = Command::new("cat").args(["test/api/test.txt"]); + let cmd = Command::new("cat").args(["test/test.txt"]); let output = tauri::async_runtime::block_on(cmd.output()).unwrap(); assert_eq!(String::from_utf8(output.stderr).unwrap(), ""); @@ -492,13 +505,13 @@ mod tests { #[cfg(not(windows))] #[test] fn test_cmd_output_output_fail() { - let cmd = Command::new("cat").args(["test/api/"]); + let cmd = Command::new("cat").args(["test/"]); let output = tauri::async_runtime::block_on(cmd.output()).unwrap(); assert_eq!(String::from_utf8(output.stdout).unwrap(), ""); assert_eq!( String::from_utf8(output.stderr).unwrap(), - "cat: test/api/: Is a directory\n" + "cat: test/: Is a directory\n" ); } } diff --git a/plugins/shell/test/test.txt b/plugins/shell/test/test.txt new file mode 100644 index 00000000..ec77307a --- /dev/null +++ b/plugins/shell/test/test.txt @@ -0,0 +1 @@ +This is a test doc! \ No newline at end of file diff --git a/plugins/single-instance/README.md b/plugins/single-instance/README.md index 18ad3f6f..6c72dc4e 100644 --- a/plugins/single-instance/README.md +++ b/plugins/single-instance/README.md @@ -4,7 +4,7 @@ Ensure a single instance of your tauri app is running. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/sql/README.md b/plugins/sql/README.md index 387c5db1..153d23dd 100644 --- a/plugins/sql/README.md +++ b/plugins/sql/README.md @@ -4,7 +4,7 @@ Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx) ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/store/README.md b/plugins/store/README.md index 4f6d1fa0..cc63ab7e 100644 --- a/plugins/store/README.md +++ b/plugins/store/README.md @@ -4,7 +4,7 @@ Simple, persistent key-value store. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/store/src/lib.rs b/plugins/store/src/lib.rs index a7b66b2e..7c7918f4 100644 --- a/plugins/store/src/lib.rs +++ b/plugins/store/src/lib.rs @@ -46,7 +46,7 @@ pub fn with_store) -> Result>( if collection.frozen { return Err(Error::NotFound(path.to_path_buf())); } - let mut store = StoreBuilder::new(app, path.to_path_buf()).build(); + let mut store = StoreBuilder::new(path).build(app); // ignore loading errors, just use the default if let Err(err) = store.load() { warn!( @@ -205,15 +205,14 @@ impl Builder { /// # Examples /// /// ``` - /// # fn main() -> Result<(), Box> { - /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// use tauri_plugin_store::{StoreBuilder, Builder}; /// - /// let store = StoreBuilder::new("store.bin".parse()?).build(); - /// - /// let builder = PluginBuilder::default().store(store); - /// - /// # Ok(()) - /// # } + /// tauri::Builder::default() + /// .setup(|app| { + /// let store = StoreBuilder::new("store.bin").build(app.handle()); + /// let builder = Builder::default().store(store); + /// Ok(()) + /// }); /// ``` pub fn store(mut self, store: Store) -> Self { self.stores.insert(store.path.clone(), store); @@ -225,15 +224,14 @@ impl Builder { /// # Examples /// /// ``` - /// # fn main() -> Result<(), Box> { - /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; - /// - /// let store = StoreBuilder::new("store.bin".parse()?).build(); - /// - /// let builder = PluginBuilder::default().stores([store]); + /// use tauri_plugin_store::{StoreBuilder, Builder}; /// - /// # Ok(()) - /// # } + /// tauri::Builder::default() + /// .setup(|app| { + /// let store = StoreBuilder::new("store.bin").build(app.handle()); + /// let builder = Builder::default().stores([store]); + /// Ok(()) + /// }); /// ``` pub fn stores>>(mut self, stores: T) -> Self { self.stores = stores @@ -250,15 +248,14 @@ impl Builder { /// # Examples /// /// ``` - /// # fn main() -> Result<(), Box> { - /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; + /// use tauri_plugin_store::{StoreBuilder, Builder}; /// - /// let store = StoreBuilder::new("store.bin".parse()?).build(); - /// - /// let builder = PluginBuilder::default().freeze(); - /// - /// # Ok(()) - /// # } + /// tauri::Builder::default() + /// .setup(|app| { + /// let store = StoreBuilder::new("store.bin").build(app.handle()); + /// app.handle().plugin(Builder::default().freeze().build()); + /// Ok(()) + /// }); /// ``` pub fn freeze(mut self) -> Self { self.frozen = true; @@ -270,16 +267,14 @@ impl Builder { /// # Examples /// /// ``` - /// # fn main() -> Result<(), Box> { - /// use tauri_plugin_store::{StoreBuilder,PluginBuilder}; - /// use tauri::Wry; - /// - /// let store = StoreBuilder::new("store.bin".parse()?).build(); - /// - /// let plugin = PluginBuilder::default().build::(); + /// use tauri_plugin_store::{StoreBuilder, Builder}; /// - /// # Ok(()) - /// # } + /// tauri::Builder::default() + /// .setup(|app| { + /// let store = StoreBuilder::new("store.bin").build(app.handle()); + /// app.handle().plugin(Builder::default().build()); + /// Ok(()) + /// }); /// ``` pub fn build(mut self) -> TauriPlugin { plugin::Builder::new("store") diff --git a/plugins/store/src/store.rs b/plugins/store/src/store.rs index 004b05b9..f8cba4e0 100644 --- a/plugins/store/src/store.rs +++ b/plugins/store/src/store.rs @@ -8,7 +8,7 @@ use std::{ collections::HashMap, fs::{create_dir_all, read, File}, io::Write, - path::PathBuf, + path::{Path, PathBuf}, }; use tauri::{AppHandle, Manager, Runtime}; @@ -30,8 +30,7 @@ fn default_deserialize( } /// Builds a [`Store`] -pub struct StoreBuilder { - app: AppHandle, +pub struct StoreBuilder { path: PathBuf, defaults: Option>, cache: HashMap, @@ -39,7 +38,7 @@ pub struct StoreBuilder { deserialize: DeserializeFn, } -impl StoreBuilder { +impl StoreBuilder { /// Creates a new [`StoreBuilder`]. /// /// # Examples @@ -47,15 +46,14 @@ impl StoreBuilder { /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// - /// let builder = StoreBuilder::new("store.bin".parse()?); + /// let builder = StoreBuilder::new("store.bin"); /// /// # Ok(()) /// # } /// ``` - pub fn new(app: AppHandle, path: PathBuf) -> Self { + pub fn new>(path: P) -> Self { Self { - app, - path, + path: path.as_ref().to_path_buf(), defaults: None, cache: Default::default(), serialize: default_serialize, @@ -75,7 +73,7 @@ impl StoreBuilder { /// /// defaults.insert("foo".to_string(), "bar".into()); /// - /// let builder = StoreBuilder::new("store.bin".parse()?) + /// let builder = StoreBuilder::new("store.bin") /// .defaults(defaults); /// /// # Ok(()) @@ -93,7 +91,7 @@ impl StoreBuilder { /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// - /// let builder = StoreBuilder::new("store.bin".parse()?) + /// let builder = StoreBuilder::new("store.bin") /// .default("foo".to_string(), "bar".into()); /// /// # Ok(()) @@ -113,7 +111,7 @@ impl StoreBuilder { /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// - /// let builder = StoreBuilder::new("store.json".parse()?) + /// let builder = StoreBuilder::new("store.json") /// .serialize(|cache| serde_json::to_vec(&cache).map_err(Into::into)); /// /// # Ok(()) @@ -130,7 +128,7 @@ impl StoreBuilder { /// # fn main() -> Result<(), Box> { /// use tauri_plugin_store::StoreBuilder; /// - /// let builder = StoreBuilder::new("store.json".parse()?) + /// let builder = StoreBuilder::new("store.json") /// .deserialize(|bytes| serde_json::from_slice(&bytes).map_err(Into::into)); /// /// # Ok(()) @@ -144,16 +142,15 @@ impl StoreBuilder { /// /// # Examples /// ``` - /// # fn main() -> Result<(), Box> { - /// use tauri_plugin_store::StoreBuilder; - /// - /// let store = StoreBuilder::new("store.bin".parse()?).build(); - /// - /// # Ok(()) - /// # } - pub fn build(self) -> Store { + /// tauri::Builder::default() + /// .setup(|app| { + /// let store = tauri_plugin_store::StoreBuilder::new("store.json").build(app.handle()); + /// Ok(()) + /// }); + /// ``` + pub fn build(self, app: AppHandle) -> Store { Store { - app: self.app, + app, path: self.path, defaults: self.defaults, cache: self.cache, diff --git a/plugins/stronghold/README.md b/plugins/stronghold/README.md index da84b0d7..b57bd2d9 100644 --- a/plugins/stronghold/README.md +++ b/plugins/stronghold/README.md @@ -4,7 +4,7 @@ Store secrets and keys using the [IOTA Stronghold](https://github.com/iotaledger ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/updater/Cargo.toml b/plugins/updater/Cargo.toml index a7da0aeb..ab74fd5d 100644 --- a/plugins/updater/Cargo.toml +++ b/plugins/updater/Cargo.toml @@ -1,20 +1,15 @@ [package] name = "tauri-plugin-updater" version = "0.0.0" -edition = "2021" -#edition.workspace = true -#authors.workspace = true -#license.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true [dependencies] -#tauri = { workspace = true, features = ["updater"] } -#serde.workspace = true -#serde_json.workspace = true -#thiserror.workspace = true -tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = ["updater", "fs-extract-api"] } -serde = "1" -serde_json = "1" -thiserror = "1" +tauri = { workspace = true, features = ["updater", "fs-extract-api"] } +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true tokio = "1" reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] } diff --git a/plugins/updater/README.md b/plugins/updater/README.md index cb6e26fa..d618b606 100644 --- a/plugins/updater/README.md +++ b/plugins/updater/README.md @@ -4,7 +4,7 @@ In-app updates for Tauri applications. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/updater/tests/app-updater/Cargo.toml b/plugins/updater/tests/app-updater/Cargo.toml index 72c1e1f7..0d09ee9e 100644 --- a/plugins/updater/tests/app-updater/Cargo.toml +++ b/plugins/updater/tests/app-updater/Cargo.toml @@ -1,18 +1,17 @@ [package] name = "app-updater" version = "0.1.0" -edition = "2021" -#edition.workspace = true +edition.workspace = true [build-dependencies] -tauri-build = { path = "../../../../../tauri/core/tauri-build", features = [] } +tauri-build.workspace = true [dependencies] -tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" } +tauri.workspace = true +serde.workspace = true +serde_json.workspace = true tauri-plugin-updater = { path = "../.." } tiny_http = "0.11" -serde = "1" -serde_json = "1" time = { version = "0.3", features = ["formatting"] } [features] diff --git a/plugins/updater/tests/app-updater/build.rs b/plugins/updater/tests/app-updater/build.rs index b055ec37..5ebf8d2f 100644 --- a/plugins/updater/tests/app-updater/build.rs +++ b/plugins/updater/tests/app-updater/build.rs @@ -3,5 +3,5 @@ // SPDX-License-Identifier: MIT fn main() { - tauri_build::build() + tauri_build::build() } diff --git a/plugins/updater/tests/app-updater/src/main.rs b/plugins/updater/tests/app-updater/src/main.rs index 272da058..12678e78 100644 --- a/plugins/updater/tests/app-updater/src/main.rs +++ b/plugins/updater/tests/app-updater/src/main.rs @@ -7,43 +7,43 @@ use tauri_plugin_updater::UpdaterExt; fn main() { - let mut context = tauri::generate_context!(); - if std::env::var("TARGET").unwrap_or_default() == "nsis" { - // /D sets the default installation directory ($INSTDIR), - // overriding InstallDir and InstallDirRegKey. - // It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces. - // Only absolute paths are supported. - // NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder - context.config_mut().tauri.updater.windows.installer_args = vec![format!( - "/D={}", - tauri::utils::platform::current_exe() - .unwrap() - .parent() - .unwrap() - .display() - )]; - } - tauri::Builder::default() - .plugin(tauri_plugin_updater::Builder::new().build()) - .setup(|app| { - let handle = app.handle(); - tauri::async_runtime::spawn(async move { - match handle.updater().check().await { - Ok(update) => { - if let Err(e) = update.download_and_install().await { - println!("{e}"); - std::process::exit(1); - } - std::process::exit(0); - } - Err(e) => { - println!("{e}"); - std::process::exit(1); - } - } - }); - Ok(()) - }) - .run(context) - .expect("error while running tauri application"); + let mut context = tauri::generate_context!(); + if std::env::var("TARGET").unwrap_or_default() == "nsis" { + // /D sets the default installation directory ($INSTDIR), + // overriding InstallDir and InstallDirRegKey. + // It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces. + // Only absolute paths are supported. + // NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder + context.config_mut().tauri.updater.windows.installer_args = vec![format!( + "/D={}", + tauri::utils::platform::current_exe() + .unwrap() + .parent() + .unwrap() + .display() + )]; + } + tauri::Builder::default() + .plugin(tauri_plugin_updater::Builder::new().build()) + .setup(|app| { + let handle = app.handle(); + tauri::async_runtime::spawn(async move { + match handle.updater().check().await { + Ok(update) => { + if let Err(e) = update.download_and_install(|_event| {}).await { + println!("{e}"); + std::process::exit(1); + } + std::process::exit(0); + } + Err(e) => { + println!("{e}"); + std::process::exit(1); + } + } + }); + Ok(()) + }) + .run(context) + .expect("error while running tauri application"); } diff --git a/plugins/updater/tests/app-updater/tests/update.rs b/plugins/updater/tests/app-updater/tests/update.rs index ac9a7dd7..6cc2033b 100644 --- a/plugins/updater/tests/app-updater/tests/update.rs +++ b/plugins/updater/tests/app-updater/tests/update.rs @@ -176,7 +176,7 @@ fn update_app() { let target = tauri_plugin_updater::target().expect("running updater test in an unsupported platform"); let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let root_dir = manifest_dir.clone(); + let root_dir = manifest_dir.join("../../../.."); let mut config = Config { package: PackageConfig { version: "1.0.0" }, diff --git a/plugins/upload/README.md b/plugins/upload/README.md index 08401560..a7e387ee 100644 --- a/plugins/upload/README.md +++ b/plugins/upload/README.md @@ -4,7 +4,7 @@ Upload files from disk to a remote server over HTTP. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/upload/guest-js/index.ts b/plugins/upload/guest-js/index.ts index 1a605633..f13a1627 100644 --- a/plugins/upload/guest-js/index.ts +++ b/plugins/upload/guest-js/index.ts @@ -1,30 +1,38 @@ -import { invoke } from "@tauri-apps/api/tauri"; -import { appWindow } from "tauri-plugin-window-api"; +import { invoke, transformCallback } from "@tauri-apps/api/tauri"; interface ProgressPayload { - id: number; progress: number; total: number; } -type ProgressHandler = (progress: number, total: number) => void; -const handlers: Map = new Map(); -let listening = false; +type ProgressHandler = (progress: ProgressPayload) => void; -async function listenToEventIfNeeded(event: string): Promise { - if (listening) { - return await Promise.resolve(); - } - return await appWindow - .listen(event, ({ payload }) => { - const handler = handlers.get(payload.id); - if (handler != null) { - handler(payload.progress, payload.total); - } - }) - .then(() => { - listening = true; +// TODO: use channel from @tauri-apps/api on v2 +class Channel { + id: number; + // @ts-expect-error field used by the IPC serializer + private readonly __TAURI_CHANNEL_MARKER__ = true; + #onmessage: (response: T) => void = () => { + // no-op + }; + + constructor() { + this.id = transformCallback((response: T) => { + this.#onmessage(response); }); + } + + set onmessage(handler: (response: T) => void) { + this.#onmessage = handler; + } + + get onmessage(): (response: T) => void { + return this.#onmessage; + } + + toJSON(): string { + return `__CHANNEL__:${this.id}`; + } } async function upload( @@ -37,17 +45,17 @@ async function upload( window.crypto.getRandomValues(ids); const id = ids[0]; + const onProgress = new Channel(); if (progressHandler != null) { - handlers.set(id, progressHandler); + onProgress.onmessage = progressHandler; } - await listenToEventIfNeeded("upload://progress"); - await invoke("plugin:upload|upload", { id, url, filePath, headers: headers ?? {}, + onProgress, }); } @@ -65,17 +73,17 @@ async function download( window.crypto.getRandomValues(ids); const id = ids[0]; + const onProgress = new Channel(); if (progressHandler != null) { - handlers.set(id, progressHandler); + onProgress.onmessage = progressHandler; } - await listenToEventIfNeeded("download://progress"); - await invoke("plugin:upload|download", { id, url, filePath, headers: headers ?? {}, + onProgress, }); } diff --git a/plugins/upload/package.json b/plugins/upload/package.json index e84f3b62..acd01d42 100644 --- a/plugins/upload/package.json +++ b/plugins/upload/package.json @@ -28,7 +28,6 @@ "tslib": "^2.5.0" }, "dependencies": { - "@tauri-apps/api": "^1.2.0", - "tauri-plugin-window-api": "0.0.0" + "@tauri-apps/api": "^1.2.0" } } diff --git a/plugins/upload/src/lib.rs b/plugins/upload/src/lib.rs index c4a0d8c7..714fd1ca 100644 --- a/plugins/upload/src/lib.rs +++ b/plugins/upload/src/lib.rs @@ -5,16 +5,17 @@ use futures_util::TryStreamExt; use serde::{ser::Serializer, Serialize}; use tauri::{ + api::ipc::Channel, command, plugin::{Builder as PluginBuilder, TauriPlugin}, - Runtime, Window, + Runtime, }; use tokio::{fs::File, io::AsyncWriteExt}; use tokio_util::codec::{BytesCodec, FramedRead}; use read_progress_stream::ReadProgressStream; -use std::{collections::HashMap, sync::Mutex}; +use std::collections::HashMap; type Result = std::result::Result; @@ -39,19 +40,17 @@ impl Serialize for Error { #[derive(Clone, Serialize)] struct ProgressPayload { - id: u32, progress: u64, total: u64, } #[command] async fn download( - window: Window, - id: u32, url: &str, file_path: &str, headers: HashMap, -) -> Result { + on_progress: Channel, +) -> Result<()> { let client = reqwest::Client::new(); let mut request = client.get(url); @@ -69,33 +68,28 @@ async fn download( while let Some(chunk) = stream.try_next().await? { file.write_all(&chunk).await?; - let _ = window.emit( - "download://progress", - ProgressPayload { - id, - progress: chunk.len() as u64, - total, - }, - ); + let _ = on_progress.send(&ProgressPayload { + progress: chunk.len() as u64, + total, + }); } - Ok(id) + Ok(()) } #[command] async fn upload( - window: Window, - id: u32, url: &str, file_path: &str, headers: HashMap, + on_progress: Channel, ) -> Result { // Read the file let file = File::open(file_path).await?; // Create the request and attach the file to the body let client = reqwest::Client::new(); - let mut request = client.post(url).body(file_to_body(id, window, file)); + let mut request = client.post(url).body(file_to_body(on_progress, file)); // Loop trought the headers keys and values // and add them to the request object. @@ -108,20 +102,13 @@ async fn upload( response.json().await.map_err(Into::into) } -fn file_to_body(id: u32, window: Window, file: File) -> reqwest::Body { +fn file_to_body(channel: Channel, file: File) -> reqwest::Body { let stream = FramedRead::new(file, BytesCodec::new()).map_ok(|r| r.freeze()); - let window = Mutex::new(window); + reqwest::Body::wrap_stream(ReadProgressStream::new( stream, Box::new(move |progress, total| { - let _ = window.lock().unwrap().emit( - "upload://progress", - ProgressPayload { - id, - progress, - total, - }, - ); + let _ = channel.send(&ProgressPayload { progress, total }); }), )) } diff --git a/plugins/websocket/README.md b/plugins/websocket/README.md index 4557b851..1e2aa98e 100644 --- a/plugins/websocket/README.md +++ b/plugins/websocket/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/window-state/README.md b/plugins/window-state/README.md index 710270a3..ef99f245 100644 --- a/plugins/window-state/README.md +++ b/plugins/window-state/README.md @@ -4,7 +4,7 @@ Save window positions and sizes and restore them when the app is reopened. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/plugins/window-state/guest-js/index.ts b/plugins/window-state/guest-js/index.ts index a40d8183..2d303112 100644 --- a/plugins/window-state/guest-js/index.ts +++ b/plugins/window-state/guest-js/index.ts @@ -1,5 +1,17 @@ import { invoke } from "@tauri-apps/api/tauri"; -import { WindowLabel, getCurrent } from "tauri-plugin-window-api"; + +interface WindowDef { + label: string; +} + +declare global { + interface Window { + __TAURI_METADATA__: { + __windows: WindowDef[]; + __currentWindow: WindowDef; + }; + } +} export enum StateFlags { SIZE = 1 << 0, @@ -21,7 +33,7 @@ async function saveWindowState(flags: StateFlags) { /** * Restore the state for the specified window from disk. */ -async function restoreState(label: WindowLabel, flags: StateFlags) { +async function restoreState(label: string, flags: StateFlags) { invoke("plugin:window-state|restore_state", { label, flags }); } @@ -29,7 +41,7 @@ async function restoreState(label: WindowLabel, flags: StateFlags) { * Restore the state for the current window from disk. */ async function restoreStateCurrent(flags: StateFlags) { - restoreState(getCurrent().label, flags); + restoreState(window.__TAURI_METADATA__.__currentWindow.label, flags); } export { restoreState, restoreStateCurrent, saveWindowState }; diff --git a/plugins/window-state/package.json b/plugins/window-state/package.json index d6937f9f..f7938586 100644 --- a/plugins/window-state/package.json +++ b/plugins/window-state/package.json @@ -28,7 +28,6 @@ "tslib": "^2.5.0" }, "dependencies": { - "@tauri-apps/api": "^1.2.0", - "tauri-plugin-window-api": "0.0.0" + "@tauri-apps/api": "^1.2.0" } } diff --git a/plugins/window/README.md b/plugins/window/README.md index 4d6a6c8a..00257ccc 100644 --- a/plugins/window/README.md +++ b/plugins/window/README.md @@ -4,7 +4,7 @@ Interact with the Tauri window. ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c43adf01..2305976a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -198,9 +198,6 @@ importers: '@tauri-apps/api': specifier: ^1.2.0 version: 1.2.0 - tauri-plugin-window-api: - specifier: 0.0.0 - version: link:../window devDependencies: tslib: specifier: ^2.5.0 @@ -337,9 +334,6 @@ importers: '@tauri-apps/api': specifier: ^1.2.0 version: 1.2.0 - tauri-plugin-window-api: - specifier: 0.0.0 - version: link:../window devDependencies: tslib: specifier: ^2.5.0 @@ -401,9 +395,6 @@ importers: '@tauri-apps/api': specifier: ^1.2.0 version: 1.2.0 - tauri-plugin-window-api: - specifier: 0.0.0 - version: link:../window devDependencies: tslib: specifier: ^2.5.0 diff --git a/shared/template/README.md b/shared/template/README.md index 9ef4ffe0..f1641d84 100644 --- a/shared/template/README.md +++ b/shared/template/README.md @@ -4,7 +4,7 @@ ## Install -_This plugin requires a Rust version of at least **1.64**_ +_This plugin requires a Rust version of at least **1.65**_ There are three general methods of installation that we can recommend.