diff --git a/.changes/android-home-dir.md b/.changes/android-home-dir.md deleted file mode 100644 index b2b5a5885cd0..000000000000 --- a/.changes/android-home-dir.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": "patch:feat" ---- - -Add `PathResolver::home_dir()` method on Android. diff --git a/.changes/bundler-linux-recommends.md b/.changes/bundler-linux-recommends.md deleted file mode 100644 index 13a18b9f8025..000000000000 --- a/.changes/bundler-linux-recommends.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri-bundler": "patch:feat" ---- - -Add `bundle > linux > deb > recommends` and `bundle > linux > rpm > recommends` fields to declare a strong, but not absolute, dependency for your `.deb` and `.rpm` packages. - diff --git a/.changes/cli-updater-errorr.md b/.changes/cli-updater-errorr.md deleted file mode 100644 index 8a865ac290d5..000000000000 --- a/.changes/cli-updater-errorr.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"tauri-cli": "patch:enhance" -"@tauri-apps/cli": "patch:enhance" ---- - -Add more context for errors when decoding secret and public keys for signing updater artifacts. - diff --git a/.changes/curosr-position-gtk.md b/.changes/curosr-position-gtk.md deleted file mode 100644 index 4bc87b6bb9c6..000000000000 --- a/.changes/curosr-position-gtk.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"tauri": "patch:bug" -"tauri-runtime-wry": "patch:bug" ---- - -Fix `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter method failing on Linux with `GDK may only be used from the main thread`. diff --git a/.changes/deb-updater-support.md b/.changes/deb-updater-support.md new file mode 100644 index 000000000000..001a5b1dccd7 --- /dev/null +++ b/.changes/deb-updater-support.md @@ -0,0 +1,7 @@ +--- +'tauri-bundler': minor:feat +'tauri-cli': minor:feat +'@tauri-apps/cli': minor:feat +--- + +Generate signature for `.deb` packages when `createUpdaterArtifacts` option is enabled. diff --git a/.changes/extension-path.md b/.changes/extension-path.md new file mode 100644 index 000000000000..caec2b54d50e --- /dev/null +++ b/.changes/extension-path.md @@ -0,0 +1,7 @@ +--- +"tauri": "minor:feat" +"tauri-runtime": "minor:feat" +"tauri-runtime-wry": "minor:feat" +--- + +Add `WebviewWindowBuilder/WebviewBuilder::extensions_path` on Linux and Windows. diff --git a/.changes/info-linux-de-and-session.md b/.changes/info-linux-de-and-session.md new file mode 100644 index 000000000000..024c5f23591b --- /dev/null +++ b/.changes/info-linux-de-and-session.md @@ -0,0 +1,7 @@ +--- +"tauri-cli": "patch:feat" +"@tauri-apps/cli": "patch:feat" +--- + +Include Linux destkop environment and session type in `tauri info` command. + diff --git a/.changes/js-submenu-in-options.md b/.changes/js-submenu-in-options.md deleted file mode 100644 index 3a0ae0c8a280..000000000000 --- a/.changes/js-submenu-in-options.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@tauri-apps/api": "patch:bug" ---- - -Fix submenu created as a menu item instead of a submenu when created by using an object in the `items` field in the options object passed to `Menu.new` or `Submenu.new`. diff --git a/.changes/json5-capability-files.md b/.changes/json5-capability-files.md new file mode 100644 index 000000000000..b0fe7fb54699 --- /dev/null +++ b/.changes/json5-capability-files.md @@ -0,0 +1,6 @@ +--- +"tauri": patch:bug +"tauri-utils": patch:bug +--- + +Fix `.json5` capability files not recognized even with `config-json5` feature enabled diff --git a/.changes/migrate-schema.md b/.changes/migrate-schema.md deleted file mode 100644 index 1430e589853c..000000000000 --- a/.changes/migrate-schema.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@tauri-apps/cli": patch:enhance -"tauri-cli": patch:enhance ---- - -Migrate the `$schema` Tauri configuration to the v2 format. diff --git a/.changes/permission-add-default-windows.md b/.changes/permission-add-default-windows.md deleted file mode 100644 index 3540e83e44e9..000000000000 --- a/.changes/permission-add-default-windows.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@tauri-apps/cli": patch:enhance -"tauri-cli": patch:enhance ---- - -Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands. diff --git a/.changes/resolve_command_scope.md b/.changes/resolve_command_scope.md deleted file mode 100644 index 8245a8acb3aa..000000000000 --- a/.changes/resolve_command_scope.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"tauri": patch:feat ---- - -Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime. diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index 0e0acef62dc4..6aef978f34bc 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -90,7 +90,6 @@ jobs: - name: downgrade crates with MSRV conflict run: | - cargo update -p ravif --precise 0.11.5 cargo update -p aws-config --precise 1.5.5 cargo update -p aws-sdk-ssooidc --precise 1.40.0 cargo update -p aws-sdk-s3 --precise 1.46.0 diff --git a/Cargo.lock b/Cargo.lock index bf60de3db43b..b97cf5f91a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.1.0" dependencies = [ "insta", "serde_json", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", ] [[package]] @@ -217,9 +217,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.91" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "api" @@ -254,7 +254,7 @@ dependencies = [ "rsa", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", "x509-certificate", ] @@ -338,7 +338,7 @@ dependencies = [ "spki 0.7.3", "subtle", "tempfile", - "thiserror", + "thiserror 1.0.68", "tokio", "tungstenite 0.21.0", "uuid", @@ -366,7 +366,7 @@ dependencies = [ "scroll", "serde", "serde-xml-rs", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -392,7 +392,7 @@ dependencies = [ "sha1", "sha2", "signature 2.2.0", - "thiserror", + "thiserror 1.0.68", "url", "x509-certificate", "xml-rs", @@ -422,7 +422,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -467,7 +467,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -479,7 +479,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -507,7 +507,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -570,18 +570,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" dependencies = [ "arrayvec", ] [[package]] name = "aws-config" -version = "1.5.5" +version = "1.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697" +checksum = "2d6448cfb224dd6a9b9ac734f58622dd0d4751f3589f3b777345745f46b2eb14" dependencies = [ "aws-credential-types", "aws-runtime", @@ -682,9 +682,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.39.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11822090cf501c316c6f75711d77b96fba30658e3867a7762e5e2f5d32d31e81" +checksum = "ded855583fa1d22e88fe39fd6062b062376e50a8211989e07cf5e38d52eb3453" dependencies = [ "aws-credential-types", "aws-runtime", @@ -704,9 +704,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.40.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a2a06ff89176123945d1bbe865603c4d7101bea216a550bb4d2e4e9ba74d74" +checksum = "9177ea1192e6601ae16c7273385690d88a7ed386a00b74a6bc894d12103cd933" dependencies = [ "aws-credential-types", "aws-runtime", @@ -726,9 +726,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.39.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20a91795850826a6f456f4a48eff1dfa59a0e69bdbf5b8c50518fd372106574" +checksum = "823ef553cf36713c97453e2ddff1eb8f62be7f4523544e2a5db64caf80100f0a" dependencies = [ "aws-credential-types", "aws-runtime", @@ -861,9 +861,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.7.1" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1ce695746394772e7000b39fe073095db6d45a862d0767dd5ad0ac0d7f8eb87" +checksum = "be28bd063fa91fd871d131fc8b68d7cd4c5fa0869bea68daca50dcb1cbd76be2" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -905,9 +905,9 @@ dependencies = [ [[package]] name = "aws-smithy-types" -version = "1.2.7" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", "bytes", @@ -1020,7 +1020,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide 0.8.0", - "object 0.36.4", + "object 0.36.5", "rustc-demangle", "windows-targets 0.52.6", ] @@ -1160,7 +1160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57792b99d555ebf109c83169228076f7d997e2b37ba1a653850ccd703ac7bab0" dependencies = [ "sysctl", - "thiserror", + "thiserror 1.0.68", "uname", "winapi", ] @@ -1265,7 +1265,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "syn_derive", ] @@ -1383,9 +1383,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -1438,7 +1438,7 @@ dependencies = [ "glib", "libc", "once_cell", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -1499,7 +1499,7 @@ dependencies = [ "serde", "serde_json", "textwrap", - "thiserror", + "thiserror 1.0.68", "toml 0.8.19", "ureq", "which 6.0.3", @@ -1527,7 +1527,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -1697,7 +1697,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -1879,6 +1879,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "cookie-factory" version = "0.3.3" @@ -1986,7 +1996,7 @@ dependencies = [ "chrono", "is_executable", "simple-file-manifest", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -2144,7 +2154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2154,7 +2164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2201,7 +2211,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2225,7 +2235,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2236,7 +2246,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2313,7 +2323,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2334,7 +2344,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2344,7 +2354,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2357,7 +2367,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2384,7 +2394,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.68", "zeroize", ] @@ -2468,7 +2478,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2491,7 +2501,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -2740,7 +2750,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" dependencies = [ "num-traits", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3018,7 +3028,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3059,7 +3069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" dependencies = [ "nom", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -3143,7 +3153,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3367,7 +3377,7 @@ dependencies = [ "once_cell", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -3403,7 +3413,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -3417,7 +3427,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3542,7 +3552,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -3595,16 +3605,17 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.1.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25b617d1375ef96eeb920ae717e3da34a02fc979fe632c75128350f9e1f74a" +checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315" dependencies = [ "log", + "num-order", "pest", "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -3930,6 +3941,124 @@ dependencies = [ "png", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "idea" version = "0.5.1" @@ -3947,12 +4076,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3973,9 +4113,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.4" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", "byteorder-lite", @@ -4150,7 +4290,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -4183,9 +4323,9 @@ dependencies = [ [[package]] name = "is_executable" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba3d8548b8b04dafdf2f4cc6f5e379db766d0a6d9aac233ad4c9a92ea892233" +checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" dependencies = [ "winapi", ] @@ -4295,7 +4435,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.68", "walkdir", "windows-sys 0.45.0", ] @@ -4338,7 +4478,7 @@ checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b" dependencies = [ "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -4350,7 +4490,7 @@ dependencies = [ "jsonptr 0.4.7", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -4362,7 +4502,7 @@ dependencies = [ "jsonptr 0.6.3", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -4421,7 +4561,7 @@ dependencies = [ "jsonrpsee-core", "pin-project", "soketto", - "thiserror", + "thiserror 1.0.68", "tokio", "tokio-util", "tracing", @@ -4448,7 +4588,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", "tokio", "tokio-stream", "tracing", @@ -4473,7 +4613,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror", + "thiserror 1.0.68", "tokio", "tokio-stream", "tokio-util", @@ -4490,7 +4630,7 @@ dependencies = [ "http 1.1.0", "serde", "serde_json", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -4528,7 +4668,7 @@ dependencies = [ "parking_lot", "percent-encoding", "regex", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "time", @@ -4784,6 +4924,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "local-ip-address" version = "0.6.3" @@ -4792,7 +4938,7 @@ checksum = "3669cf5561f8d27e8fc84cc15e58350e70f557d4d65f70e3154e54cd2f8e1782" dependencies = [ "libc", "neli", - "thiserror", + "thiserror 1.0.68", "windows-sys 0.59.0", ] @@ -4964,7 +5110,7 @@ dependencies = [ "miette-derive", "owo-colors", "textwrap", - "thiserror", + "thiserror 1.0.68", "unicode-width", ] @@ -4976,7 +5122,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5082,7 +5228,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror", + "thiserror 1.0.68", "windows-sys 0.59.0", ] @@ -5116,7 +5262,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5131,7 +5277,7 @@ dependencies = [ "quote", "regex", "semver", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5172,7 +5318,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -5362,7 +5508,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5385,6 +5531,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -5424,7 +5585,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5679,9 +5840,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -5719,12 +5880,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -5755,7 +5913,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -5866,7 +6024,7 @@ checksum = "197b36739db0e80919e19a90785233eea5664697d4cd829bd49af34838ec43d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6125,7 +6283,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6160,7 +6318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.68", "ucd-trie", ] @@ -6184,7 +6342,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6256,7 +6414,7 @@ dependencies = [ "sha3", "signature 2.2.0", "smallvec", - "thiserror", + "thiserror 1.0.68", "twofish", "x25519-dalek", "zeroize", @@ -6366,7 +6524,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6425,7 +6583,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6521,12 +6679,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -6642,7 +6794,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "version_check", "yansi", ] @@ -6663,7 +6815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -6771,7 +6923,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.13", "socket2", - "thiserror", + "thiserror 1.0.68", "tokio", "tracing", ] @@ -6788,7 +6940,7 @@ dependencies = [ "rustc-hash", "rustls 0.23.13", "slab", - "thiserror", + "thiserror 1.0.68", "tinyvec", "tracing", ] @@ -6979,16 +7131,16 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.68", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.5" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" dependencies = [ "avif-serialize", "imgref", @@ -7051,7 +7203,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -7142,9 +7294,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -7354,7 +7506,7 @@ dependencies = [ "pgp", "sha1", "sha2", - "thiserror", + "thiserror 1.0.68", "xz2", "zstd", ] @@ -7685,7 +7837,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -7711,7 +7863,7 @@ checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -7893,7 +8045,7 @@ checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" dependencies = [ "log", "serde", - "thiserror", + "thiserror 1.0.68", "xml-rs", ] @@ -7905,7 +8057,7 @@ checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -7916,7 +8068,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -7959,7 +8111,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8010,7 +8162,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8048,7 +8200,7 @@ checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8245,7 +8397,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.68", "time", ] @@ -8428,7 +8580,7 @@ checksum = "4ccbb212565d2dc177bc15ecb7b039d66c4490da892436a4eee5b394d620c9bc" dependencies = [ "paste", "specta-macros", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -8440,7 +8592,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8661,9 +8813,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.85" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -8679,7 +8831,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8709,6 +8861,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sysctl" version = "0.4.6" @@ -8718,7 +8881,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "libc", - "thiserror", + "thiserror 1.0.68", "walkdir", ] @@ -8758,9 +8921,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.30.2" +version = "0.30.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e48d7c56b3f7425d061886e8ce3b6acfab1993682ed70bef50fd133d721ee6" +checksum = "833b4d43383d76d5078d72f3acd977f47eb5b6751eb40baa665d13828e7b79df" dependencies = [ "bitflags 2.6.0", "cocoa 0.26.0", @@ -8803,7 +8966,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -8814,9 +8977,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", @@ -8831,7 +8994,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.0.6" +version = "2.1.1" dependencies = [ "anyhow", "bytes", @@ -8863,7 +9026,7 @@ dependencies = [ "quickcheck", "quickcheck_macros", "raw-window-handle", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "serde_repr", @@ -8875,8 +9038,8 @@ dependencies = [ "tauri-macros", "tauri-runtime", "tauri-runtime-wry", - "tauri-utils 2.0.2", - "thiserror", + "tauri-utils 2.1.0", + "thiserror 2.0.0", "tokio", "tracing", "tray-icon", @@ -8891,7 +9054,7 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.0.2" +version = "2.0.3" dependencies = [ "anyhow", "cargo_toml", @@ -8905,7 +9068,7 @@ dependencies = [ "serde", "serde_json", "tauri-codegen", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "tauri-winres", "toml 0.8.19", "walkdir", @@ -8913,7 +9076,7 @@ dependencies = [ [[package]] name = "tauri-bundler" -version = "2.0.4" +version = "2.1.0" dependencies = [ "anyhow", "ar", @@ -8942,9 +9105,9 @@ dependencies = [ "tar", "tauri-icns", "tauri-macos-sign", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "tempfile", - "thiserror", + "thiserror 2.0.0", "time", "ureq", "url", @@ -8957,7 +9120,7 @@ dependencies = [ [[package]] name = "tauri-cli" -version = "2.0.4" +version = "2.1.0" dependencies = [ "anyhow", "ar", @@ -8999,7 +9162,7 @@ dependencies = [ "minisign", "notify", "notify-debouncer-mini", - "object 0.36.4", + "object 0.36.5", "os_info", "os_pipe", "oxc_allocator", @@ -9022,7 +9185,7 @@ dependencies = [ "tauri-icns", "tauri-macos-sign", "tauri-utils 1.6.0", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "tempfile", "tokio", "toml 0.8.19", @@ -9047,7 +9210,7 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.0.2" +version = "2.0.3" dependencies = [ "base64 0.22.1", "brotli", @@ -9062,9 +9225,9 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.85", - "tauri-utils 2.0.2", - "thiserror", + "syn 2.0.87", + "tauri-utils 2.1.0", + "thiserror 2.0.0", "time", "url", "uuid", @@ -9131,14 +9294,14 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.0.2" +version = "2.0.3" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "tauri-codegen", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", ] [[package]] @@ -9160,7 +9323,7 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.0.2" +version = "2.0.3" dependencies = [ "anyhow", "glob", @@ -9168,7 +9331,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "toml 0.8.19", "walkdir", ] @@ -9191,7 +9354,7 @@ dependencies = [ "swift-rs", "tauri", "tauri-plugin 2.0.0-rc.13", - "thiserror", + "thiserror 1.0.68", "time", ] @@ -9202,13 +9365,13 @@ dependencies = [ "log", "serde", "tauri", - "tauri-plugin 2.0.2", - "thiserror", + "tauri-plugin 2.0.3", + "thiserror 2.0.0", ] [[package]] name = "tauri-runtime" -version = "2.1.1" +version = "2.2.0" dependencies = [ "dpi", "gtk", @@ -9217,15 +9380,15 @@ dependencies = [ "raw-window-handle", "serde", "serde_json", - "tauri-utils 2.0.2", - "thiserror", + "tauri-utils 2.1.0", + "thiserror 2.0.0", "url", "windows", ] [[package]] name = "tauri-runtime-wry" -version = "2.1.2" +version = "2.2.0" dependencies = [ "gtk", "http 1.1.0", @@ -9239,7 +9402,7 @@ dependencies = [ "softbuffer", "tao", "tauri-runtime", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "tracing", "url", "webkit2gtk", @@ -9255,7 +9418,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "tauri-utils 2.0.2", + "tauri-utils 2.1.0", "url", ] @@ -9298,7 +9461,7 @@ dependencies = [ "serde_json", "serde_with", "serialize-to-javascript", - "thiserror", + "thiserror 1.0.68", "toml 0.7.8", "url", "windows-version", @@ -9331,7 +9494,7 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror", + "thiserror 1.0.68", "toml 0.8.19", "url", "urlpattern", @@ -9340,7 +9503,7 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.0.2" +version = "2.1.0" dependencies = [ "aes-gcm", "brotli", @@ -9350,6 +9513,7 @@ dependencies = [ "getrandom 0.2.15", "glob", "html5ever", + "http 1.1.0", "infer 0.16.0", "json-patch 3.0.1", "json5", @@ -9369,7 +9533,7 @@ dependencies = [ "serial_test", "serialize-to-javascript", "swift-rs", - "thiserror", + "thiserror 2.0.0", "toml 0.8.19", "url", "urlpattern", @@ -9461,22 +9625,42 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +dependencies = [ + "thiserror-impl 1.0.68", +] + +[[package]] +name = "thiserror" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +dependencies = [ + "thiserror-impl 2.0.0", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -9562,6 +9746,16 @@ dependencies = [ "url", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -9579,9 +9773,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", @@ -9603,7 +9797,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -9809,7 +10003,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -9838,7 +10032,7 @@ dependencies = [ "once_cell", "png", "serde", - "thiserror", + "thiserror 1.0.68", "windows-sys 0.59.0", ] @@ -9874,7 +10068,7 @@ dependencies = [ "rustls-native-certs 0.7.3", "rustls-pki-types", "sha1", - "thiserror", + "thiserror 1.0.68", "url", "utf-8", ] @@ -9893,7 +10087,7 @@ dependencies = [ "log", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.68", "utf-8", ] @@ -10050,15 +10244,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-properties" version = "0.1.3" @@ -10137,9 +10322,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -10198,12 +10383,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -10380,7 +10577,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -10414,7 +10611,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -10529,7 +10726,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -10538,7 +10735,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" dependencies = [ - "thiserror", + "thiserror 1.0.68", "windows", "windows-core 0.58.0", ] @@ -10664,7 +10861,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -10675,7 +10872,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", ] [[package]] @@ -11037,7 +11234,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.5.0", "serde_json", - "thiserror", + "thiserror 1.0.68", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -11051,7 +11248,7 @@ dependencies = [ "async-trait", "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-macro-support", @@ -11070,14 +11267,27 @@ dependencies = [ "web-sys", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wry" -version = "0.46.1" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73" +checksum = "553ca1ce149982123962fac2506aa75b8b76288779a77e72b12fa2fc34938647" dependencies = [ "base64 0.22.1", "block2", + "cookie", "crossbeam-channel", "dpi", "dunce", @@ -11101,8 +11311,9 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror", + "thiserror 1.0.68", "tracing", + "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", @@ -11179,7 +11390,7 @@ dependencies = [ "ring", "signature 2.2.0", "spki 0.7.3", - "thiserror", + "thiserror 1.0.68", "zeroize", ] @@ -11233,6 +11444,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -11251,7 +11486,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure 0.13.1", ] [[package]] @@ -11271,7 +11527,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.85", + "syn 2.0.87", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -11299,7 +11577,7 @@ dependencies = [ "flate2", "indexmap 2.6.0", "memchr", - "thiserror", + "thiserror 1.0.68", "zopfli", ] @@ -11311,7 +11589,7 @@ checksum = "ce824a6bfffe8942820fa36d24973b7c83a40896749a42e33de0abdd11750ee5" dependencies = [ "byteorder", "bytesize", - "thiserror", + "thiserror 1.0.68", ] [[package]] diff --git a/crates/tauri-build/CHANGELOG.md b/crates/tauri-build/CHANGELOG.md index 4070e0d9ea6f..43c42af79d65 100644 --- a/crates/tauri-build/CHANGELOG.md +++ b/crates/tauri-build/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## \[2.0.3] + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` +- Upgraded to `tauri-codegen@2.0.3` + ## \[2.0.2] ### Dependencies diff --git a/crates/tauri-build/Cargo.toml b/crates/tauri-build/Cargo.toml index d6557a3852f0..858e500f1787 100644 --- a/crates/tauri-build/Cargo.toml +++ b/crates/tauri-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-build" -version = "2.0.2" +version = "2.0.3" description = "build time code to pair with https://crates.io/crates/tauri" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -28,8 +28,8 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] anyhow = "1" quote = { version = "1", optional = true } -tauri-codegen = { version = "2.0.2", path = "../tauri-codegen", optional = true } -tauri-utils = { version = "2.0.2", path = "../tauri-utils", features = [ +tauri-codegen = { version = "2.0.3", path = "../tauri-codegen", optional = true } +tauri-utils = { version = "2.1.0", path = "../tauri-utils", features = [ "build", "resources", ] } diff --git a/crates/tauri-bundler/CHANGELOG.md b/crates/tauri-bundler/CHANGELOG.md index 7c914b189bac..1acb1a70b9db 100644 --- a/crates/tauri-bundler/CHANGELOG.md +++ b/crates/tauri-bundler/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## \[2.1.0] + +### New Features + +- [`1b6b2cfaa`](https://www.github.com/tauri-apps/tauri/commit/1b6b2cfaa14ab1d418c676cedbf942a812377a30) ([#11521](https://www.github.com/tauri-apps/tauri/pull/11521) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Process `bundle > windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it. +- [`6dea12a06`](https://www.github.com/tauri-apps/tauri/commit/6dea12a0677a905cb1f14969fe05c53e7cd717c6) ([#11402](https://www.github.com/tauri-apps/tauri/pull/11402) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > deb > recommends` and `bundle > linux > rpm > recommends` fields to declare a strong, but not absolute, dependency for your `.deb` and `.rpm` packages. +- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` + ## \[2.0.4] ### New Features diff --git a/crates/tauri-bundler/Cargo.toml b/crates/tauri-bundler/Cargo.toml index a485f0a9ccf7..0bac40537fcd 100644 --- a/crates/tauri-bundler/Cargo.toml +++ b/crates/tauri-bundler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-bundler" -version = "2.0.4" +version = "2.1.0" authors = [ "George Burton ", "Tauri Programme within The Commons Conservancy", @@ -15,13 +15,13 @@ rust-version = "1.77.2" exclude = ["CHANGELOG.md", "/target", "rustfmt.toml"] [dependencies] -tauri-utils = { version = "2.0.2", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.1.0", path = "../tauri-utils", features = [ "resources", ] } image = "0.25.0" flate2 = "1.0" anyhow = "1.0" -thiserror = "1.0" +thiserror = "2" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } strsim = "0.11.0" diff --git a/crates/tauri-bundler/src/bundle.rs b/crates/tauri-bundler/src/bundle.rs index 17100cb9cdfd..2f470c0c38d9 100644 --- a/crates/tauri-bundler/src/bundle.rs +++ b/crates/tauri-bundler/src/bundle.rs @@ -151,6 +151,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { | PackageType::MacOsBundle | PackageType::Nsis | PackageType::WindowsMsi + | PackageType::Deb ) } else { matches!(package_type, PackageType::MacOsBundle) @@ -166,7 +167,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { // Self contained updater, no need to zip matches!( package_type, - PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi + PackageType::AppImage | PackageType::Nsis | PackageType::WindowsMsi | PackageType::Deb ) }) { diff --git a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs index ce6d6170c160..f6942a47f97c 100644 --- a/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs +++ b/crates/tauri-bundler/src/bundle/linux/appimage/mod.rs @@ -106,10 +106,7 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { // initialize shell script template. let mut handlebars = Handlebars::new(); handlebars.register_escape_fn(handlebars::no_escape); - handlebars - .register_template_string("appimage", include_str!("./appimage")) - .expect("Failed to register template for handlebars"); - let temp = handlebars.render("appimage", &sh_map)?; + let temp = handlebars.render_template(include_str!("./appimage"), &sh_map)?; // create the shell script file in the target/ folder. let sh_file = output_path.join("build_appimage.sh"); diff --git a/crates/tauri-bundler/src/bundle/linux/rpm.rs b/crates/tauri-bundler/src/bundle/linux/rpm.rs index 06cbc953dfa1..f01f6a8718a4 100644 --- a/crates/tauri-bundler/src/bundle/linux/rpm.rs +++ b/crates/tauri-bundler/src/bundle/linux/rpm.rs @@ -12,6 +12,7 @@ use std::{ fs::{self, File}, path::{Path, PathBuf}, }; +use tauri_utils::config::RpmCompression; use super::freedesktop; @@ -54,11 +55,24 @@ pub fn bundle_project(settings: &Settings) -> crate::Result> { let license = settings.license().unwrap_or_default(); let name = heck::AsKebabCase(settings.product_name()).to_string(); + + let compression = settings + .rpm() + .compression + .map(|c| match c { + RpmCompression::Gzip { level } => rpm::CompressionWithLevel::Gzip(level), + RpmCompression::Zstd { level } => rpm::CompressionWithLevel::Zstd(level), + RpmCompression::Xz { level } => rpm::CompressionWithLevel::Xz(level), + RpmCompression::Bzip2 { level } => rpm::CompressionWithLevel::Bzip2(level), + _ => rpm::CompressionWithLevel::None, + }) + // This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%. + .unwrap_or(rpm::CompressionWithLevel::Gzip(6)); + let mut builder = rpm::PackageBuilder::new(&name, version, &license, arch, summary) .epoch(epoch) .release(release) - // This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%. - .compression(rpm::CompressionWithLevel::Gzip(6)); + .compression(compression); if let Some(description) = settings.long_description() { builder = builder.description(description); diff --git a/crates/tauri-bundler/src/bundle/settings.rs b/crates/tauri-bundler/src/bundle/settings.rs index 610c6e749f1e..a29398f76b98 100644 --- a/crates/tauri-bundler/src/bundle/settings.rs +++ b/crates/tauri-bundler/src/bundle/settings.rs @@ -8,7 +8,10 @@ use crate::bundle::{common, platform::target_triple}; use anyhow::Context; pub use tauri_utils::config::WebviewInstallMode; use tauri_utils::{ - config::{BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression}, + config::{ + BundleType, DeepLinkProtocol, FileAssociation, NSISInstallerMode, NsisCompression, + RpmCompression, + }, resources::{external_binaries, ResourcePaths}, }; @@ -262,6 +265,8 @@ pub struct RpmSettings { /// Path to script that will be executed after the package is removed. See /// pub post_remove_script: Option, + /// Compression algorithm and level. Defaults to `Gzip` with level 6. + pub compression: Option, } /// Position coordinates struct. diff --git a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs index f9daaf25718e..bc633a4be9f3 100644 --- a/crates/tauri-bundler/src/bundle/windows/msi/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/msi/mod.rs @@ -726,38 +726,26 @@ pub fn build_wix_app_installer( ); // Create the update task XML - let mut skip_uac_task = Handlebars::new(); + let skip_uac_task = Handlebars::new(); let xml = include_str!("./update-task.xml"); - skip_uac_task - .register_template_string("update.xml", xml) - .map_err(|e| e.to_string()) - .expect("Failed to setup Update Task handlebars"); + let update_content = skip_uac_task.render_template(xml, &data)?; let temp_xml_path = output_path.join("update.xml"); - let update_content = skip_uac_task.render("update.xml", &data)?; fs::write(temp_xml_path, update_content)?; // Create the Powershell script to install the task let mut skip_uac_task_installer = Handlebars::new(); skip_uac_task_installer.register_escape_fn(handlebars::no_escape); let xml = include_str!("./install-task.ps1"); - skip_uac_task_installer - .register_template_string("install-task.ps1", xml) - .map_err(|e| e.to_string()) - .expect("Failed to setup Update Task Installer handlebars"); + let install_script_content = skip_uac_task_installer.render_template(xml, &data)?; let temp_ps1_path = output_path.join("install-task.ps1"); - let install_script_content = skip_uac_task_installer.render("install-task.ps1", &data)?; fs::write(temp_ps1_path, install_script_content)?; // Create the Powershell script to uninstall the task let mut skip_uac_task_uninstaller = Handlebars::new(); skip_uac_task_uninstaller.register_escape_fn(handlebars::no_escape); let xml = include_str!("./uninstall-task.ps1"); - skip_uac_task_uninstaller - .register_template_string("uninstall-task.ps1", xml) - .map_err(|e| e.to_string()) - .expect("Failed to setup Update Task Uninstaller handlebars"); + let install_script_content = skip_uac_task_uninstaller.render_template(xml, &data)?; let temp_ps1_path = output_path.join("uninstall-task.ps1"); - let install_script_content = skip_uac_task_uninstaller.render("uninstall-task.ps1", &data)?; fs::write(temp_ps1_path, install_script_content)?; data.insert("enable_elevated_update_task", to_json(true)); @@ -772,7 +760,9 @@ pub fn build_wix_app_installer( let extension_regex = Regex::new("\"http://schemas.microsoft.com/wix/(\\w+)\"")?; for fragment_path in fragment_paths { let fragment_path = current_dir.join(fragment_path); - let fragment = fs::read_to_string(&fragment_path)?; + let fragment_content = fs::read_to_string(&fragment_path)?; + let fragment_handlebars = Handlebars::new(); + let fragment = fragment_handlebars.render_template(&fragment_content, &data)?; let mut extensions = Vec::new(); for cap in extension_regex.captures_iter(&fragment) { extensions.push(wix_toolset_path.join(format!("Wix{}.dll", &cap[1]))); diff --git a/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs b/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs index 3d281489c369..810141214c5c 100644 --- a/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs +++ b/crates/tauri-bundler/src/bundle/windows/nsis/mod.rs @@ -90,8 +90,8 @@ pub fn bundle_project(settings: &Settings, updater: bool) -> crate::Result windows > wix > fragmentPaths` with Handlebars to interpolate expressions within it. +- [`6bf917941`](https://www.github.com/tauri-apps/tauri/commit/6bf917941ff0fcc49e86b3ba427340b75f3ce49c) ([#11322](https://www.github.com/tauri-apps/tauri/pull/11322) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Add `tauri remove` to remove plugins from projects. +- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level. + +### Enhancements + +- [`1f311832a`](https://www.github.com/tauri-apps/tauri/commit/1f311832ab5b2d62a533dfcf9b1d78bddf249ae8) ([#11405](https://www.github.com/tauri-apps/tauri/pull/11405) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add more context for errors when decoding secret and public keys for signing updater artifacts. +- [`e0d1307d3`](https://www.github.com/tauri-apps/tauri/commit/e0d1307d3f78987d0059921a5ab01ea4b26e0ef1) ([#11414](https://www.github.com/tauri-apps/tauri/pull/11414) by [@Czxck001](https://www.github.com/tauri-apps/tauri/../../Czxck001)) Migrate the `$schema` Tauri configuration to the v2 format. +- [`c43d5df15`](https://www.github.com/tauri-apps/tauri/commit/c43d5df15828ecffa606482ea2b60350c488c981) ([#11512](https://www.github.com/tauri-apps/tauri/pull/11512) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands. + +### Bug Fixes + +- [`7af01ff2c`](https://www.github.com/tauri-apps/tauri/commit/7af01ff2ce623d727cd13a4c8a549c1c80031882) ([#11523](https://www.github.com/tauri-apps/tauri/pull/11523) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` failing to install NPM depenencies when running from Deno. +- [`100a4455a`](https://www.github.com/tauri-apps/tauri/commit/100a4455aa48df508510bbc08273215bdf70c012) ([#11529](https://www.github.com/tauri-apps/tauri/pull/11529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix detecting yarn berry (v2 and higher) in various tauri cli commands. +- [`60e86d5f6`](https://www.github.com/tauri-apps/tauri/commit/60e86d5f6e0f0c769d34ef368cd8801a918d796d) ([#11624](https://www.github.com/tauri-apps/tauri/pull/11624) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the public network IP address on `android dev` by default on Windows. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` +- Upgraded to `tauri-bundler@2.1.0` + ## \[2.0.4] ### Enhancements diff --git a/crates/tauri-cli/Cargo.toml b/crates/tauri-cli/Cargo.toml index 464ec7dcdb20..e0756d299beb 100644 --- a/crates/tauri-cli/Cargo.toml +++ b/crates/tauri-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-cli" -version = "2.0.4" +version = "2.1.0" authors = ["Tauri Programme within The Commons Conservancy"] edition = "2021" rust-version = "1.77.2" @@ -47,7 +47,7 @@ sublime_fuzzy = "0.7" clap_complete = "4" clap = { version = "4.5", features = ["derive", "env"] } anyhow = "1.0" -tauri-bundler = { version = "2.0.4", default-features = false, path = "../tauri-bundler" } +tauri-bundler = { version = "2.1.0", default-features = false, path = "../tauri-bundler" } colored = "2.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"] } @@ -57,7 +57,7 @@ shared_child = "1.0" duct = "0.13" toml_edit = { version = "0.22", features = ["serde"] } json-patch = "3.0" -tauri-utils = { version = "2.0.2", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.1.0", path = "../tauri-utils", features = [ "isolation", "schema", "config-json5", diff --git a/crates/tauri-cli/config.schema.json b/crates/tauri-cli/config.schema.json index a05ba79935ed..c701eb78c6b0 100644 --- a/crates/tauri-cli/config.schema.json +++ b/crates/tauri-cli/config.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.tauri.app/config/2.0.6", + "$id": "https://schema.tauri.app/config/2.1.1", "title": "Config", "description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"../dist\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```", "type": "object", @@ -397,6 +397,13 @@ "default": false, "type": "boolean" }, + "windowClassname": { + "description": "The name of the window class created on Windows to create the window. **Windows only**.", + "type": [ + "string", + "null" + ] + }, "theme": { "description": "The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.", "anyOf": [ @@ -486,6 +493,29 @@ "description": "Whether browser extensions can be installed for the webview process\n\n ## Platform-specific:\n\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n - **MacOS / Linux / iOS / Android** - Unsupported.", "default": false, "type": "boolean" + }, + "useHttpsScheme": { + "description": "Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`.\n\n ## Note\n\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux.\n\n ## Warning\n\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.", + "default": false, + "type": "boolean" + }, + "devtools": { + "description": "Enable web inspector which is usually called browser devtools. Enabled by default.\n\n This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n\n ## Platform-specific\n\n - macOS: This will call private functions on **macOS**.\n - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.", + "type": [ + "boolean", + "null" + ] + }, + "backgroundColor": { + "description": "Set the window and webview background color.\n\n ## Platform-specific:\n\n - **Windows**: alpha channel is ignored for the window layer.\n - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\n - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.", + "anyOf": [ + { + "$ref": "#/definitions/Color" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -827,32 +857,96 @@ ] }, "Color": { - "description": "a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.", - "type": "array", - "items": [ + "anyOf": [ { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Color hex string, for example: #fff, #ffffff, or #ffffffff.", + "type": "string", + "pattern": "^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$" }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Array of RGB colors. Each value has minimum of 0 and maximum of 255.", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Array of RGBA colors. Each value has minimum of 0 and maximum of 255.", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + ], + "maxItems": 4, + "minItems": 4 }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.", + "type": "object", + "required": [ + "blue", + "green", + "red" + ], + "properties": { + "red": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "green": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "blue": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "alpha": { + "default": 255, + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } } - ], - "maxItems": 4, - "minItems": 4 + ] }, "SecurityConfig": { "description": "Security configuration.\n\n See more: ", @@ -924,6 +1018,17 @@ "items": { "$ref": "#/definitions/CapabilityEntry" } + }, + "headers": { + "description": "The headers, which are added to every http response from tauri to the web view\n This doesn't include IPC Messages and error responses", + "anyOf": [ + { + "$ref": "#/definitions/HeaderConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1104,7 +1209,7 @@ ] }, "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", "type": "object", "required": [ "identifier", @@ -1151,7 +1256,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ```", + "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ]\n ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" @@ -1333,6 +1438,168 @@ } ] }, + "HeaderConfig": { + "description": "A struct, where the keys are some specific http header names.\n If the values to those keys are defined, then they will be send as part of a response message.\n This does not include error messages and ipc messages\n\n ## Example configuration\n ```javascript\n {\n //..\n app:{\n //..\n security: {\n headers: {\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Timing-Allow-Origin\": [\n \"https://developer.mozilla.org\",\n \"https://example.com\",\n ],\n \"Access-Control-Expose-Headers\": \"Tauri-Custom-Header\",\n \"Tauri-Custom-Header\": {\n \"key1\": \"'value1' 'value2'\",\n \"key2\": \"'value3'\"\n }\n },\n csp: \"default-src 'self'; connect-src ipc: http://ipc.localhost\",\n }\n //..\n }\n //..\n }\n ```\n In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\n The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\n The Content-Security-Policy header is defined separately, because it is also handled separately.\n\n For the helloworld example, this config translates into those response headers:\n ```http\n access-control-allow-origin: http://tauri.localhost\n access-control-expose-headers: Tauri-Custom-Header\n content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\n content-type: text/html\n cross-origin-embedder-policy: require-corp\n cross-origin-opener-policy: same-origin\n tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\n timing-allow-origin: https://developer.mozilla.org, https://example.com\n ```\n Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\n - `String`(JS/Rust): stay the same for the resulting header value\n - `Array`(JS)/`Vec\\`(Rust): Item are joined by \", \" for the resulting header value\n - `Object`(JS)/ `Hashmap\\`(Rust): Items are composed from: key + space + value. Item are then joined by \"; \" for the resulting header value", + "type": "object", + "properties": { + "Access-Control-Allow-Credentials": { + "description": "The Access-Control-Allow-Credentials response header tells browsers whether the\n server allows cross-origin HTTP requests to include credentials.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Allow-Headers": { + "description": "The Access-Control-Allow-Headers response header is used in response\n to a preflight request which includes the Access-Control-Request-Headers\n to indicate which HTTP headers can be used during the actual request.\n\n This header is required if the request has an Access-Control-Request-Headers header.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Allow-Methods": { + "description": "The Access-Control-Allow-Methods response header specifies one or more methods\n allowed when accessing a resource in response to a preflight request.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Expose-Headers": { + "description": "The Access-Control-Expose-Headers response header allows a server to indicate\n which response headers should be made available to scripts running in the browser,\n in response to a cross-origin request.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Max-Age": { + "description": "The Access-Control-Max-Age response header indicates how long the results of a\n preflight request (that is the information contained in the\n Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\n be cached.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Embedder-Policy": { + "description": "The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\n cross-origin resources into the document.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Opener-Policy": { + "description": "The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\n top-level document does not share a browsing context group with cross-origin documents.\n COOP will process-isolate your document and potential attackers can't access your global\n object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Resource-Policy": { + "description": "The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\n browser blocks no-cors cross-origin/cross-site requests to the given resource.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Permissions-Policy": { + "description": "The HTTP Permissions-Policy header provides a mechanism to allow and deny the\n use of browser features in a document or within any \\ elements in the document.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Timing-Allow-Origin": { + "description": "The Timing-Allow-Origin response header specifies origins that are allowed to see values\n of attributes retrieved via features of the Resource Timing API, which would otherwise be\n reported as zero due to cross-origin restrictions.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "X-Content-Type-Options": { + "description": "The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\n that the MIME types advertised in the Content-Type headers should be followed and not be\n changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\n are deliberately configured.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Tauri-Custom-Header": { + "description": "A custom header field Tauri-Custom-Header, don't use it.\n Remember to set Access-Control-Expose-Headers accordingly\n\n **NOT INTENDED FOR PRODUCTION USE**", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "HeaderSource": { + "description": "definition of a header source\n\n The header value to a header name", + "anyOf": [ + { + "description": "string version of the header Value", + "type": "string" + }, + { + "description": "list version of the header value. Item are joined by \",\" for the real header value", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "(Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \";\" for the real header value", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, "TrayIconConfig": { "description": "Configuration for application tray icon.\n\n See more: ", "type": "object", @@ -2789,10 +3056,133 @@ "string", "null" ] + }, + "compression": { + "description": "Compression algorithm and level. Defaults to `Gzip` with level 6.", + "anyOf": [ + { + "$ref": "#/definitions/RpmCompression" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false }, + "RpmCompression": { + "description": "Compression algorithms used when bundling RPM packages.", + "oneOf": [ + { + "description": "Gzip compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "gzip" + ] + }, + "level": { + "description": "Gzip compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Zstd compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "zstd" + ] + }, + "level": { + "description": "Zstd compression level", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + { + "description": "Xz compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "xz" + ] + }, + "level": { + "description": "Xz compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Bzip2 compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "bzip2" + ] + }, + "level": { + "description": "Bzip2 compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Disable compression", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ] + } + }, + "additionalProperties": false + } + ] + }, "MacConfig": { "description": "Configuration for the macOS bundles.\n\n See more: ", "type": "object", diff --git a/crates/tauri-cli/metadata-v2.json b/crates/tauri-cli/metadata-v2.json index 9aad788db254..97d130b6b6dd 100644 --- a/crates/tauri-cli/metadata-v2.json +++ b/crates/tauri-cli/metadata-v2.json @@ -1,9 +1,9 @@ { "cli.js": { - "version": "2.0.4", + "version": "2.1.0", "node": ">= 10.0.0" }, - "tauri": "2.0.6", - "tauri-build": "2.0.1", - "tauri-plugin": "2.0.1" + "tauri": "2.1.1", + "tauri-build": "2.0.2", + "tauri-plugin": "2.0.2" } diff --git a/crates/tauri-cli/schema.json b/crates/tauri-cli/schema.json index 9d28874da38b..613287fc78f8 100644 --- a/crates/tauri-cli/schema.json +++ b/crates/tauri-cli/schema.json @@ -2995,4 +2995,4 @@ "additionalProperties": true } } -} \ No newline at end of file +} diff --git a/crates/tauri-cli/src/acl/permission/mod.rs b/crates/tauri-cli/src/acl/permission/mod.rs index e65295919ce4..deb82f00bec1 100644 --- a/crates/tauri-cli/src/acl/permission/mod.rs +++ b/crates/tauri-cli/src/acl/permission/mod.rs @@ -9,7 +9,7 @@ use crate::Result; pub mod add; mod ls; mod new; -mod rm; +pub mod rm; #[derive(Debug, Parser)] #[clap(about = "Manage or create permissions for your app or plugin")] diff --git a/crates/tauri-cli/src/acl/permission/rm.rs b/crates/tauri-cli/src/acl/permission/rm.rs index 4c2e11764a92..f565b6e56b6a 100644 --- a/crates/tauri-cli/src/acl/permission/rm.rs +++ b/crates/tauri-cli/src/acl/permission/rm.rs @@ -46,13 +46,15 @@ fn rm_permission_files(identifier: &str, dir: &Path) -> Result<()> { permission_file.default = None; } else { let set_len = permission_file.set.len(); - permission_file.set.retain(|s| s.identifier != identifier); + permission_file + .set + .retain(|s| !identifier_match(identifier, &s.identifier)); updated = permission_file.set.len() != set_len; let permission_len = permission_file.permission.len(); permission_file .permission - .retain(|s| s.identifier != identifier); + .retain(|s| !identifier_match(identifier, &s.identifier)); updated = updated || permission_file.permission.len() != permission_len; } @@ -84,7 +86,11 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> { if let Ok(mut value) = content.parse::() { if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) { let prev_len = permissions.len(); - permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false)); + permissions.retain(|p| { + p.as_str() + .map(|p| !identifier_match(identifier, p)) + .unwrap_or(false) + }); if prev_len != permissions.len() { std::fs::write(&path, value.to_string())?; log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display()); @@ -97,7 +103,11 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> { if let Ok(mut value) = serde_json::from_slice::(&content) { if let Some(permissions) = value.get_mut("permissions").and_then(|p| p.as_array_mut()) { let prev_len = permissions.len(); - permissions.retain(|p| p.as_str().map(|p| p != identifier).unwrap_or(false)); + permissions.retain(|p| { + p.as_str() + .map(|p| !identifier_match(identifier, p)) + .unwrap_or(false) + }); if prev_len != permissions.len() { std::fs::write(&path, serde_json::to_vec_pretty(&value)?)?; log::info!(action = "Removed"; "permission from capability at {}", dunce::simplified(&path).display()); @@ -113,11 +123,20 @@ fn rm_permission_from_capabilities(identifier: &str, dir: &Path) -> Result<()> { Ok(()) } +fn identifier_match(identifier: &str, permission: &str) -> bool { + match identifier.split_once(':') { + Some((plugin_name, "*")) => permission.contains(plugin_name), + _ => permission == identifier, + } +} + #[derive(Debug, Parser)] #[clap(about = "Remove a permission file, and its reference from any capability")] pub struct Options { /// Permission to remove. - identifier: String, + /// + /// To remove all permissions for a given plugin, provide `:*` + pub identifier: String, } pub fn command(options: Options) -> Result<()> { diff --git a/crates/tauri-cli/src/add.rs b/crates/tauri-cli/src/add.rs index ceb73533326e..56ad58ef0495 100644 --- a/crates/tauri-cli/src/add.rs +++ b/crates/tauri-cli/src/add.rs @@ -81,10 +81,7 @@ pub fn run(options: Options) -> Result<()> { })?; if !metadata.rust_only { - if let Some(manager) = frontend_dir - .map(PackageManager::from_project) - .and_then(|managers| managers.into_iter().next()) - { + if let Some(manager) = frontend_dir.map(PackageManager::from_project) { let npm_version_req = version .map(ToString::to_string) .or(metadata.version_req.as_ref().map(|v| match manager { @@ -93,10 +90,7 @@ pub fn run(options: Options) -> Result<()> { })); let npm_spec = match (npm_version_req, options.tag, options.rev, options.branch) { - (Some(version_req), _, _, _) => match manager { - PackageManager::Deno => format!("npm:{npm_name}@{version_req}"), - _ => format!("{npm_name}@{version_req}"), - }, + (Some(version_req), _, _, _) => format!("{npm_name}@{version_req}"), (None, Some(tag), None, None) => { format!("tauri-apps/tauri-plugin-{plugin}#{tag}") } diff --git a/crates/tauri-cli/src/bundle.rs b/crates/tauri-cli/src/bundle.rs index eded4aa2b477..5473c4b4690c 100644 --- a/crates/tauri-cli/src/bundle.rs +++ b/crates/tauri-cli/src/bundle.rs @@ -226,7 +226,11 @@ fn sign_updaters( .filter(|bundle| { matches!( bundle.package_type, - PackageType::Updater | PackageType::Nsis | PackageType::WindowsMsi | PackageType::AppImage + PackageType::Updater + | PackageType::Nsis + | PackageType::WindowsMsi + | PackageType::AppImage + | PackageType::Deb ) }) .collect(); diff --git a/crates/tauri-cli/src/helpers/cargo.rs b/crates/tauri-cli/src/helpers/cargo.rs index 24628bb9df9a..ffa4493b7959 100644 --- a/crates/tauri-cli/src/helpers/cargo.rs +++ b/crates/tauri-cli/src/helpers/cargo.rs @@ -61,3 +61,33 @@ pub fn install_one(options: CargoInstallOptions) -> crate::Result<()> { Ok(()) } + +#[derive(Debug, Default, Clone, Copy)] +pub struct CargoUninstallOptions<'a> { + pub name: &'a str, + pub cwd: Option<&'a std::path::Path>, + pub target: Option<&'a str>, +} + +pub fn uninstall_one(options: CargoUninstallOptions) -> crate::Result<()> { + let mut cargo = Command::new("cargo"); + cargo.arg("remove"); + + cargo.arg(options.name); + + if let Some(target) = options.target { + cargo.args(["--target", target]); + } + + if let Some(cwd) = options.cwd { + cargo.current_dir(cwd); + } + + log::info!("Uninstalling Cargo dependency \"{}\"...", options.name); + let status = cargo.status().context("failed to run `cargo remove`")?; + if !status.success() { + anyhow::bail!("Failed to remove Cargo dependency"); + } + + Ok(()) +} diff --git a/crates/tauri-cli/src/helpers/npm.rs b/crates/tauri-cli/src/helpers/npm.rs index 8e6134bb1bf9..1189aa323a52 100644 --- a/crates/tauri-cli/src/helpers/npm.rs +++ b/crates/tauri-cli/src/helpers/npm.rs @@ -7,6 +7,22 @@ use anyhow::Context; use crate::helpers::cross_command; use std::{fmt::Display, path::Path, process::Command}; +pub fn manager_version(package_manager: &str) -> Option { + cross_command(package_manager) + .arg("-v") + .output() + .map(|o| { + if o.status.success() { + let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); + Some(v.split('\n').next().unwrap().to_string()) + } else { + None + } + }) + .ok() + .unwrap_or_default() +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum PackageManager { Npm, @@ -35,7 +51,16 @@ impl Display for PackageManager { } impl PackageManager { - pub fn from_project>(path: P) -> Vec { + /// Detects package manager from the given directory, falls back to [`PackageManager::Npm`]. + pub fn from_project>(path: P) -> Self { + Self::all_from_project(path) + .first() + .copied() + .unwrap_or(Self::Npm) + } + + /// Detects all possible package managers from the given directory. + pub fn all_from_project>(path: P) -> Vec { let mut found = Vec::new(); if let Ok(entries) = std::fs::read_dir(path) { @@ -47,7 +72,15 @@ impl PackageManager { } else if name.as_ref() == "pnpm-lock.yaml" { found.push(PackageManager::Pnpm); } else if name.as_ref() == "yarn.lock" { - found.push(PackageManager::Yarn); + let yarn = if manager_version("yarn") + .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default()) + .unwrap_or(false) + { + PackageManager::YarnBerry + } else { + PackageManager::Yarn + }; + found.push(yarn); } else if name.as_ref() == "bun.lockb" { found.push(PackageManager::Bun); } else if name.as_ref() == "deno.lock" { @@ -89,10 +122,15 @@ impl PackageManager { .join(", ") ); - let status = self - .cross_command() - .arg("add") - .args(dependencies) + let mut command = self.cross_command(); + command.arg("add"); + + match self { + PackageManager::Deno => command.args(dependencies.iter().map(|d| format!("npm:{d}"))), + _ => command.args(dependencies), + }; + + let status = command .current_dir(frontend_dir) .status() .with_context(|| format!("failed to run {self}"))?; diff --git a/crates/tauri-cli/src/info/env_nodejs.rs b/crates/tauri-cli/src/info/env_nodejs.rs index 5d210429ca41..eb9938eee72d 100644 --- a/crates/tauri-cli/src/info/env_nodejs.rs +++ b/crates/tauri-cli/src/info/env_nodejs.rs @@ -5,23 +5,7 @@ use super::{ActionResult, SectionItem, VersionMetadata}; use colored::Colorize; -use crate::helpers::cross_command; - -pub fn manager_version(package_manager: &str) -> Option { - cross_command(package_manager) - .arg("-v") - .output() - .map(|o| { - if o.status.success() { - let v = String::from_utf8_lossy(o.stdout.as_slice()).to_string(); - Some(v.split('\n').next().unwrap().to_string()) - } else { - None - } - }) - .ok() - .unwrap_or_default() -} +use crate::helpers::{cross_command, npm::manager_version}; pub fn items(metadata: &VersionMetadata) -> Vec { let node_target_ver = metadata.js_cli.node.replace(">= ", ""); diff --git a/crates/tauri-cli/src/info/env_system.rs b/crates/tauri-cli/src/info/env_system.rs index a0570c28afc6..9deb72844e37 100644 --- a/crates/tauri-cli/src/info/env_system.rs +++ b/crates/tauri-cli/src/info/env_system.rs @@ -175,17 +175,45 @@ fn is_xcode_command_line_tools_installed() -> bool { .map(|o| o.status.success()) .unwrap_or(false) } +fn de_and_session() -> String { + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd" + ))] + return { + let de = std::env::var("DESKTOP_SESSION"); + let session = std::env::var("XDG_SESSION_TYPE"); + format!( + " ({} on {})", + de.as_deref().unwrap_or("Unknown DE"), + session.as_deref().unwrap_or("Unknown Session") + ) + }; + + #[cfg(not(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd" + )))] + String::new() +} pub fn items() -> Vec { vec![ SectionItem::new().action(|| { let os_info = os_info::get(); format!( - "OS: {} {} {} ({:?})", + "OS: {} {} {} ({:?}){}", os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("Unknown Architecture"), - os_info.bitness() + os_info.bitness(), + de_and_session(), ).into() }), #[cfg(windows)] diff --git a/crates/tauri-cli/src/info/packages_nodejs.rs b/crates/tauri-cli/src/info/packages_nodejs.rs index f484ffd20e7a..73b87d8b7803 100644 --- a/crates/tauri-cli/src/info/packages_nodejs.rs +++ b/crates/tauri-cli/src/info/packages_nodejs.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use super::SectionItem; -use super::{env_nodejs::manager_version, VersionMetadata}; +use super::VersionMetadata; use colored::Colorize; use serde::Deserialize; use std::path::PathBuf; @@ -77,7 +77,7 @@ pub fn npm_latest_version(pm: &PackageManager, name: &str) -> crate::Result PackageManager { - let found = PackageManager::from_project(frontend_dir); + let found = PackageManager::all_from_project(frontend_dir); if found.is_empty() { println!( @@ -98,15 +98,7 @@ pub fn package_manager(frontend_dir: &PathBuf) -> PackageManager { ); } - if pkg_manager == PackageManager::Yarn - && manager_version("yarn") - .map(|v| v.chars().next().map(|c| c > '1').unwrap_or_default()) - .unwrap_or(false) - { - PackageManager::YarnBerry - } else { - pkg_manager - } + pkg_manager } pub fn items( diff --git a/crates/tauri-cli/src/init.rs b/crates/tauri-cli/src/init.rs index cbd4ae1a0ef5..f12bc4abf00e 100644 --- a/crates/tauri-cli/src/init.rs +++ b/crates/tauri-cli/src/init.rs @@ -131,10 +131,7 @@ impl Options { ) })?; - let detected_package_manager = match PackageManager::from_project(&self.directory).first() { - Some(&package_manager) => package_manager, - None => PackageManager::Npm, - }; + let detected_package_manager = PackageManager::from_project(&self.directory); self.before_dev_command = self .before_dev_command diff --git a/crates/tauri-cli/src/interface/rust.rs b/crates/tauri-cli/src/interface/rust.rs index 08008171427b..8a407f8eebe7 100644 --- a/crates/tauri-cli/src/interface/rust.rs +++ b/crates/tauri-cli/src/interface/rust.rs @@ -1388,6 +1388,7 @@ fn tauri_config_to_bundle_settings( post_install_script: config.linux.rpm.post_install_script, pre_remove_script: config.linux.rpm.pre_remove_script, post_remove_script: config.linux.rpm.post_remove_script, + compression: config.linux.rpm.compression, }, dmg: DmgSettings { background: config.macos.dmg.background, diff --git a/crates/tauri-cli/src/lib.rs b/crates/tauri-cli/src/lib.rs index 2ab75cf95127..4a9e11d3b44f 100644 --- a/crates/tauri-cli/src/lib.rs +++ b/crates/tauri-cli/src/lib.rs @@ -30,6 +30,7 @@ mod interface; mod migrate; mod mobile; mod plugin; +mod remove; mod signer; use clap::{ArgAction, CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum}; @@ -146,6 +147,7 @@ enum Commands { Migrate, Info(info::Options), Add(add::Options), + Remove(remove::Options), Plugin(plugin::Cli), Icon(icon::Options), Signer(signer::Cli), @@ -265,6 +267,7 @@ where Commands::Bundle(options) => bundle::command(options, cli.verbose)?, Commands::Dev(options) => dev::command(options)?, Commands::Add(options) => add::command(options)?, + Commands::Remove(options) => remove::command(options)?, Commands::Icon(options) => icon::command(options)?, Commands::Info(options) => info::command(options)?, Commands::Init(options) => init::command(options)?, diff --git a/crates/tauri-cli/src/migrate/migrations/v1/config.rs b/crates/tauri-cli/src/migrate/migrations/v1/config.rs index ab956e76aa19..2b211470a096 100644 --- a/crates/tauri-cli/src/migrate/migrations/v1/config.rs +++ b/crates/tauri-cli/src/migrate/migrations/v1/config.rs @@ -87,6 +87,28 @@ fn migrate_config(config: &mut Value) -> Result { migrated.permissions = permissions; } + // dangerousUseHttpScheme/useHttpsScheme + let dangerouse_use_http = tauri_config + .get("security") + .and_then(|w| w.as_object()) + .and_then(|w| { + w.get("dangerousUseHttpScheme") + .or_else(|| w.get("dangerous-use-http-scheme")) + }) + .and_then(|v| v.as_bool()) + .unwrap_or_default(); + + if let Some(windows) = tauri_config + .get_mut("windows") + .and_then(|w| w.as_array_mut()) + { + for window in windows { + if let Some(window) = window.as_object_mut() { + window.insert("useHttpsScheme".to_string(), (!dangerouse_use_http).into()); + } + } + } + // security if let Some(security) = tauri_config .get_mut("security") @@ -802,7 +824,8 @@ mod test { "pattern": { "use": "brownfield" }, "security": { "csp": "default-src 'self' tauri:" - } + }, + "windows": [{}] } }); @@ -907,6 +930,8 @@ mod test { migrated["app"]["withGlobalTauri"], original["build"]["withGlobalTauri"] ); + + assert_eq!(migrated["app"]["windows"][0]["useHttpsScheme"], true); } #[test] @@ -941,6 +966,28 @@ mod test { ); } + #[test] + fn migrate_dangerous_use_http_scheme() { + let original = serde_json::json!({ + "tauri": { + "windows": [{}], + "security": { + "dangerousUseHttpScheme": true, + } + } + }); + + let migrated = migrate(&original); + assert_eq!( + !migrated["app"]["windows"][0]["useHttpsScheme"] + .as_bool() + .unwrap(), + original["tauri"]["security"]["dangerousUseHttpScheme"] + .as_bool() + .unwrap() + ); + } + #[test] fn can_migrate_default_config() { let original = serde_json::to_value(tauri_utils_v1::config::Config::default()).unwrap(); diff --git a/crates/tauri-cli/src/migrate/migrations/v1/frontend.rs b/crates/tauri-cli/src/migrate/migrations/v1/frontend.rs index 83f906a905cc..8ad2771f3554 100644 --- a/crates/tauri-cli/src/migrate/migrations/v1/frontend.rs +++ b/crates/tauri-cli/src/migrate/migrations/v1/frontend.rs @@ -84,10 +84,7 @@ pub fn migrate(frontend_dir: &Path) -> Result> { ) }; - let pm = PackageManager::from_project(frontend_dir) - .into_iter() - .next() - .unwrap_or(PackageManager::Npm); + let pm = PackageManager::from_project(frontend_dir); for pkg in ["@tauri-apps/cli", "@tauri-apps/api"] { let version = pm diff --git a/crates/tauri-cli/src/migrate/migrations/v2_rc.rs b/crates/tauri-cli/src/migrate/migrations/v2_rc.rs index 50ce8a37b10e..844c7de048be 100644 --- a/crates/tauri-cli/src/migrate/migrations/v2_rc.rs +++ b/crates/tauri-cli/src/migrate/migrations/v2_rc.rs @@ -35,10 +35,7 @@ pub fn run() -> Result<()> { } fn migrate_npm_dependencies(frontend_dir: &Path) -> Result<()> { - let pm = PackageManager::from_project(frontend_dir) - .into_iter() - .next() - .unwrap_or(PackageManager::Npm); + let pm = PackageManager::from_project(frontend_dir); let mut install_deps = Vec::new(); for pkg in [ diff --git a/crates/tauri-cli/src/mobile/android/dev.rs b/crates/tauri-cli/src/mobile/android/dev.rs index 1a56d737a431..6899b21a174e 100644 --- a/crates/tauri-cli/src/mobile/android/dev.rs +++ b/crates/tauri-cli/src/mobile/android/dev.rs @@ -15,7 +15,8 @@ use crate::{ }, interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions}, mobile::{ - use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevProcess, TargetDevice, + use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess, + TargetDevice, }, ConfigValue, Result, }; @@ -33,7 +34,7 @@ use cargo_mobile2::{ target::TargetTrait, }; -use std::{env::set_current_dir, net::IpAddr}; +use std::env::set_current_dir; #[derive(Debug, Clone, Parser)] #[clap( @@ -70,6 +71,8 @@ pub struct Options { /// Use the public network address for the development server. /// If an actual address it provided, it is used instead of prompting to pick one. /// + /// On Windows we use the public network address by default. + /// /// This option is particularly useful along the `--open` flag when you intend on running on a physical device. /// /// This replaces the devUrl configuration value to match the public network address host, @@ -79,8 +82,8 @@ pub struct Options { /// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST` /// environment variable so you can check this on your framework's configuration to expose the development server /// on the public network address. - #[clap(long)] - pub host: Option>, + #[clap(long, default_value_t, default_missing_value(""), num_args(0..=1))] + pub host: DevHost, /// Disable the built-in dev server for static files. #[clap(long)] pub no_dev_server: bool, @@ -103,7 +106,7 @@ impl From for DevOptions { no_dev_server: options.no_dev_server, port: options.port, release_mode: options.release_mode, - host: None, + host: options.host.0.unwrap_or_default(), } } } @@ -197,7 +200,7 @@ fn run_dev( noise_level: NoiseLevel, ) -> Result<()> { // when running on an actual device we must use the network IP - if options.host.is_some() + if options.host.0.is_some() || device .as_ref() .map(|device| !device.serial_no().starts_with("emulator")) diff --git a/crates/tauri-cli/src/mobile/ios/dev.rs b/crates/tauri-cli/src/mobile/ios/dev.rs index c1d57376ca74..892afa76e26e 100644 --- a/crates/tauri-cli/src/mobile/ios/dev.rs +++ b/crates/tauri-cli/src/mobile/ios/dev.rs @@ -14,7 +14,9 @@ use crate::{ flock, }, interface::{AppInterface, Interface, MobileOptions, Options as InterfaceOptions}, - mobile::{use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevProcess}, + mobile::{ + use_network_address_for_dev_url, write_options, CliOptions, DevChild, DevHost, DevProcess, + }, ConfigValue, Result, }; use clap::{ArgAction, Parser}; @@ -29,7 +31,7 @@ use cargo_mobile2::{ opts::{NoiseLevel, Profile}, }; -use std::{env::set_current_dir, net::IpAddr}; +use std::env::set_current_dir; const PHYSICAL_IPHONE_DEV_WARNING: &str = "To develop on physical phones you need the `--host` option (not required for Simulators). See the documentation for more information: https://v2.tauri.app/develop/#development-server"; @@ -84,8 +86,8 @@ pub struct Options { /// When this is set or when running on an iOS device the CLI sets the `TAURI_DEV_HOST` /// environment variable so you can check this on your framework's configuration to expose the development server /// on the public network address. - #[clap(long)] - pub host: Option>, + #[clap(long, default_value_t, default_missing_value(""), num_args(0..=1))] + pub host: DevHost, /// Disable the built-in dev server for static files. #[clap(long)] pub no_dev_server: bool, @@ -108,7 +110,7 @@ impl From for DevOptions { no_dev_server: options.no_dev_server, no_dev_server_wait: options.no_dev_server_wait, port: options.port, - host: None, + host: options.host.0.unwrap_or_default(), } } } @@ -230,7 +232,7 @@ fn run_dev( noise_level: NoiseLevel, ) -> Result<()> { // when running on an actual device we must use the network IP - if options.host.is_some() + if options.host.0.is_some() || device .as_ref() .map(|device| !matches!(device.kind(), DeviceKind::Simulator)) @@ -249,7 +251,7 @@ fn run_dev( })?; let _lock = flock::open_rw(out_dir.join("lock").with_extension("ios"), "iOS")?; - let set_host = options.host.is_some(); + let set_host = options.host.0.is_some(); let open = options.open; interface.mobile_dev( diff --git a/crates/tauri-cli/src/mobile/mod.rs b/crates/tauri-cli/src/mobile/mod.rs index a9c443a5c9ca..d9e5a10dbc19 100644 --- a/crates/tauri-cli/src/mobile/mod.rs +++ b/crates/tauri-cli/src/mobile/mod.rs @@ -30,11 +30,12 @@ use std::{ collections::HashMap, env::{set_var, temp_dir}, ffi::OsString, - fmt::Write, + fmt::{Display, Write}, fs::{read_to_string, write}, - net::{IpAddr, Ipv4Addr, SocketAddr}, + net::{AddrParseError, IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, process::{exit, ExitStatus}, + str::FromStr, sync::{ atomic::{AtomicBool, Ordering}, Arc, OnceLock, @@ -141,6 +142,44 @@ pub struct TargetDevice { name: String, } +#[derive(Debug, Clone)] +pub struct DevHost(Option>); + +impl FromStr for DevHost { + type Err = AddrParseError; + fn from_str(s: &str) -> std::result::Result { + if s.is_empty() || s == "" { + Ok(Self(Some(None))) + } else if s == "" { + Ok(Self(None)) + } else { + IpAddr::from_str(s).map(|addr| Self(Some(Some(addr)))) + } + } +} + +impl Display for DevHost { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.0 { + Some(None) => write!(f, ""), + Some(Some(addr)) => write!(f, "{addr}"), + None => write!(f, ""), + } + } +} + +impl Default for DevHost { + fn default() -> Self { + // on Windows we want to force using the public network address for the development server + // because the adb port forwarding does not work well + if cfg!(windows) { + Self(Some(None)) + } else { + Self(None) + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CliOptions { pub dev: bool, diff --git a/crates/tauri-cli/src/remove.rs b/crates/tauri-cli/src/remove.rs new file mode 100644 index 000000000000..319f60e82860 --- /dev/null +++ b/crates/tauri-cli/src/remove.rs @@ -0,0 +1,69 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use clap::Parser; + +use crate::{ + acl, + helpers::{ + app_paths::{resolve_frontend_dir, tauri_dir}, + cargo, + npm::PackageManager, + }, + Result, +}; + +#[derive(Debug, Parser)] +#[clap(about = "Remove a tauri plugin from the project")] +pub struct Options { + /// The plugin to remove. + pub plugin: String, +} + +pub fn command(options: Options) -> Result<()> { + crate::helpers::app_paths::resolve(); + run(options) +} + +pub fn run(options: Options) -> Result<()> { + let plugin = options.plugin; + + let crate_name = format!("tauri-plugin-{plugin}"); + + let mut plugins = crate::helpers::plugins::known_plugins(); + let metadata = plugins.remove(plugin.as_str()).unwrap_or_default(); + + let frontend_dir = resolve_frontend_dir(); + let tauri_dir = tauri_dir(); + + let target_str = metadata + .desktop_only + .then_some(r#"cfg(not(any(target_os = "android", target_os = "ios")))"#) + .or_else(|| { + metadata + .mobile_only + .then_some(r#"cfg(any(target_os = "android", target_os = "ios"))"#) + }); + + cargo::uninstall_one(cargo::CargoUninstallOptions { + name: &crate_name, + cwd: Some(tauri_dir), + target: target_str, + })?; + + if !metadata.rust_only { + if let Some(manager) = frontend_dir.map(PackageManager::from_project) { + let npm_name = format!("@tauri-apps/plugin-{plugin}"); + manager.remove(&[npm_name], tauri_dir)?; + } + + acl::permission::rm::command(acl::permission::rm::Options { + identifier: format!("{plugin}:*"), + })?; + } + + log::info!("Now, you must manually remove the plugin from your Rust code.",); + + Ok(()) +} diff --git a/crates/tauri-cli/templates/plugin/Cargo.crate-manifest b/crates/tauri-cli/templates/plugin/Cargo.crate-manifest index 64170c165365..e8fc7774087e 100644 --- a/crates/tauri-cli/templates/plugin/Cargo.crate-manifest +++ b/crates/tauri-cli/templates/plugin/Cargo.crate-manifest @@ -11,7 +11,7 @@ links = "tauri-plugin-{{ plugin_name }}" [dependencies] tauri = {{ tauri_dep }} serde = "1.0" -thiserror = "1.0" +thiserror = "2" [build-dependencies] tauri-plugin = {{{ tauri_plugin_dep }}} diff --git a/crates/tauri-codegen/CHANGELOG.md b/crates/tauri-codegen/CHANGELOG.md index f83815365f6c..95a0fb544b90 100644 --- a/crates/tauri-codegen/CHANGELOG.md +++ b/crates/tauri-codegen/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.0.3] + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` + ## \[2.0.2] ### Dependencies diff --git a/crates/tauri-codegen/Cargo.toml b/crates/tauri-codegen/Cargo.toml index 19884121d878..90f0df6270e0 100644 --- a/crates/tauri-codegen/Cargo.toml +++ b/crates/tauri-codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-codegen" -version = "2.0.2" +version = "2.0.3" description = "code generation meant to be consumed inside of `tauri` through `tauri-build` or `tauri-macros`" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -20,10 +20,10 @@ quote = "1" syn = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tauri-utils = { version = "2.0.2", path = "../tauri-utils", features = [ +tauri-utils = { version = "2.1.0", path = "../tauri-utils", features = [ "build", ] } -thiserror = "1" +thiserror = "2" walkdir = "2" brotli = { version = "7", optional = true, default-features = false, features = [ "std", diff --git a/crates/tauri-macros/CHANGELOG.md b/crates/tauri-macros/CHANGELOG.md index a61e11044b42..3c80b3359ff7 100644 --- a/crates/tauri-macros/CHANGELOG.md +++ b/crates/tauri-macros/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## \[2.0.3] + +### Enhancements + +- [`17c6952ae`](https://www.github.com/tauri-apps/tauri/commit/17c6952aec965fa41e6695ad68461a218afc20f1) ([#11522](https://www.github.com/tauri-apps/tauri/pull/11522) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance the error message when using `async` commands with a reference. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` +- Upgraded to `tauri-codegen@2.0.3` + ## \[2.0.1] ### What's Changed diff --git a/crates/tauri-macros/Cargo.toml b/crates/tauri-macros/Cargo.toml index 543bc7f69d19..adf7d9806f76 100644 --- a/crates/tauri-macros/Cargo.toml +++ b/crates/tauri-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-macros" -version = "2.0.2" +version = "2.0.3" description = "Macros for the tauri crate." exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -20,8 +20,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] } quote = "1" syn = { version = "2", features = ["full"] } heck = "0.5" -tauri-codegen = { version = "2.0.2", default-features = false, path = "../tauri-codegen" } -tauri-utils = { version = "2.0.2", path = "../tauri-utils" } +tauri-codegen = { version = "2.0.3", default-features = false, path = "../tauri-codegen" } +tauri-utils = { version = "2.1.0", path = "../tauri-utils" } [features] custom-protocol = [] diff --git a/crates/tauri-macros/src/command/wrapper.rs b/crates/tauri-macros/src/command/wrapper.rs index 306d964e864f..4babd0dbb325 100644 --- a/crates/tauri-macros/src/command/wrapper.rs +++ b/crates/tauri-macros/src/command/wrapper.rs @@ -186,9 +186,18 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream { // only implemented by `Result`. That way we don't exclude renamed result types // which we wouldn't otherwise be able to detect purely from the token stream. // The "error message" displayed to the user is simply the trait name. + // + // TODO: remove this check once our MSRV is high enough + let diagnostic = if is_rustc_at_least(1, 78) { + quote!(#[diagnostic::on_unimplemented(message = "async commands that contain references as inputs must return a `Result`")]) + } else { + quote!() + }; + async_command_check = quote_spanned! {return_type.span() => #[allow(unreachable_code, clippy::diverging_sub_expression)] const _: () = if false { + #diagnostic trait AsyncCommandMustReturnResult {} impl AsyncCommandMustReturnResult for ::std::result::Result {} let _check: #return_type = unreachable!(); @@ -452,3 +461,42 @@ fn parse_arg( } ))) } + +fn is_rustc_at_least(major: u32, minor: u32) -> bool { + let version = rustc_version(); + version.0 >= major && version.1 >= minor +} + +fn rustc_version() -> (u32, u32) { + cross_command("rustc") + .arg("-V") + .output() + .ok() + .and_then(|o| { + let version = String::from_utf8_lossy(&o.stdout) + .trim() + .split(' ') + .nth(1) + .unwrap_or_default() + .split(".") + .take(2) + .flat_map(|p| p.parse::().ok()) + .collect::>(); + version + .first() + .and_then(|major| version.get(1).map(|minor| (*major, *minor))) + }) + .unwrap_or((1, 0)) +} + +fn cross_command(bin: &str) -> std::process::Command { + #[cfg(target_os = "windows")] + let cmd = { + let mut cmd = std::process::Command::new("cmd"); + cmd.arg("/c").arg(bin); + cmd + }; + #[cfg(not(target_os = "windows"))] + let cmd = std::process::Command::new(bin); + cmd +} diff --git a/crates/tauri-plugin/CHANGELOG.md b/crates/tauri-plugin/CHANGELOG.md index 482b831b6be5..26976234ab45 100644 --- a/crates/tauri-plugin/CHANGELOG.md +++ b/crates/tauri-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## \[2.0.3] + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` + ## \[2.0.2] ### Dependencies diff --git a/crates/tauri-plugin/Cargo.toml b/crates/tauri-plugin/Cargo.toml index 074e1b47b1dd..b572e55ff1ac 100644 --- a/crates/tauri-plugin/Cargo.toml +++ b/crates/tauri-plugin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-plugin" -version = "2.0.2" +version = "2.0.3" description = "Build script and runtime Tauri plugin definitions" authors.workspace = true homepage.workspace = true @@ -30,7 +30,7 @@ runtime = [] [dependencies] anyhow = { version = "1", optional = true } serde = { version = "1", optional = true } -tauri-utils = { version = "2.0.2", default-features = false, features = [ +tauri-utils = { version = "2.1.0", default-features = false, features = [ "build", ], path = "../tauri-utils" } serde_json = { version = "1", optional = true } diff --git a/crates/tauri-runtime-wry/CHANGELOG.md b/crates/tauri-runtime-wry/CHANGELOG.md index 11c4ee676cee..6fa4627d2914 100644 --- a/crates/tauri-runtime-wry/CHANGELOG.md +++ b/crates/tauri-runtime-wry/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## \[2.2.0] + +### New Features + +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows. + +### Bug Fixes + +- [`229d7f8e2`](https://www.github.com/tauri-apps/tauri/commit/229d7f8e220cc8d5ca06eff1ed85cb7d047c1d6c) ([#11616](https://www.github.com/tauri-apps/tauri/pull/11616) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix regression in creating child webviews on macOS and Windows, covering the whole window. +- [`8c6d1e8e6`](https://www.github.com/tauri-apps/tauri/commit/8c6d1e8e6c852667bb223b5f4823948868c26d98) ([#11401](https://www.github.com/tauri-apps/tauri/pull/11401) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter method failing on Linux with `GDK may only be used from the main thread`. +- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` +- Upgraded to `tauri-runtime@2.2.0` + ## \[2.1.2] ### Dependencies diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index dd7ab2009b39..125b1de598cd 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-runtime-wry" -version = "2.1.2" +version = "2.2.0" description = "Wry bindings to the Tauri runtime" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -17,15 +17,15 @@ rustc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -wry = { version = "0.46.1", default-features = false, features = [ +wry = { version = "0.47", default-features = false, features = [ "drag-drop", "protocol", "os-webview", "linux-body", ] } -tao = { version = "0.30.2", default-features = false, features = ["rwh_06"] } -tauri-runtime = { version = "2.1.0", path = "../tauri-runtime" } -tauri-utils = { version = "2.0.2", path = "../tauri-utils" } +tao = { version = "0.30.6", default-features = false, features = ["rwh_06"] } +tauri-runtime = { version = "2.2.0", path = "../tauri-runtime" } +tauri-utils = { version = "2.1.0", path = "../tauri-utils" } raw-window-handle = "0.6" http = "1.1" url = "2" diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 4dd17d494910..6d256f1c32a8 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -22,8 +22,8 @@ use tauri_runtime::{ monitor::Monitor, webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler}, window::{ - CursorIcon, DetachedWindow, DragDropEvent, PendingWindow, RawWindow, WebviewEvent, - WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints, + CursorIcon, DetachedWindow, DetachedWindowWebview, DragDropEvent, PendingWindow, RawWindow, + WebviewEvent, WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, WindowSizeConstraints, }, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState, ProgressBarStatus, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, @@ -65,7 +65,10 @@ use tao::{ }; #[cfg(target_os = "macos")] use tauri_utils::TitleBarStyle; -use tauri_utils::{config::WindowConfig, Theme}; +use tauri_utils::{ + config::{Color, WindowConfig}, + Theme, +}; use url::Url; use wry::{ DragDropEvent as WryDragDropEvent, ProxyConfig, ProxyEndpoint, WebContext as WryWebContext, @@ -276,7 +279,16 @@ impl Context { let label = pending.label.clone(); let context = self.clone(); let window_id = self.next_window_id(); - let webview_id = pending.webview.as_ref().map(|_| context.next_webview_id()); + let (webview_id, use_https_scheme) = pending + .webview + .as_ref() + .map(|w| { + ( + Some(context.next_webview_id()), + w.webview_attributes.use_https_scheme, + ) + }) + .unwrap_or((None, false)); send_user_message( self, @@ -300,13 +312,19 @@ impl Context { context: self.clone(), }; - let detached_webview = webview_id.map(|id| DetachedWebview { - label: label.clone(), - dispatcher: WryWebviewDispatcher { - window_id: Arc::new(Mutex::new(window_id)), - webview_id: id, - context: self.clone(), - }, + let detached_webview = webview_id.map(|id| { + let webview = DetachedWebview { + label: label.clone(), + dispatcher: WryWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id: id, + context: self.clone(), + }, + }; + DetachedWindowWebview { + webview, + use_https_scheme, + } }); Ok(DetachedWindow { @@ -746,6 +764,13 @@ impl WindowBuilder for WindowBuilderWrapper { builder = builder.title_bar_style(TitleBarStyle::Visible); } + builder = builder.title("Tauri App"); + + #[cfg(windows)] + { + builder = builder.window_classname("Tauri Window"); + } + builder } @@ -789,6 +814,7 @@ impl WindowBuilder for WindowBuilderWrapper { window = window .title(config.title.to_string()) .inner_size(config.width, config.height) + .focused(config.focus) .visible(config.visible) .resizable(config.resizable) .fullscreen(config.fullscreen) @@ -819,6 +845,9 @@ impl WindowBuilder for WindowBuilderWrapper { if let Some(max_height) = config.max_height { constraints.max_height = Some(tao::dpi::LogicalUnit::new(max_height).into()); } + if let Some(color) = config.background_color { + window = window.background_color(color); + } window = window.inner_size_constraints(constraints); if let (Some(x), Some(y)) = (config.x, config.y) { @@ -828,6 +857,10 @@ impl WindowBuilder for WindowBuilderWrapper { if config.center { window = window.center(); } + + if let Some(window_classname) = &config.window_classname { + window = window.window_classname(window_classname); + } } window @@ -1053,6 +1086,11 @@ impl WindowBuilder for WindowBuilderWrapper { Ok(self) } + fn background_color(mut self, color: Color) -> Self { + self.inner = self.inner.with_background_color(color.into()); + self + } + #[cfg(any(windows, target_os = "linux"))] fn skip_taskbar(mut self, skip: bool) -> Self { self.inner = self.inner.with_skip_taskbar(skip); @@ -1088,6 +1126,16 @@ impl WindowBuilder for WindowBuilderWrapper { _ => Theme::Light, }) } + + #[cfg(windows)] + fn window_classname>(mut self, window_classname: S) -> Self { + self.inner = self.inner.with_window_classname(window_classname); + self + } + #[cfg(not(windows))] + fn window_classname>(self, _window_classname: S) -> Self { + self + } } #[cfg(any( @@ -1218,6 +1266,7 @@ pub enum WindowMessage { SetProgressBar(ProgressBarState), SetTitleBarStyle(tauri_utils::TitleBarStyle), SetTheme(Option), + SetBackgroundColor(Option), DragWindow, ResizeDragWindow(tauri_runtime::ResizeDirection), RequestRedraw, @@ -1259,6 +1308,7 @@ pub enum WebviewMessage { Reparent(WindowId, Sender>), SetAutoResize(bool), SetZoom(f64), + SetBackgroundColor(Option), ClearAllBrowsingData, // Getters Url(Sender>), @@ -1575,6 +1625,17 @@ impl WebviewDispatch for WryWebviewDispatcher { ), ) } + + fn set_background_color(&self, color: Option) -> Result<()> { + send_user_message( + &self.context, + Message::Webview( + *self.window_id.lock().unwrap(), + self.webview_id, + WebviewMessage::SetBackgroundColor(color), + ), + ) + } } /// The Tauri [`WindowDispatch`] for [`Wry`]. @@ -2087,6 +2148,13 @@ impl WindowDispatch for WryWindowDispatcher { Message::Window(self.window_id, WindowMessage::SetTheme(theme)), ) } + + fn set_background_color(&self, color: Option) -> Result<()> { + send_user_message( + &self.context, + Message::Window(self.window_id, WindowMessage::SetBackgroundColor(color)), + ) + } } #[derive(Clone)] @@ -2135,6 +2203,8 @@ pub struct WindowWrapper { webviews: Vec, window_event_listeners: WindowEventListeners, #[cfg(windows)] + background_color: Option, + #[cfg(windows)] is_window_transparent: bool, #[cfg(windows)] surface: Option, Arc>>, @@ -2496,10 +2566,16 @@ impl Runtime for Wry { ) -> Result> { let label = pending.label.clone(); let window_id = self.context.next_window_id(); - let webview_id = pending + let (webview_id, use_https_scheme) = pending .webview .as_ref() - .map(|_| self.context.next_webview_id()); + .map(|w| { + ( + Some(self.context.next_webview_id()), + w.webview_attributes.use_https_scheme, + ) + }) + .unwrap_or((None, false)); let window = create_window( window_id, @@ -2523,13 +2599,19 @@ impl Runtime for Wry { .borrow_mut() .insert(window_id, window); - let detached_webview = webview_id.map(|id| DetachedWebview { - label: label.clone(), - dispatcher: WryWebviewDispatcher { - window_id: Arc::new(Mutex::new(window_id)), - webview_id: id, - context: self.context.clone(), - }, + let detached_webview = webview_id.map(|id| { + let webview = DetachedWebview { + label: label.clone(), + dispatcher: WryWebviewDispatcher { + window_id: Arc::new(Mutex::new(window_id)), + webview_id: id, + context: self.context.clone(), + }, + }; + DetachedWindowWebview { + webview, + use_https_scheme, + } }); Ok(DetachedWindow { @@ -3048,6 +3130,9 @@ fn handle_user_message( _ => None, }); } + WindowMessage::SetBackgroundColor(color) => { + window.set_background_color(color.map(Into::into)) + } } } } @@ -3243,6 +3328,13 @@ fn handle_user_message( log::error!("failed to set webview zoom: {e}"); } } + WebviewMessage::SetBackgroundColor(color) => { + if let Err(e) = + webview.set_background_color(color.map(Into::into).unwrap_or((255, 255, 255, 255))) + { + log::error!("failed to set webview background color: {e}"); + } + } WebviewMessage::ClearAllBrowsingData => { if let Err(e) = webview.clear_all_browsing_data() { log::error!("failed to clear webview browsing data: {e}"); @@ -3411,6 +3503,8 @@ fn handle_user_message( Message::CreateRawWindow(window_id, handler, sender) => { let (label, builder) = handler(); + #[cfg(windows)] + let background_color = builder.window.background_color; #[cfg(windows)] let is_window_transparent = builder.window.transparent; @@ -3423,7 +3517,7 @@ fn handle_user_message( let surface = if is_window_transparent { if let Ok(context) = softbuffer::Context::new(window.clone()) { if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) { - window.clear_surface(&mut surface); + window.draw_surface(&mut surface, background_color); Some(surface) } else { None @@ -3444,6 +3538,8 @@ fn handle_user_message( window_event_listeners: Default::default(), webviews: Vec::new(), #[cfg(windows)] + background_color, + #[cfg(windows)] is_window_transparent, #[cfg(windows)] surface, @@ -3507,9 +3603,10 @@ fn handle_event_loop( let mut windows_ref = windows.0.borrow_mut(); if let Some(window) = windows_ref.get_mut(&window_id) { if window.is_window_transparent { + let background_color = window.background_color; if let Some(surface) = &mut window.surface { if let Some(window) = &window.inner { - window.clear_surface(surface); + window.draw_surface(surface, background_color); } } } @@ -3793,6 +3890,8 @@ fn create_window( let window_event_listeners = WindowEventListeners::default(); + #[cfg(windows)] + let background_color = window_builder.inner.window.background_color; #[cfg(windows)] let is_window_transparent = window_builder.inner.window.transparent; @@ -3924,7 +4023,7 @@ fn create_window( let surface = if is_window_transparent { if let Ok(context) = softbuffer::Context::new(window.clone()) { if let Ok(mut surface) = softbuffer::Surface::new(&context, window.clone()) { - window.clear_surface(&mut surface); + window.draw_surface(&mut surface, background_color); Some(surface) } else { None @@ -3943,6 +4042,8 @@ fn create_window( webviews, window_event_listeners, #[cfg(windows)] + background_color, + #[cfg(windows)] is_window_transparent, #[cfg(windows)] surface, @@ -4017,7 +4118,7 @@ fn create_webview( let mut webview_builder = WebViewBuilder::with_web_context(&mut web_context.inner) .with_id(&label) - .with_focused(window.is_focused()) + .with_focused(webview_attributes.focus) .with_url(&url) .with_transparent(webview_attributes.transparent) .with_accept_first_mouse(webview_attributes.accept_first_mouse) @@ -4025,6 +4126,15 @@ fn create_webview( .with_clipboard(webview_attributes.clipboard) .with_hotkeys_zoom(webview_attributes.zoom_hotkeys_enabled); + #[cfg(any(target_os = "windows", target_os = "android"))] + { + webview_builder = webview_builder.with_https_scheme(webview_attributes.use_https_scheme); + } + + if let Some(color) = webview_attributes.background_color { + webview_builder = webview_builder.with_background_color(color.into()); + } + if webview_attributes.drag_drop_handler_enabled { let proxy = context.proxy.clone(); let window_id_ = window_id.clone(); @@ -4169,13 +4279,22 @@ fn create_webview( #[cfg(windows)] { - webview_builder = webview_builder.with_https_scheme(false); + webview_builder = webview_builder + .with_browser_extensions_enabled(webview_attributes.browser_extensions_enabled); } - #[cfg(windows)] + #[cfg(any( + windows, + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ))] { - webview_builder = webview_builder - .with_browser_extensions_enabled(webview_attributes.browser_extensions_enabled); + if let Some(path) = &webview_attributes.extensions_path { + webview_builder = webview_builder.with_extension_path(path); + } } webview_builder = webview_builder.with_ipc_handler(create_ipc_handler( @@ -4225,7 +4344,7 @@ fn create_webview( #[cfg(any(debug_assertions, feature = "devtools"))] { - webview_builder = webview_builder.with_devtools(true); + webview_builder = webview_builder.with_devtools(webview_attributes.devtools.unwrap_or(true)); } #[cfg(target_os = "android")] @@ -4259,7 +4378,7 @@ fn create_webview( target_os = "ios", target_os = "android" ))] - WebviewKind::WindowChild => webview_builder.build(&window), + WebviewKind::WindowChild => webview_builder.build_as_child(&window), WebviewKind::WindowContent => { #[cfg(any( target_os = "windows", @@ -4281,7 +4400,7 @@ fn create_webview( builder } } - .map_err(|e| Error::CreateWebview(Box::new(dbg!(e))))?; + .map_err(|e| Error::CreateWebview(Box::new(e)))?; if kind == WebviewKind::WindowContent { #[cfg(any( diff --git a/crates/tauri-runtime-wry/src/window/mod.rs b/crates/tauri-runtime-wry/src/window/mod.rs index 1c649e0764dd..c2b1b448686d 100644 --- a/crates/tauri-runtime-wry/src/window/mod.rs +++ b/crates/tauri-runtime-wry/src/window/mod.rs @@ -39,12 +39,13 @@ pub trait WindowExt { /// Clears the window sufrace. i.e make it it transparent. #[cfg(windows)] - fn clear_surface( + fn draw_surface( &self, surface: &mut softbuffer::Surface< std::sync::Arc, std::sync::Arc, >, + background_color: Option, ); } diff --git a/crates/tauri-runtime-wry/src/window/windows.rs b/crates/tauri-runtime-wry/src/window/windows.rs index f7825051f777..6c032beabda8 100644 --- a/crates/tauri-runtime-wry/src/window/windows.rs +++ b/crates/tauri-runtime-wry/src/window/windows.rs @@ -43,12 +43,13 @@ impl super::WindowExt for tao::window::Window { } } - fn clear_surface( + fn draw_surface( &self, surface: &mut softbuffer::Surface< std::sync::Arc, std::sync::Arc, >, + background_color: Option, ) { let size = self.inner_size(); if let (Some(width), Some(height)) = ( @@ -57,7 +58,10 @@ impl super::WindowExt for tao::window::Window { ) { surface.resize(width, height).unwrap(); let mut buffer = surface.buffer_mut().unwrap(); - buffer.fill(0); + let color = background_color + .map(|(r, g, b, _)| (b as u32) | ((g as u32) << 8) | ((r as u32) << 16)) + .unwrap_or(0); + buffer.fill(color); let _ = buffer.present(); } } diff --git a/crates/tauri-runtime/CHANGELOG.md b/crates/tauri-runtime/CHANGELOG.md index c57d8b0152bf..8970d59f66fb 100644 --- a/crates/tauri-runtime/CHANGELOG.md +++ b/crates/tauri-runtime/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## \[2.2.0] + +### New Features + +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows. + +### Bug Fixes + +- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` + ## \[2.1.1] ### Dependencies diff --git a/crates/tauri-runtime/Cargo.toml b/crates/tauri-runtime/Cargo.toml index f5b6fd0814ef..6a2110af08a0 100644 --- a/crates/tauri-runtime/Cargo.toml +++ b/crates/tauri-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-runtime" -version = "2.1.1" +version = "2.2.0" description = "Runtime for Tauri applications" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -28,8 +28,8 @@ targets = [ [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0" -tauri-utils = { version = "2.0.2", path = "../tauri-utils" } +thiserror = "2" +tauri-utils = { version = "2.1.0", path = "../tauri-utils" } http = "1.1" raw-window-handle = "0.6" url = { version = "2" } diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index b1e2c7184f61..1257dab15f86 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -18,6 +18,7 @@ use raw_window_handle::DisplayHandle; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt::Debug, sync::mpsc::Sender}; +use tauri_utils::config::Color; use tauri_utils::Theme; use url::Url; use webview::{DetachedWebview, PendingWebview}; @@ -523,6 +524,9 @@ pub trait WebviewDispatch: Debug + Clone + Send + Sync + Sized + ' /// Set the webview zoom level fn set_zoom(&self, scale_factor: f64) -> Result<()>; + /// Set the webview background. + fn set_background_color(&self, color: Option) -> Result<()>; + /// Clear all browsing data for this webview. fn clear_all_browsing_data(&self) -> Result<()>; } @@ -753,6 +757,9 @@ pub trait WindowDispatch: Debug + Clone + Send + Sync + Sized + 's /// Updates the window visibleOnAllWorkspaces flag. fn set_visible_on_all_workspaces(&self, visible_on_all_workspaces: bool) -> Result<()>; + /// Set the window background. + fn set_background_color(&self, color: Option) -> Result<()>; + /// Prevents the window contents from being captured by other apps. fn set_content_protected(&self, protected: bool) -> Result<()>; diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index 4c4c83e3ea8a..e14799bf44bf 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -7,7 +7,7 @@ use crate::{window::is_label_valid, Rect, Runtime, UserEvent}; use http::Request; -use tauri_utils::config::{WebviewUrl, WindowConfig, WindowEffectsConfig}; +use tauri_utils::config::{Color, WebviewUrl, WindowConfig, WindowEffectsConfig}; use url::Url; use std::{ @@ -204,17 +204,27 @@ pub struct WebviewAttributes { pub window_effects: Option, pub incognito: bool, pub transparent: bool, + pub focus: bool, pub bounds: Option, pub auto_resize: bool, pub proxy_url: Option, pub zoom_hotkeys_enabled: bool, pub browser_extensions_enabled: bool, + pub extensions_path: Option, + pub use_https_scheme: bool, + pub devtools: Option, + pub background_color: Option, } impl From<&WindowConfig> for WebviewAttributes { fn from(config: &WindowConfig) -> Self { - let mut builder = Self::new(config.url.clone()); - builder = builder.incognito(config.incognito); + let mut builder = Self::new(config.url.clone()) + .incognito(config.incognito) + .focused(config.focus) + .zoom_hotkeys_enabled(config.zoom_hotkeys_enabled) + .use_https_scheme(config.use_https_scheme) + .browser_extensions_enabled(config.browser_extensions_enabled) + .devtools(config.devtools); #[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))] { builder = builder.transparent(config.transparent); @@ -235,8 +245,9 @@ impl From<&WindowConfig> for WebviewAttributes { if let Some(url) = &config.proxy_url { builder = builder.proxy_url(url.to_owned()); } - builder = builder.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled); - builder = builder.browser_extensions_enabled(config.browser_extensions_enabled); + if let Some(color) = config.background_color { + builder = builder.background_color(color); + } builder } } @@ -256,11 +267,16 @@ impl WebviewAttributes { window_effects: None, incognito: false, transparent: false, + focus: true, bounds: None, auto_resize: false, proxy_url: None, zoom_hotkeys_enabled: false, browser_extensions_enabled: false, + extensions_path: None, + use_https_scheme: false, + devtools: None, + background_color: None, } } @@ -338,6 +354,13 @@ impl WebviewAttributes { self } + /// Whether the webview should be focused or not. + #[must_use] + pub fn focused(mut self, focus: bool) -> Self { + self.focus = focus; + self + } + /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes. #[must_use] pub fn auto_resize(mut self) -> Self { @@ -378,6 +401,47 @@ impl WebviewAttributes { self.browser_extensions_enabled = enabled; self } + + /// Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + /// + /// ## Note + /// + /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + /// + /// ## Warning + /// + /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data. + #[must_use] + pub fn use_https_scheme(mut self, enabled: bool) -> Self { + self.use_https_scheme = enabled; + self + } + + /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default. + /// + /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + /// + /// ## Platform-specific + /// + /// - macOS: This will call private functions on **macOS**. + /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + #[must_use] + pub fn devtools(mut self, enabled: Option) -> Self { + self.devtools = enabled; + self + } + + /// Set the window and webview background color. + /// ## Platform-specific: + /// + /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer. + /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + #[must_use] + pub fn background_color(mut self, color: Color) -> Self { + self.background_color = Some(color); + self + } } /// IPC handler. diff --git a/crates/tauri-runtime/src/window.rs b/crates/tauri-runtime/src/window.rs index 54676ef65bf9..dc843100da98 100644 --- a/crates/tauri-runtime/src/window.rs +++ b/crates/tauri-runtime/src/window.rs @@ -11,7 +11,10 @@ use crate::{ use dpi::PixelUnit; use serde::{Deserialize, Deserializer, Serialize}; -use tauri_utils::{config::WindowConfig, Theme}; +use tauri_utils::{ + config::{Color, WindowConfig}, + Theme, +}; #[cfg(windows)] use windows::Win32::Foundation::HWND; @@ -354,6 +357,10 @@ pub trait WindowBuilder: WindowBuilderBase { #[must_use] fn skip_taskbar(self, skip: bool) -> Self; + /// Set the window background color. + #[must_use] + fn background_color(self, color: Color) -> Self; + /// Sets whether or not the window has shadow. /// /// ## Platform-specific @@ -438,6 +445,10 @@ pub trait WindowBuilder: WindowBuilderBase { fn has_icon(&self) -> bool; fn get_theme(&self) -> Option; + + /// Sets custom name for Windows' window class. **Windows only**. + #[must_use] + fn window_classname>(self, window_classname: S) -> Self; } /// A window that has yet to be built. @@ -512,7 +523,23 @@ pub struct DetachedWindow> { pub dispatcher: R::WindowDispatcher, /// The webview dispatcher in case this window has an attached webview. - pub webview: Option>, + pub webview: Option>, +} + +/// A detached webview associated with a window. +#[derive(Debug)] +pub struct DetachedWindowWebview> { + pub webview: DetachedWebview, + pub use_https_scheme: bool, +} + +impl> Clone for DetachedWindowWebview { + fn clone(&self) -> Self { + Self { + webview: self.webview.clone(), + use_https_scheme: self.use_https_scheme, + } + } } impl> Clone for DetachedWindow { diff --git a/crates/tauri-schema-generator/schemas/capability.schema.json b/crates/tauri-schema-generator/schemas/capability.schema.json index 80df9a6893f8..0c9462c500aa 100644 --- a/crates/tauri-schema-generator/schemas/capability.schema.json +++ b/crates/tauri-schema-generator/schemas/capability.schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Capability", - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", "type": "object", "required": [ "identifier", @@ -48,7 +48,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ```", + "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ]\n ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" diff --git a/crates/tauri-schema-generator/schemas/config.schema.json b/crates/tauri-schema-generator/schemas/config.schema.json index a05ba79935ed..c701eb78c6b0 100644 --- a/crates/tauri-schema-generator/schemas/config.schema.json +++ b/crates/tauri-schema-generator/schemas/config.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://schema.tauri.app/config/2.0.6", + "$id": "https://schema.tauri.app/config/2.1.1", "title": "Config", "description": "The Tauri configuration object.\n It is read from a file where you can define your frontend assets,\n configure the bundler and define a tray icon.\n\n The configuration file is generated by the\n [`tauri init`](https://v2.tauri.app/reference/cli/#init) command that lives in\n your Tauri application source directory (src-tauri).\n\n Once generated, you may modify it at will to customize your Tauri application.\n\n ## File Formats\n\n By default, the configuration is defined as a JSON file named `tauri.conf.json`.\n\n Tauri also supports JSON5 and TOML files via the `config-json5` and `config-toml` Cargo features, respectively.\n The JSON5 file name must be either `tauri.conf.json` or `tauri.conf.json5`.\n The TOML file name is `Tauri.toml`.\n\n ## Platform-Specific Configuration\n\n In addition to the default configuration file, Tauri can\n read a platform-specific configuration from `tauri.linux.conf.json`,\n `tauri.windows.conf.json`, `tauri.macos.conf.json`, `tauri.android.conf.json` and `tauri.ios.conf.json`\n (or `Tauri.linux.toml`, `Tauri.windows.toml`, `Tauri.macos.toml`, `Tauri.android.toml` and `Tauri.ios.toml` if the `Tauri.toml` format is used),\n which gets merged with the main configuration object.\n\n ## Configuration Structure\n\n The configuration is composed of the following objects:\n\n - [`app`](#appconfig): The Tauri configuration\n - [`build`](#buildconfig): The build configuration\n - [`bundle`](#bundleconfig): The bundle configurations\n - [`plugins`](#pluginconfig): The plugins configuration\n\n Example tauri.config.json file:\n\n ```json\n {\n \"productName\": \"tauri-app\",\n \"version\": \"0.1.0\",\n \"build\": {\n \"beforeBuildCommand\": \"\",\n \"beforeDevCommand\": \"\",\n \"devUrl\": \"../dist\",\n \"frontendDist\": \"../dist\"\n },\n \"app\": {\n \"security\": {\n \"csp\": null\n },\n \"windows\": [\n {\n \"fullscreen\": false,\n \"height\": 600,\n \"resizable\": true,\n \"title\": \"Tauri App\",\n \"width\": 800\n }\n ]\n },\n \"bundle\": {},\n \"plugins\": {}\n }\n ```", "type": "object", @@ -397,6 +397,13 @@ "default": false, "type": "boolean" }, + "windowClassname": { + "description": "The name of the window class created on Windows to create the window. **Windows only**.", + "type": [ + "string", + "null" + ] + }, "theme": { "description": "The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+.", "anyOf": [ @@ -486,6 +493,29 @@ "description": "Whether browser extensions can be installed for the webview process\n\n ## Platform-specific:\n\n - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)\n - **MacOS / Linux / iOS / Android** - Unsupported.", "default": false, "type": "boolean" + }, + "useHttpsScheme": { + "description": "Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`.\n\n ## Note\n\n Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux.\n\n ## Warning\n\n Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.", + "default": false, + "type": "boolean" + }, + "devtools": { + "description": "Enable web inspector which is usually called browser devtools. Enabled by default.\n\n This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.\n\n ## Platform-specific\n\n - macOS: This will call private functions on **macOS**.\n - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.\n - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.", + "type": [ + "boolean", + "null" + ] + }, + "backgroundColor": { + "description": "Set the window and webview background color.\n\n ## Platform-specific:\n\n - **Windows**: alpha channel is ignored for the window layer.\n - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.\n - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer.", + "anyOf": [ + { + "$ref": "#/definitions/Color" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -827,32 +857,96 @@ ] }, "Color": { - "description": "a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255.", - "type": "array", - "items": [ + "anyOf": [ { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Color hex string, for example: #fff, #ffffff, or #ffffffff.", + "type": "string", + "pattern": "^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$" }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Array of RGB colors. Each value has minimum of 0 and maximum of 255.", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + ], + "maxItems": 3, + "minItems": 3 }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Array of RGBA colors. Each value has minimum of 0 and maximum of 255.", + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + ], + "maxItems": 4, + "minItems": 4 }, { - "type": "integer", - "format": "uint8", - "minimum": 0.0 + "description": "Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255.", + "type": "object", + "required": [ + "blue", + "green", + "red" + ], + "properties": { + "red": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "green": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "blue": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "alpha": { + "default": 255, + "type": "integer", + "format": "uint8", + "minimum": 0.0 + } + } } - ], - "maxItems": 4, - "minItems": 4 + ] }, "SecurityConfig": { "description": "Security configuration.\n\n See more: ", @@ -924,6 +1018,17 @@ "items": { "$ref": "#/definitions/CapabilityEntry" } + }, + "headers": { + "description": "The headers, which are added to every http response from tauri to the web view\n This doesn't include IPC Messages and error responses", + "anyOf": [ + { + "$ref": "#/definitions/HeaderConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -1104,7 +1209,7 @@ ] }, "Capability": { - "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\n It controls application windows fine grained access to the Tauri core, application, or plugin commands.\n If a window is not matching any capability then it has no access to the IPC layer at all.\n\n This can be done to create groups of windows, based on their required system access, which can reduce\n impact of frontend vulnerabilities in less privileged windows.\n Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.\n A Window can have none, one, or multiple associated capabilities.\n\n ## Example\n\n ```json\n {\n \"identifier\": \"main-user-files-write\",\n \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\",\n \"windows\": [\n \"main\"\n ],\n \"permissions\": [\n \"core:default\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n },\n ],\n \"platforms\": [\"macOS\",\"windows\"]\n }\n ```", "type": "object", "required": [ "identifier", @@ -1151,7 +1256,7 @@ } }, "permissions": { - "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ```", + "description": "List of permissions attached to this capability.\n\n Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.\n For commands directly implemented in the application itself only `${permission-name}`\n is required.\n\n ## Example\n\n ```json\n [\n \"core:default\",\n \"shell:allow-open\",\n \"dialog:open\",\n {\n \"identifier\": \"fs:allow-write-text-file\",\n \"allow\": [{ \"path\": \"$HOME/test.txt\" }]\n }\n ]\n ```", "type": "array", "items": { "$ref": "#/definitions/PermissionEntry" @@ -1333,6 +1438,168 @@ } ] }, + "HeaderConfig": { + "description": "A struct, where the keys are some specific http header names.\n If the values to those keys are defined, then they will be send as part of a response message.\n This does not include error messages and ipc messages\n\n ## Example configuration\n ```javascript\n {\n //..\n app:{\n //..\n security: {\n headers: {\n \"Cross-Origin-Opener-Policy\": \"same-origin\",\n \"Cross-Origin-Embedder-Policy\": \"require-corp\",\n \"Timing-Allow-Origin\": [\n \"https://developer.mozilla.org\",\n \"https://example.com\",\n ],\n \"Access-Control-Expose-Headers\": \"Tauri-Custom-Header\",\n \"Tauri-Custom-Header\": {\n \"key1\": \"'value1' 'value2'\",\n \"key2\": \"'value3'\"\n }\n },\n csp: \"default-src 'self'; connect-src ipc: http://ipc.localhost\",\n }\n //..\n }\n //..\n }\n ```\n In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer).\n The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs.\n The Content-Security-Policy header is defined separately, because it is also handled separately.\n\n For the helloworld example, this config translates into those response headers:\n ```http\n access-control-allow-origin: http://tauri.localhost\n access-control-expose-headers: Tauri-Custom-Header\n content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs='\n content-type: text/html\n cross-origin-embedder-policy: require-corp\n cross-origin-opener-policy: same-origin\n tauri-custom-header: key1 'value1' 'value2'; key2 'value3'\n timing-allow-origin: https://developer.mozilla.org, https://example.com\n ```\n Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted.\n - `String`(JS/Rust): stay the same for the resulting header value\n - `Array`(JS)/`Vec\\`(Rust): Item are joined by \", \" for the resulting header value\n - `Object`(JS)/ `Hashmap\\`(Rust): Items are composed from: key + space + value. Item are then joined by \"; \" for the resulting header value", + "type": "object", + "properties": { + "Access-Control-Allow-Credentials": { + "description": "The Access-Control-Allow-Credentials response header tells browsers whether the\n server allows cross-origin HTTP requests to include credentials.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Allow-Headers": { + "description": "The Access-Control-Allow-Headers response header is used in response\n to a preflight request which includes the Access-Control-Request-Headers\n to indicate which HTTP headers can be used during the actual request.\n\n This header is required if the request has an Access-Control-Request-Headers header.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Allow-Methods": { + "description": "The Access-Control-Allow-Methods response header specifies one or more methods\n allowed when accessing a resource in response to a preflight request.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Expose-Headers": { + "description": "The Access-Control-Expose-Headers response header allows a server to indicate\n which response headers should be made available to scripts running in the browser,\n in response to a cross-origin request.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Access-Control-Max-Age": { + "description": "The Access-Control-Max-Age response header indicates how long the results of a\n preflight request (that is the information contained in the\n Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can\n be cached.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Embedder-Policy": { + "description": "The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding\n cross-origin resources into the document.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Opener-Policy": { + "description": "The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a\n top-level document does not share a browsing context group with cross-origin documents.\n COOP will process-isolate your document and potential attackers can't access your global\n object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Cross-Origin-Resource-Policy": { + "description": "The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the\n browser blocks no-cors cross-origin/cross-site requests to the given resource.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Permissions-Policy": { + "description": "The HTTP Permissions-Policy header provides a mechanism to allow and deny the\n use of browser features in a document or within any \\ elements in the document.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Timing-Allow-Origin": { + "description": "The Timing-Allow-Origin response header specifies origins that are allowed to see values\n of attributes retrieved via features of the Resource Timing API, which would otherwise be\n reported as zero due to cross-origin restrictions.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "X-Content-Type-Options": { + "description": "The X-Content-Type-Options response HTTP header is a marker used by the server to indicate\n that the MIME types advertised in the Content-Type headers should be followed and not be\n changed. The header allows you to avoid MIME type sniffing by saying that the MIME types\n are deliberately configured.\n\n See ", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + }, + "Tauri-Custom-Header": { + "description": "A custom header field Tauri-Custom-Header, don't use it.\n Remember to set Access-Control-Expose-Headers accordingly\n\n **NOT INTENDED FOR PRODUCTION USE**", + "anyOf": [ + { + "$ref": "#/definitions/HeaderSource" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "HeaderSource": { + "description": "definition of a header source\n\n The header value to a header name", + "anyOf": [ + { + "description": "string version of the header Value", + "type": "string" + }, + { + "description": "list version of the header value. Item are joined by \",\" for the real header value", + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "(Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by \";\" for the real header value", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, "TrayIconConfig": { "description": "Configuration for application tray icon.\n\n See more: ", "type": "object", @@ -2789,10 +3056,133 @@ "string", "null" ] + }, + "compression": { + "description": "Compression algorithm and level. Defaults to `Gzip` with level 6.", + "anyOf": [ + { + "$ref": "#/definitions/RpmCompression" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false }, + "RpmCompression": { + "description": "Compression algorithms used when bundling RPM packages.", + "oneOf": [ + { + "description": "Gzip compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "gzip" + ] + }, + "level": { + "description": "Gzip compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Zstd compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "zstd" + ] + }, + "level": { + "description": "Zstd compression level", + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + { + "description": "Xz compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "xz" + ] + }, + "level": { + "description": "Xz compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Bzip2 compression", + "type": "object", + "required": [ + "level", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "bzip2" + ] + }, + "level": { + "description": "Bzip2 compression level", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "description": "Disable compression", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ] + } + }, + "additionalProperties": false + } + ] + }, "MacConfig": { "description": "Configuration for the macOS bundles.\n\n See more: ", "type": "object", diff --git a/crates/tauri-schema-worker/src/config.rs b/crates/tauri-schema-worker/src/config.rs index 41669eceb344..b4c818b910bf 100644 --- a/crates/tauri-schema-worker/src/config.rs +++ b/crates/tauri-schema-worker/src/config.rs @@ -106,7 +106,7 @@ async fn try_next_schema() -> anyhow::Result { async fn schema_file_for_version(version: Version) -> anyhow::Result { let cache = Cache::open("schema".to_string()).await; - let cache_key = format!("https://scheam.tauri.app/config/{version}"); + let cache_key = format!("https://schema.tauri.app/config/{version}"); if let Some(mut cached) = cache.get(cache_key.clone(), true).await? { console_log!("Serving schema for {version} from cache"); return cached.text().await.map_err(Into::into); diff --git a/crates/tauri-utils/CHANGELOG.md b/crates/tauri-utils/CHANGELOG.md index 531326454d6f..319527f97ece 100644 --- a/crates/tauri-utils/CHANGELOG.md +++ b/crates/tauri-utils/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## \[2.1.0] + +### New Features + +- [`fabc2f283`](https://www.github.com/tauri-apps/tauri/commit/fabc2f283e38b62c721326e44645d47138418cbc) ([#11485](https://www.github.com/tauri-apps/tauri/pull/11485) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds a new configuration option `app > security > headers` to define headers that will be added to every http response from tauri to the web view. This doesn't include IPC messages and error responses. +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview. +- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `app > windows > windowClassname` config option to specify the name of the window class on Windows. + +### Enhancements + +- [`c33bbf457`](https://www.github.com/tauri-apps/tauri/commit/c33bbf45740274b6918ea6c647f366fb6008e459) ([#11575](https://www.github.com/tauri-apps/tauri/pull/11575) by [@kornelski](https://www.github.com/tauri-apps/tauri/../../kornelski)) Include the path in ACL I/O errors. + +### Bug Fixes + +- [`378142914`](https://www.github.com/tauri-apps/tauri/commit/37814291475814b4a24cc77b6fa457ec9ba7a779) ([#11429](https://www.github.com/tauri-apps/tauri/pull/11429) by [@griffi-gh](https://www.github.com/tauri-apps/tauri/../../griffi-gh)) Enhance resource directory resolution to support running on distros like NixOS. + ## \[2.0.2] ### New Features diff --git a/crates/tauri-utils/Cargo.toml b/crates/tauri-utils/Cargo.toml index cd36d6ce217c..27a44f5e6646 100644 --- a/crates/tauri-utils/Cargo.toml +++ b/crates/tauri-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri-utils" -version = "2.0.2" +version = "2.1.0" description = "Utilities for Tauri" exclude = ["CHANGELOG.md", "/target"] readme = "README.md" @@ -15,7 +15,7 @@ rust-version.workspace = true [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" -thiserror = "1" +thiserror = "2" phf = { version = "0.11", features = ["macros"] } brotli = { version = "7", optional = true, default-features = false, features = [ "std", @@ -46,6 +46,7 @@ log = "0.4.21" cargo_metadata = { version = "0.18", optional = true } serde-untagged = "0.1" uuid = { version = "1", features = ["serde"] } +http = "1.1.0" [target."cfg(target_os = \"macos\")".dependencies] swift-rs = { version = "1.0.7", optional = true, features = ["build"] } diff --git a/crates/tauri-utils/src/acl/build.rs b/crates/tauri-utils/src/acl/build.rs index d2425b50ad46..12ce4e9e0a84 100644 --- a/crates/tauri-utils/src/acl/build.rs +++ b/crates/tauri-utils/src/acl/build.rs @@ -34,7 +34,12 @@ pub const PERMISSION_FILE_EXTENSIONS: &[&str] = &["json", "toml"]; pub const PERMISSION_DOCS_FILE_NAME: &str = "reference.md"; /// Allowed capability file extensions -const CAPABILITY_FILE_EXTENSIONS: &[&str] = &["json", "toml"]; +const CAPABILITY_FILE_EXTENSIONS: &[&str] = &[ + "json", + #[cfg(feature = "config-json5")] + "json5", + "toml", +]; /// Known folder name of the capability schemas const CAPABILITIES_SCHEMA_FOLDER_NAME: &str = "schemas"; @@ -44,8 +49,8 @@ const CORE_PLUGIN_PERMISSIONS_TOKEN: &str = "__CORE_PLUGIN__"; fn parse_permissions(paths: Vec) -> Result, Error> { let mut permissions = Vec::new(); for path in paths { - let permission_file = fs::read_to_string(&path).map_err(Error::ReadFile)?; let ext = path.extension().unwrap().to_string_lossy().to_string(); + let permission_file = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?; let permission: PermissionFile = match ext.as_str() { "toml" => toml::from_str(&permission_file)?, "json" => serde_json::from_str(&permission_file)?, @@ -81,7 +86,8 @@ pub fn define_permissions bool>( let pkg_name_valid_path = pkg_name.replace(':', "-"); let permission_files_path = out_dir.join(format!("{}-permission-files", pkg_name_valid_path)); let permission_files_json = serde_json::to_string(&permission_files)?; - fs::write(&permission_files_path, permission_files_json).map_err(Error::WriteFile)?; + fs::write(&permission_files_path, permission_files_json) + .map_err(|e| Error::WriteFile(e, permission_files_path.clone()))?; if let Some(plugin_name) = pkg_name.strip_prefix("tauri:") { println!( @@ -115,7 +121,8 @@ pub fn read_permissions() -> Result>, Error> }) { let permissions_path = PathBuf::from(value); - let permissions_str = fs::read_to_string(&permissions_path).map_err(Error::ReadFile)?; + let permissions_str = + fs::read_to_string(&permissions_path).map_err(|e| Error::ReadFile(e, permissions_path))?; let permissions: Vec = serde_json::from_str(&permissions_str)?; let permissions = parse_permissions(permissions)?; @@ -139,7 +146,7 @@ pub fn define_global_scope_schema( out_dir: &Path, ) -> Result<(), Error> { let path = out_dir.join("global-scope.json"); - fs::write(&path, serde_json::to_vec(&schema)?).map_err(Error::WriteFile)?; + fs::write(&path, serde_json::to_vec(&schema)?).map_err(|e| Error::WriteFile(e, path.clone()))?; if let Some(plugin_name) = pkg_name.strip_prefix("tauri:") { println!( @@ -170,7 +177,7 @@ pub fn read_global_scope_schemas() -> Result, }) { let path = PathBuf::from(value); - let json = fs::read_to_string(&path).map_err(Error::ReadFile)?; + let json = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?; let schema: serde_json::Value = serde_json::from_str(&json)?; let plugin_crate_name = plugin_crate_name_var.to_lowercase().replace('_', "-"); @@ -368,7 +375,7 @@ pub fn generate_docs( format!("{default_permission}\n{PERMISSION_TABLE_HEADER}\n{permission_table}\n"); let reference_path = out_dir.join(PERMISSION_DOCS_FILE_NAME); - write_if_changed(reference_path, docs).map_err(Error::WriteFile)?; + write_if_changed(&reference_path, docs).map_err(|e| Error::WriteFile(e, reference_path))?; Ok(()) } diff --git a/crates/tauri-utils/src/acl/capability.rs b/crates/tauri-utils/src/acl/capability.rs index a158ea7d3df7..d5aeb3a79eac 100644 --- a/crates/tauri-utils/src/acl/capability.rs +++ b/crates/tauri-utils/src/acl/capability.rs @@ -261,11 +261,14 @@ impl CapabilityFile { /// Load the given capability file. pub fn load>(path: P) -> Result { let path = path.as_ref(); - let capability_file = std::fs::read_to_string(path).map_err(super::Error::ReadFile)?; + let capability_file = + std::fs::read_to_string(path).map_err(|e| super::Error::ReadFile(e, path.into()))?; let ext = path.extension().unwrap().to_string_lossy().to_string(); let file: Self = match ext.as_str() { "toml" => toml::from_str(&capability_file)?, "json" => serde_json::from_str(&capability_file)?, + #[cfg(feature = "config-json5")] + "json5" => json5::from_str(&capability_file)?, _ => return Err(super::Error::UnknownCapabilityFormat(ext)), }; Ok(file) diff --git a/crates/tauri-utils/src/acl/identifier.rs b/crates/tauri-utils/src/acl/identifier.rs index 64df0ed274cc..1693e4195b7c 100644 --- a/crates/tauri-utils/src/acl/identifier.rs +++ b/crates/tauri-utils/src/acl/identifier.rs @@ -122,7 +122,7 @@ pub enum ParseIdentifierError { Empty, /// Identifier is too long. - #[error("identifiers cannot be longer than {}, found {0}", MAX_LEN_IDENTIFIER)] + #[error("identifiers cannot be longer than {len}, found {0}", len = MAX_LEN_IDENTIFIER)] Humongous(usize), /// Identifier is not in a valid format. diff --git a/crates/tauri-utils/src/acl/mod.rs b/crates/tauri-utils/src/acl/mod.rs index 3a48b377d763..7821b7c068ba 100644 --- a/crates/tauri-utils/src/acl/mod.rs +++ b/crates/tauri-utils/src/acl/mod.rs @@ -22,7 +22,7 @@ //! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax use serde::{Deserialize, Serialize}; -use std::{num::NonZeroU64, str::FromStr, sync::Arc}; +use std::{num::NonZeroU64, path::PathBuf, str::FromStr, sync::Arc}; use thiserror::Error; use url::Url; @@ -71,20 +71,20 @@ pub enum Error { LinksName, /// IO error while reading a file - #[error("failed to read file: {0}")] - ReadFile(std::io::Error), + #[error("failed to read file '{}': {}", _1.display(), _0)] + ReadFile(std::io::Error, PathBuf), /// IO error while writing a file - #[error("failed to write file: {0}")] - WriteFile(std::io::Error), + #[error("failed to write file '{}': {}", _1.display(), _0)] + WriteFile(std::io::Error, PathBuf), /// IO error while creating a file - #[error("failed to create file: {0}")] - CreateFile(std::io::Error), + #[error("failed to create file '{}': {}", _1.display(), _0)] + CreateFile(std::io::Error, PathBuf), /// IO error while creating a dir - #[error("failed to create dir: {0}")] - CreateDir(std::io::Error), + #[error("failed to create dir '{}': {}", _1.display(), _0)] + CreateDir(std::io::Error, PathBuf), /// [`cargo_metadata`] was not able to complete successfully #[cfg(feature = "build")] @@ -103,6 +103,11 @@ pub enum Error { #[error("failed to parse JSON: {0}")] Json(#[from] serde_json::Error), + /// Invalid JSON5 encountered + #[cfg(feature = "config-json5")] + #[error("failed to parse JSON5: {0}")] + Json5(#[from] json5::Error), + /// Invalid permissions file format #[error("unknown permission format {0}")] UnknownPermissionFormat(String), diff --git a/crates/tauri-utils/src/acl/schema.rs b/crates/tauri-utils/src/acl/schema.rs index bdce02ea013a..24cc07de6412 100644 --- a/crates/tauri-utils/src/acl/schema.rs +++ b/crates/tauri-utils/src/acl/schema.rs @@ -336,10 +336,10 @@ pub fn generate_permissions_schema>( let schema_str = serde_json::to_string_pretty(&schema)?; let out_dir = out_dir.as_ref().join(PERMISSION_SCHEMAS_FOLDER_NAME); - fs::create_dir_all(&out_dir).map_err(Error::CreateDir)?; + fs::create_dir_all(&out_dir).map_err(|e| Error::CreateDir(e, out_dir.clone()))?; let schema_path = out_dir.join(PERMISSION_SCHEMA_FILE_NAME); - write_if_changed(&schema_path, schema_str).map_err(Error::WriteFile)?; + write_if_changed(&schema_path, schema_str).map_err(|e| Error::WriteFile(e, schema_path))?; Ok(()) } diff --git a/crates/tauri-utils/src/config.rs b/crates/tauri-utils/src/config.rs index 3780079bf1e5..349873126393 100644 --- a/crates/tauri-utils/src/config.rs +++ b/crates/tauri-utils/src/config.rs @@ -23,6 +23,7 @@ //! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with- //! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax +use http::response::Builder; #[cfg(feature = "schema")] use schemars::JsonSchema; use semver::Version; @@ -392,6 +393,36 @@ pub struct LinuxConfig { pub rpm: RpmConfig, } +/// Compression algorithms used when bundling RPM packages. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields, tag = "type")] +#[non_exhaustive] +pub enum RpmCompression { + /// Gzip compression + Gzip { + /// Gzip compression level + level: u32, + }, + /// Zstd compression + Zstd { + /// Zstd compression level + level: i32, + }, + /// Xz compression + Xz { + /// Xz compression level + level: u32, + }, + /// Bzip2 compression + Bzip2 { + /// Bzip2 compression level + level: u32, + }, + /// Disable compression + None, +} + /// Configuration for RPM bundles. #[skip_serializing_none] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] @@ -440,6 +471,8 @@ pub struct RpmConfig { /// #[serde(alias = "post-remove-script")] pub post_remove_script: Option, + /// Compression algorithm and level. Defaults to `Gzip` with level 6. + pub compression: Option, } impl Default for RpmConfig { @@ -458,6 +491,7 @@ impl Default for RpmConfig { post_install_script: None, pre_remove_script: None, post_remove_script: None, + compression: None, } } } @@ -1242,9 +1276,8 @@ pub struct BundleConfig { pub android: AndroidConfig, } -/// a tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255. -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize, Default)] -#[cfg_attr(feature = "schema", derive(JsonSchema))] +/// A tuple struct of RGBA colors. Each value has minimum of 0 and maximum of 255. +#[derive(Debug, PartialEq, Eq, Serialize, Default, Clone, Copy)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct Color(pub u8, pub u8, pub u8, pub u8); @@ -1254,6 +1287,139 @@ impl From for (u8, u8, u8, u8) { } } +impl From for (u8, u8, u8) { + fn from(value: Color) -> Self { + (value.0, value.1, value.2) + } +} + +impl From<(u8, u8, u8, u8)> for Color { + fn from(value: (u8, u8, u8, u8)) -> Self { + Color(value.0, value.1, value.2, value.3) + } +} + +impl From<(u8, u8, u8)> for Color { + fn from(value: (u8, u8, u8)) -> Self { + Color(value.0, value.1, value.2, 255) + } +} + +impl From for [u8; 4] { + fn from(value: Color) -> Self { + [value.0, value.1, value.2, value.3] + } +} + +impl From for [u8; 3] { + fn from(value: Color) -> Self { + [value.0, value.1, value.2] + } +} + +impl From<[u8; 4]> for Color { + fn from(value: [u8; 4]) -> Self { + Color(value[0], value[1], value[2], value[3]) + } +} + +impl From<[u8; 3]> for Color { + fn from(value: [u8; 3]) -> Self { + Color(value[0], value[1], value[2], 255) + } +} + +impl FromStr for Color { + type Err = String; + fn from_str(mut color: &str) -> Result { + color = color.trim().strip_prefix('#').unwrap_or(color); + let color = match color.len() { + // TODO: use repeat_n once our MSRV is bumped to 1.82 + 3 => color.chars() + .flat_map(|c| std::iter::repeat(c).take(2)) + .chain(std::iter::repeat('f').take(2)) + .collect(), + 6 => format!("{color}FF"), + 8 => color.to_string(), + _ => return Err("Invalid hex color length, must be either 3, 6 or 8, for example: #fff, #ffffff, or #ffffffff".into()), + }; + + let r = u8::from_str_radix(&color[0..2], 16).map_err(|e| e.to_string())?; + let g = u8::from_str_radix(&color[2..4], 16).map_err(|e| e.to_string())?; + let b = u8::from_str_radix(&color[4..6], 16).map_err(|e| e.to_string())?; + let a = u8::from_str_radix(&color[6..8], 16).map_err(|e| e.to_string())?; + + Ok(Color(r, g, b, a)) + } +} + +fn default_alpha() -> u8 { + 255 +} + +#[derive(Deserialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(untagged)] +enum InnerColor { + /// Color hex string, for example: #fff, #ffffff, or #ffffffff. + String(String), + /// Array of RGB colors. Each value has minimum of 0 and maximum of 255. + Rgb((u8, u8, u8)), + /// Array of RGBA colors. Each value has minimum of 0 and maximum of 255. + Rgba((u8, u8, u8, u8)), + /// Object of red, green, blue, alpha color values. Each value has minimum of 0 and maximum of 255. + RgbaObject { + red: u8, + green: u8, + blue: u8, + #[serde(default = "default_alpha")] + alpha: u8, + }, +} + +impl<'de> Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let color = InnerColor::deserialize(deserializer)?; + let color = match color { + InnerColor::String(string) => string.parse().map_err(serde::de::Error::custom)?, + InnerColor::Rgb(rgb) => Color(rgb.0, rgb.1, rgb.2, 255), + InnerColor::Rgba(rgb) => rgb.into(), + InnerColor::RgbaObject { + red, + green, + blue, + alpha, + } => Color(red, green, blue, alpha), + }; + + Ok(color) + } +} + +#[cfg(feature = "schema")] +impl schemars::JsonSchema for Color { + fn schema_name() -> String { + "Color".to_string() + } + + fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + let mut schema = schemars::schema_for!(InnerColor).schema; + schema.metadata = None; // Remove `title: InnerColor` from schema + + // add hex color pattern validation + let any_of = schema.subschemas().any_of.as_mut().unwrap(); + let schemars::schema::Schema::Object(str_schema) = any_of.first_mut().unwrap() else { + unreachable!() + }; + str_schema.string().pattern = Some("^#?([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$".into()); + + schema.into() + } +} + /// The window effects configuration object #[skip_serializing_none] #[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)] @@ -1396,6 +1562,8 @@ pub struct WindowConfig { /// If `true`, hides the window icon from the taskbar on Windows and Linux. #[serde(default, alias = "skip-taskbar")] pub skip_taskbar: bool, + /// The name of the window class created on Windows to create the window. **Windows only**. + pub window_classname: Option, /// The initial window theme. Defaults to the system theme. Only implemented on Windows and macOS 10.14+. pub theme: Option, /// The style of the macOS title bar. @@ -1466,6 +1634,7 @@ pub struct WindowConfig { /// ## Platform-specific /// /// - **macOS**: Requires the `macos-proxy` feature flag and only compiles for macOS 14+. + #[serde(alias = "proxy-url")] pub proxy_url: Option, /// Whether page zooming by hotkeys is enabled /// @@ -1476,7 +1645,7 @@ pub struct WindowConfig { /// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission /// /// - **Android / iOS**: Unsupported. - #[serde(default)] + #[serde(default, alias = "zoom-hotkeys-enabled")] pub zoom_hotkeys_enabled: bool, /// Whether browser extensions can be installed for the webview process /// @@ -1484,8 +1653,40 @@ pub struct WindowConfig { /// /// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled) /// - **MacOS / Linux / iOS / Android** - Unsupported. - #[serde(default)] + #[serde(default, alias = "browser-extensions-enabled")] pub browser_extensions_enabled: bool, + + /// Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + /// + /// ## Note + /// + /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + /// + /// ## Warning + /// + /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data. + #[serde(default, alias = "use-https-scheme")] + pub use_https_scheme: bool, + /// Enable web inspector which is usually called browser devtools. Enabled by default. + /// + /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + /// + /// ## Platform-specific + /// + /// - macOS: This will call private functions on **macOS**. + /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + pub devtools: Option, + + /// Set the window and webview background color. + /// + /// ## Platform-specific: + /// + /// - **Windows**: alpha channel is ignored for the window layer. + /// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer. + /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored for the webview layer. + #[serde(alias = "background-color")] + pub background_color: Option, } impl Default for WindowConfig { @@ -1521,6 +1722,7 @@ impl Default for WindowConfig { visible_on_all_workspaces: false, content_protected: false, skip_taskbar: false, + window_classname: None, theme: None, title_bar_style: Default::default(), hidden_title: false, @@ -1534,6 +1736,9 @@ impl Default for WindowConfig { proxy_url: None, zoom_hotkeys_enabled: false, browser_extensions_enabled: false, + use_https_scheme: false, + devtools: None, + background_color: None, } } } @@ -1773,6 +1978,280 @@ pub struct AssetProtocolConfig { pub enable: bool, } +/// definition of a header source +/// +/// The header value to a header name +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(rename_all = "camelCase", untagged)] +pub enum HeaderSource { + /// string version of the header Value + Inline(String), + /// list version of the header value. Item are joined by "," for the real header value + List(Vec), + /// (Rust struct | Json | JavaScript Object) equivalent of the header value. Items are composed from: key + space + value. Item are then joined by ";" for the real header value + Map(HashMap), +} + +impl Display for HeaderSource { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Inline(s) => write!(f, "{s}"), + Self::List(l) => write!(f, "{}", l.join(", ")), + Self::Map(m) => { + let len = m.len(); + let mut i = 0; + for (key, value) in m { + write!(f, "{} {}", key, value)?; + i += 1; + if i != len { + write!(f, "; ")?; + } + } + Ok(()) + } + } + } +} + +/// A trait which implements on the [`Builder`] of the http create +/// +/// Must add headers defined in the tauri configuration file to http responses +pub trait HeaderAddition { + /// adds all headers defined on the config file, given the current HeaderConfig + fn add_configured_headers(self, headers: Option<&HeaderConfig>) -> http::response::Builder; +} + +impl HeaderAddition for Builder { + /// Add the headers defined in the tauri configuration file to http responses + /// + /// this is a utility function, which is used in the same way as the `.header(..)` of the rust http library + fn add_configured_headers(mut self, headers: Option<&HeaderConfig>) -> http::response::Builder { + if let Some(headers) = headers { + // Add the header Access-Control-Allow-Credentials, if we find a value for it + if let Some(value) = &headers.access_control_allow_credentials { + self = self.header("Access-Control-Allow-Credentials", value.to_string()); + }; + + // Add the header Access-Control-Allow-Headers, if we find a value for it + if let Some(value) = &headers.access_control_allow_headers { + self = self.header("Access-Control-Allow-Headers", value.to_string()); + }; + + // Add the header Access-Control-Allow-Methods, if we find a value for it + if let Some(value) = &headers.access_control_allow_methods { + self = self.header("Access-Control-Allow-Methods", value.to_string()); + }; + + // Add the header Access-Control-Expose-Headers, if we find a value for it + if let Some(value) = &headers.access_control_expose_headers { + self = self.header("Access-Control-Expose-Headers", value.to_string()); + }; + + // Add the header Access-Control-Max-Age, if we find a value for it + if let Some(value) = &headers.access_control_max_age { + self = self.header("Access-Control-Max-Age", value.to_string()); + }; + + // Add the header Cross-Origin-Embedder-Policy, if we find a value for it + if let Some(value) = &headers.cross_origin_embedder_policy { + self = self.header("Cross-Origin-Embedder-Policy", value.to_string()); + }; + + // Add the header Cross-Origin-Opener-Policy, if we find a value for it + if let Some(value) = &headers.cross_origin_opener_policy { + self = self.header("Cross-Origin-Opener-Policy", value.to_string()); + }; + + // Add the header Cross-Origin-Resource-Policy, if we find a value for it + if let Some(value) = &headers.cross_origin_resource_policy { + self = self.header("Cross-Origin-Resource-Policy", value.to_string()); + }; + + // Add the header Permission-Policy, if we find a value for it + if let Some(value) = &headers.permissions_policy { + self = self.header("Permission-Policy", value.to_string()); + }; + + // Add the header Timing-Allow-Origin, if we find a value for it + if let Some(value) = &headers.timing_allow_origin { + self = self.header("Timing-Allow-Origin", value.to_string()); + }; + + // Add the header X-Content-Type-Options, if we find a value for it + if let Some(value) = &headers.x_content_type_options { + self = self.header("X-Content-Type-Options", value.to_string()); + }; + + // Add the header Tauri-Custom-Header, if we find a value for it + if let Some(value) = &headers.tauri_custom_header { + // Keep in mind to correctly set the Access-Control-Expose-Headers + self = self.header("Tauri-Custom-Header", value.to_string()); + }; + } + self + } +} + +/// A struct, where the keys are some specific http header names. +/// If the values to those keys are defined, then they will be send as part of a response message. +/// This does not include error messages and ipc messages +/// +/// ## Example configuration +/// ```javascript +/// { +/// //.. +/// app:{ +/// //.. +/// security: { +/// headers: { +/// "Cross-Origin-Opener-Policy": "same-origin", +/// "Cross-Origin-Embedder-Policy": "require-corp", +/// "Timing-Allow-Origin": [ +/// "https://developer.mozilla.org", +/// "https://example.com", +/// ], +/// "Access-Control-Expose-Headers": "Tauri-Custom-Header", +/// "Tauri-Custom-Header": { +/// "key1": "'value1' 'value2'", +/// "key2": "'value3'" +/// } +/// }, +/// csp: "default-src 'self'; connect-src ipc: http://ipc.localhost", +/// } +/// //.. +/// } +/// //.. +/// } +/// ``` +/// In this example `Cross-Origin-Opener-Policy` and `Cross-Origin-Embedder-Policy` are set to allow for the use of [`SharedArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer). +/// The result is, that those headers are then set on every response sent via the `get_response` function in crates/tauri/src/protocol/tauri.rs. +/// The Content-Security-Policy header is defined separately, because it is also handled separately. +/// +/// For the helloworld example, this config translates into those response headers: +/// ```http +/// access-control-allow-origin: http://tauri.localhost +/// access-control-expose-headers: Tauri-Custom-Header +/// content-security-policy: default-src 'self'; connect-src ipc: http://ipc.localhost; script-src 'self' 'sha256-Wjjrs6qinmnr+tOry8x8PPwI77eGpUFR3EEGZktjJNs=' +/// content-type: text/html +/// cross-origin-embedder-policy: require-corp +/// cross-origin-opener-policy: same-origin +/// tauri-custom-header: key1 'value1' 'value2'; key2 'value3' +/// timing-allow-origin: https://developer.mozilla.org, https://example.com +/// ``` +/// Since the resulting header values are always 'string-like'. So depending on the what data type the HeaderSource is, they need to be converted. +/// - `String`(JS/Rust): stay the same for the resulting header value +/// - `Array`(JS)/`Vec\`(Rust): Item are joined by ", " for the resulting header value +/// - `Object`(JS)/ `Hashmap\`(Rust): Items are composed from: key + space + value. Item are then joined by "; " for the resulting header value +#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(JsonSchema))] +#[serde(deny_unknown_fields)] +pub struct HeaderConfig { + /// The Access-Control-Allow-Credentials response header tells browsers whether the + /// server allows cross-origin HTTP requests to include credentials. + /// + /// See + #[serde(rename = "Access-Control-Allow-Credentials")] + pub access_control_allow_credentials: Option, + /// The Access-Control-Allow-Headers response header is used in response + /// to a preflight request which includes the Access-Control-Request-Headers + /// to indicate which HTTP headers can be used during the actual request. + /// + /// This header is required if the request has an Access-Control-Request-Headers header. + /// + /// See + #[serde(rename = "Access-Control-Allow-Headers")] + pub access_control_allow_headers: Option, + /// The Access-Control-Allow-Methods response header specifies one or more methods + /// allowed when accessing a resource in response to a preflight request. + /// + /// See + #[serde(rename = "Access-Control-Allow-Methods")] + pub access_control_allow_methods: Option, + /// The Access-Control-Expose-Headers response header allows a server to indicate + /// which response headers should be made available to scripts running in the browser, + /// in response to a cross-origin request. + /// + /// See + #[serde(rename = "Access-Control-Expose-Headers")] + pub access_control_expose_headers: Option, + /// The Access-Control-Max-Age response header indicates how long the results of a + /// preflight request (that is the information contained in the + /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) can + /// be cached. + /// + /// See + #[serde(rename = "Access-Control-Max-Age")] + pub access_control_max_age: Option, + /// The HTTP Cross-Origin-Embedder-Policy (COEP) response header configures embedding + /// cross-origin resources into the document. + /// + /// See + #[serde(rename = "Cross-Origin-Embedder-Policy")] + pub cross_origin_embedder_policy: Option, + /// The HTTP Cross-Origin-Opener-Policy (COOP) response header allows you to ensure a + /// top-level document does not share a browsing context group with cross-origin documents. + /// COOP will process-isolate your document and potential attackers can't access your global + /// object if they were to open it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks. + /// + /// See + #[serde(rename = "Cross-Origin-Opener-Policy")] + pub cross_origin_opener_policy: Option, + /// The HTTP Cross-Origin-Resource-Policy response header conveys a desire that the + /// browser blocks no-cors cross-origin/cross-site requests to the given resource. + /// + /// See + #[serde(rename = "Cross-Origin-Resource-Policy")] + pub cross_origin_resource_policy: Option, + /// The HTTP Permissions-Policy header provides a mechanism to allow and deny the + /// use of browser features in a document or within any \ elements in the document. + /// + /// See + #[serde(rename = "Permissions-Policy")] + pub permissions_policy: Option, + /// The Timing-Allow-Origin response header specifies origins that are allowed to see values + /// of attributes retrieved via features of the Resource Timing API, which would otherwise be + /// reported as zero due to cross-origin restrictions. + /// + /// See + #[serde(rename = "Timing-Allow-Origin")] + pub timing_allow_origin: Option, + /// The X-Content-Type-Options response HTTP header is a marker used by the server to indicate + /// that the MIME types advertised in the Content-Type headers should be followed and not be + /// changed. The header allows you to avoid MIME type sniffing by saying that the MIME types + /// are deliberately configured. + /// + /// See + #[serde(rename = "X-Content-Type-Options")] + pub x_content_type_options: Option, + /// A custom header field Tauri-Custom-Header, don't use it. + /// Remember to set Access-Control-Expose-Headers accordingly + /// + /// **NOT INTENDED FOR PRODUCTION USE** + #[serde(rename = "Tauri-Custom-Header")] + pub tauri_custom_header: Option, +} + +impl HeaderConfig { + /// creates a new header config + pub fn new() -> Self { + HeaderConfig { + access_control_allow_credentials: None, + access_control_allow_methods: None, + access_control_allow_headers: None, + access_control_expose_headers: None, + access_control_max_age: None, + cross_origin_embedder_policy: None, + cross_origin_opener_policy: None, + cross_origin_resource_policy: None, + permissions_policy: None, + timing_allow_origin: None, + x_content_type_options: None, + tauri_custom_header: None, + } + } +} + /// Security configuration. /// /// See more: @@ -1821,6 +2300,10 @@ pub struct SecurityConfig { /// If the list is empty, all capabilities are included. #[serde(default)] pub capabilities: Vec, + /// The headers, which are added to every http response from tauri to the web view + /// This doesn't include IPC Messages and error responses + #[serde(default)] + pub headers: Option, } /// A capability entry which can be either an inlined capability or a reference to a capability defined on its own file. @@ -2493,6 +2976,7 @@ mod build { let visible_on_all_workspaces = self.visible_on_all_workspaces; let content_protected = self.content_protected; let skip_taskbar = self.skip_taskbar; + let window_classname = opt_str_lit(self.window_classname.as_ref()); let theme = opt_lit(self.theme.as_ref()); let title_bar_style = &self.title_bar_style; let hidden_title = self.hidden_title; @@ -2505,6 +2989,9 @@ mod build { let parent = opt_str_lit(self.parent.as_ref()); let zoom_hotkeys_enabled = self.zoom_hotkeys_enabled; let browser_extensions_enabled = self.browser_extensions_enabled; + let use_https_scheme = self.use_https_scheme; + let devtools = opt_lit(self.devtools.as_ref()); + let background_color = opt_lit(self.background_color.as_ref()); literal_struct!( tokens, @@ -2540,6 +3027,7 @@ mod build { visible_on_all_workspaces, content_protected, skip_taskbar, + window_classname, theme, title_bar_style, hidden_title, @@ -2551,7 +3039,10 @@ mod build { incognito, parent, zoom_hotkeys_enabled, - browser_extensions_enabled + browser_extensions_enabled, + use_https_scheme, + devtools, + background_color ); } } @@ -2774,6 +3265,62 @@ mod build { } } + impl ToTokens for HeaderSource { + fn to_tokens(&self, tokens: &mut TokenStream) { + let prefix = quote! { ::tauri::utils::config::HeaderSource }; + + tokens.append_all(match self { + Self::Inline(s) => { + let line = s.as_str(); + quote!(#prefix::Inline(#line.into())) + } + Self::List(l) => { + let list = vec_lit(l, str_lit); + quote!(#prefix::List(#list)) + } + Self::Map(m) => { + let map = map_lit(quote! { ::std::collections::HashMap }, m, str_lit, str_lit); + quote!(#prefix::Map(#map)) + } + }) + } + } + + impl ToTokens for HeaderConfig { + fn to_tokens(&self, tokens: &mut TokenStream) { + let access_control_allow_credentials = + opt_lit(self.access_control_allow_credentials.as_ref()); + let access_control_allow_headers = opt_lit(self.access_control_allow_headers.as_ref()); + let access_control_allow_methods = opt_lit(self.access_control_allow_methods.as_ref()); + let access_control_expose_headers = opt_lit(self.access_control_expose_headers.as_ref()); + let access_control_max_age = opt_lit(self.access_control_max_age.as_ref()); + let cross_origin_embedder_policy = opt_lit(self.cross_origin_embedder_policy.as_ref()); + let cross_origin_opener_policy = opt_lit(self.cross_origin_opener_policy.as_ref()); + let cross_origin_resource_policy = opt_lit(self.cross_origin_resource_policy.as_ref()); + let permissions_policy = opt_lit(self.permissions_policy.as_ref()); + let timing_allow_origin = opt_lit(self.timing_allow_origin.as_ref()); + let x_content_type_options = opt_lit(self.x_content_type_options.as_ref()); + let tauri_custom_header = opt_lit(self.tauri_custom_header.as_ref()); + + literal_struct!( + tokens, + ::tauri::utils::config::HeaderConfig, + access_control_allow_credentials, + access_control_allow_headers, + access_control_allow_methods, + access_control_expose_headers, + access_control_max_age, + cross_origin_embedder_policy, + cross_origin_opener_policy, + cross_origin_resource_policy, + permissions_policy, + timing_allow_origin, + x_content_type_options, + tauri_custom_header + ); + } + } + impl ToTokens for SecurityConfig { fn to_tokens(&self, tokens: &mut TokenStream) { let csp = opt_lit(self.csp.as_ref()); @@ -2783,6 +3330,7 @@ mod build { let asset_protocol = &self.asset_protocol; let pattern = &self.pattern; let capabilities = vec_lit(&self.capabilities, identity); + let headers = opt_lit(self.headers.as_ref()); literal_struct!( tokens, @@ -2793,7 +3341,8 @@ mod build { dangerous_disable_asset_csp_modification, asset_protocol, pattern, - capabilities + capabilities, + headers ); } } @@ -2937,6 +3486,7 @@ mod test { asset_protocol: AssetProtocolConfig::default(), pattern: Default::default(), capabilities: Vec::new(), + headers: None, }, tray_icon: None, macos_private_api: false, @@ -2986,4 +3536,15 @@ mod test { assert_eq!(d_bundle, bundle); assert_eq!(d_windows, app.windows); } + + #[test] + fn parse_hex_color() { + use super::Color; + + assert_eq!(Color(255, 255, 255, 255), "fff".parse().unwrap()); + assert_eq!(Color(255, 255, 255, 255), "#fff".parse().unwrap()); + assert_eq!(Color(0, 0, 0, 255), "#000000".parse().unwrap()); + assert_eq!(Color(0, 0, 0, 255), "#000000ff".parse().unwrap()); + assert_eq!(Color(0, 255, 0, 255), "#00ff00ff".parse().unwrap()); + } } diff --git a/crates/tauri-utils/src/platform.rs b/crates/tauri-utils/src/platform.rs index 162539c9692b..7a9c99431ed5 100644 --- a/crates/tauri-utils/src/platform.rs +++ b/crates/tauri-utils/src/platform.rs @@ -309,12 +309,12 @@ fn resource_dir_from>( #[cfg(target_os = "linux")] { - res = if curr_dir.ends_with("/data/usr/bin") { - // running from the deb bundle dir - exe_dir - .join(format!("../lib/{}", package_info.name)) - .canonicalize() - .map_err(Into::into) + // (canonicalize checks for existence, so there's no need for an extra check) + res = if let Ok(bundle_dir) = exe_dir + .join(format!("../lib/{}", package_info.name)) + .canonicalize() + { + Ok(bundle_dir) } else if let Some(appdir) = &env.appdir { let appdir: &std::path::Path = appdir.as_ref(); Ok(PathBuf::from(format!( diff --git a/crates/tauri/CHANGELOG.md b/crates/tauri/CHANGELOG.md index 28bf12801907..14dab6a14205 100644 --- a/crates/tauri/CHANGELOG.md +++ b/crates/tauri/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## \[2.1.1] + +### Bug Fixes + +- [`e8a50f6d7`](https://www.github.com/tauri-apps/tauri/commit/e8a50f6d760fad4529e7abb400302a1b487f11dd) ([#11645](https://www.github.com/tauri-apps/tauri/pull/11645)) Fix integer values of `BasDirectory.Home` and `BaseDirectory.Font` regression which broke path APIs in JS. + +## \[2.1.0] + +### New Features + +- [`fabc2f283`](https://www.github.com/tauri-apps/tauri/commit/fabc2f283e38b62c721326e44645d47138418cbc) ([#11485](https://www.github.com/tauri-apps/tauri/pull/11485) by [@39zde](https://www.github.com/tauri-apps/tauri/../../39zde)) Adds a new configuration option `app > security > headers` to define headers that will be added to every http response from tauri to the web view. This doesn't include IPC messages and error responses. +- [`8036c78e0`](https://www.github.com/tauri-apps/tauri/commit/8036c78e08715b1bc6b9fcb0c59a570eec98014f) ([#11455](https://www.github.com/tauri-apps/tauri/pull/11455) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `PathResolver::home_dir()` method on Android. +- [`5c4b83084`](https://www.github.com/tauri-apps/tauri/commit/5c4b830843ab085f8ff9db9e08d832223b027e4e) ([#11191](https://www.github.com/tauri-apps/tauri/pull/11191) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`: + + - Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`. + - Added `Size` and `Position` classes in `dpi` module. + - Implementd `SERIALIZE_TO_IPC_FN` method on `PhysicalSize`, `PhysicalPosition`, `LogicalSize` and `LogicalPosition` to convert it into a valid IPC-compatible value that can be deserialized correctly on the Rust side into its equivalent struct. +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Window::set_background_color` and `WindowBuilder::background_color`. +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview. +- [`f0da0bde8`](https://www.github.com/tauri-apps/tauri/commit/f0da0bde87a80fdca20c588cefcad86e03b9627c) ([#11439](https://www.github.com/tauri-apps/tauri/pull/11439) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Added `WebviewWindow::resolve_command_scope` to check a command scope at runtime. +- [\`\`](https://www.github.com/tauri-apps/tauri/commit/undefined) Detect if `SERIALIZE_TO_IPC_FN`, const from the JS `core` module, is implemented on objects when serializing over IPC and use it. +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > useHttpsScheme` config option to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`f37e97d41`](https://www.github.com/tauri-apps/tauri/commit/f37e97d410c4a219e99f97692da05ca9d8e0ba3a) ([#11477](https://www.github.com/tauri-apps/tauri/pull/11477) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder/WebviewBuilder::use_https_scheme` to choose whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewWindowBuilder::devtools` and `WebviewBuilder::devtools` to enable or disable devtools for a specific webview. +- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `WebviewBuilder::focused` method to choose whether to focus webview or not on creation. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `app > windows > windowClassname` config option to specify the name of the window class on Windows. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `WindowBuilder/WebviewWindowBuilder::window_classname` method to specify the name of the window class on Windows. + +### Enhancements + +- [`17c6952ae`](https://www.github.com/tauri-apps/tauri/commit/17c6952aec965fa41e6695ad68461a218afc20f1) ([#11522](https://www.github.com/tauri-apps/tauri/pull/11522) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Enhance the error message when using `async` commands with a reference. +- [`c33bbf457`](https://www.github.com/tauri-apps/tauri/commit/c33bbf45740274b6918ea6c647f366fb6008e459) ([#11575](https://www.github.com/tauri-apps/tauri/pull/11575) by [@kornelski](https://www.github.com/tauri-apps/tauri/../../kornelski)) Include the path in ACL I/O errors. + +### Bug Fixes + +- [`229d7f8e2`](https://www.github.com/tauri-apps/tauri/commit/229d7f8e220cc8d5ca06eff1ed85cb7d047c1d6c) ([#11616](https://www.github.com/tauri-apps/tauri/pull/11616) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix regression in creating child webviews on macOS and Windows, covering the whole window. +- [`8c6d1e8e6`](https://www.github.com/tauri-apps/tauri/commit/8c6d1e8e6c852667bb223b5f4823948868c26d98) ([#11401](https://www.github.com/tauri-apps/tauri/pull/11401) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `App/AppHandle/Window/Webview/WebviewWindow::cursor_position` getter method failing on Linux with `GDK may only be used from the main thread`. +- [`f8994b214`](https://www.github.com/tauri-apps/tauri/commit/f8994b214e89acc99ab5ce8dcca8485f43a62dbb) ([#11581](https://www.github.com/tauri-apps/tauri/pull/11581) by [@Mikkel-T](https://www.github.com/tauri-apps/tauri/../../Mikkel-T)) Fix listeners created with `EventTarget::AnyLabel` never receiving events. +- [`4191a7a53`](https://www.github.com/tauri-apps/tauri/commit/4191a7a53d941b179780a550638f1b4a09d17fd1) ([#11583](https://www.github.com/tauri-apps/tauri/pull/11583) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix tray events not fired for tray icons created inside an async command. +- [`129414faa`](https://www.github.com/tauri-apps/tauri/commit/129414faa4e027c9035d56614682cacc0335a6a0) ([#11569](https://www.github.com/tauri-apps/tauri/pull/11569) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix webview not focused by default. + +### Dependencies + +- Upgraded to `tauri-utils@2.1.0` +- Upgraded to `tauri-runtime@2.2.0` +- Upgraded to `tauri-runtime-wry@2.2.0` +- Upgraded to `tauri-macros@2.0.3` +- Upgraded to `tauri-build@2.0.3` + ## \[2.0.6] ### Dependencies diff --git a/crates/tauri/Cargo.toml b/crates/tauri/Cargo.toml index 254660afdb9c..5acb944033e0 100644 --- a/crates/tauri/Cargo.toml +++ b/crates/tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tauri" -version = "2.0.6" +version = "2.1.1" description = "Make tiny, secure apps for all desktop platforms with Tauri" exclude = ["/test", "/.scripts", "CHANGELOG.md", "/target"] readme = "README.md" @@ -56,13 +56,13 @@ futures-util = "0.3" uuid = { version = "1", features = ["v4"], optional = true } url = "2" anyhow = "1.0" -thiserror = "1.0" -tauri-runtime = { version = "2.1.1", path = "../tauri-runtime" } -tauri-macros = { version = "2.0.2", path = "../tauri-macros" } -tauri-utils = { version = "2.0.2", features = [ +thiserror = "2" +tauri-runtime = { version = "2.2.0", path = "../tauri-runtime" } +tauri-macros = { version = "2.0.3", path = "../tauri-macros" } +tauri-utils = { version = "2.1.0", features = [ "resources", ], path = "../tauri-utils" } -tauri-runtime-wry = { version = "2.1.2", path = "../tauri-runtime-wry", optional = true } +tauri-runtime-wry = { version = "2.2.0", path = "../tauri-runtime-wry", optional = true } getrandom = "0.2" serde_repr = "0.1" http = "1.1" @@ -134,8 +134,8 @@ swift-rs = "1.0.7" [build-dependencies] heck = "0.5" -tauri-build = { path = "../tauri-build/", default-features = false, version = "2.0.2" } -tauri-utils = { path = "../tauri-utils/", version = "2.0.2", features = [ +tauri-build = { path = "../tauri-build/", default-features = false, version = "2.0.3" } +tauri-utils = { path = "../tauri-utils/", version = "2.1.0", features = [ "build", ] } diff --git a/crates/tauri/scripts/bundle.global.js b/crates/tauri/scripts/bundle.global.js index 54f47c5c904b..54446b68ba6d 100644 --- a/crates/tauri/scripts/bundle.global.js +++ b/crates/tauri/scripts/bundle.global.js @@ -1 +1 @@ -var __TAURI_IIFE__=function(e){"use strict";function n(e,n,t,i){if("a"===t&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof n?e!==n||!i:!n.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?i:"a"===t?i.call(e):i?i.value:n.get(e)}function t(e,n,t,i,r){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof n?e!==n||!r:!n.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?r.call(e,t):r?r.value=t:n.set(e,t),t}var i,r,a,s;function l(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),r.set(this,0),a.set(this,{}),this.id=l((({message:e,id:s})=>{if(s===n(this,r,"f")){t(this,r,s+1,"f"),n(this,i,"f").call(this,e);const l=Object.keys(n(this,a,"f"));if(l.length>0){let e=s+1;for(const t of l.sort()){if(parseInt(t)!==e)break;{const r=n(this,a,"f")[t];delete n(this,a,"f")[t],n(this,i,"f").call(this,r),e+=1}}t(this,r,e,"f")}}else n(this,a,"f")[s.toString()]=e}))}set onmessage(e){t(this,i,e,"f")}get onmessage(){return n(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}i=new WeakMap,r=new WeakMap,a=new WeakMap;class u{constructor(e,n,t){this.plugin=e,this.event=n,this.channelId=t}async unregister(){return c(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function c(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}class d{get rid(){return n(this,s,"f")}constructor(e){s.set(this,void 0),t(this,s,e,"f")}async close(){return c("plugin:resources|close",{rid:this.rid})}}s=new WeakMap;var p=Object.freeze({__proto__:null,Channel:o,PluginListener:u,Resource:d,addPluginListener:async function(e,n,t){const i=new o;return i.onmessage=t,c(`plugin:${e}|registerListener`,{event:n,handler:i}).then((()=>new u(e,n,i.id)))},checkPermissions:async function(e){return c(`plugin:${e}|check_permissions`)},convertFileSrc:function(e,n="asset"){return window.__TAURI_INTERNALS__.convertFileSrc(e,n)},invoke:c,isTauri:function(){return"isTauri"in window&&!!window.isTauri},requestPermissions:async function(e){return c(`plugin:${e}|request_permissions`)},transformCallback:l});class h extends d{constructor(e){super(e)}static async new(e,n,t){return c("plugin:image|new",{rgba:w(e),width:n,height:t}).then((e=>new h(e)))}static async fromBytes(e){return c("plugin:image|from_bytes",{bytes:w(e)}).then((e=>new h(e)))}static async fromPath(e){return c("plugin:image|from_path",{path:e}).then((e=>new h(e)))}async rgba(){return c("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return c("plugin:image|size",{rid:this.rid})}}function w(e){return null==e?null:"string"==typeof e?e:e instanceof h?e.rid:e}var y=Object.freeze({__proto__:null,Image:h,transformImage:w});var g=Object.freeze({__proto__:null,defaultWindowIcon:async function(){return c("plugin:app|default_window_icon").then((e=>e?new h(e):null))},getName:async function(){return c("plugin:app|name")},getTauriVersion:async function(){return c("plugin:app|tauri_version")},getVersion:async function(){return c("plugin:app|version")},hide:async function(){return c("plugin:app|app_hide")},setTheme:async function(e){return c("plugin:app|set_app_theme",{theme:e})},show:async function(){return c("plugin:app|app_show")}});class _{constructor(e,n){this.type="Logical",this.width=e,this.height=n}toPhysical(e){return new b(this.width*e,this.height*e)}}class b{constructor(e,n){this.type="Physical",this.width=e,this.height=n}toLogical(e){return new _(this.width/e,this.height/e)}}class m{constructor(e,n){this.type="Logical",this.x=e,this.y=n}toPhysical(e){return new v(this.x*e,this.x*e)}}class v{constructor(e,n){this.type="Physical",this.x=e,this.y=n}toLogical(e){return new m(this.x/e,this.y/e)}}var f,k=Object.freeze({__proto__:null,LogicalPosition:m,LogicalSize:_,PhysicalPosition:v,PhysicalSize:b});async function A(e,n){await c("plugin:event|unlisten",{event:e,eventId:n})}async function E(e,n,t){var i;const r="string"==typeof(null==t?void 0:t.target)?{kind:"AnyLabel",label:t.target}:null!==(i=null==t?void 0:t.target)&&void 0!==i?i:{kind:"Any"};return c("plugin:event|listen",{event:e,target:r,handler:l(n)}).then((n=>async()=>A(e,n)))}async function D(e,n,t){return E(e,(t=>{A(e,t.id),n(t)}),t)}async function T(e,n){await c("plugin:event|emit",{event:e,payload:n})}async function I(e,n,t){const i="string"==typeof e?{kind:"AnyLabel",label:e}:e;await c("plugin:event|emit_to",{target:i,event:n,payload:t})}!function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(f||(f={}));var R,S,L,P,x,z=Object.freeze({__proto__:null,get TauriEvent(){return f},emit:T,emitTo:I,listen:E,once:D});function C(e){var n;if("items"in e)e.items=null===(n=e.items)||void 0===n?void 0:n.map((e=>"rid"in e?e:C(e)));else if("action"in e&&e.action){const n=new o;return n.onmessage=e.action,delete e.action,{...e,handler:n}}return e}async function W(e,n){const t=new o;if(n&&"object"==typeof n&&("action"in n&&n.action&&(t.onmessage=n.action,delete n.action),"items"in n&&n.items)){function i(e){var n;return"rid"in e?[e.rid,e.kind]:("item"in e&&"object"==typeof e.item&&(null===(n=e.item.About)||void 0===n?void 0:n.icon)&&(e.item.About.icon=w(e.item.About.icon)),"icon"in e&&e.icon&&(e.icon=w(e.icon)),"items"in e&&e.items&&(e.items=e.items.map(i)),C(e))}n.items=n.items.map(i)}return c("plugin:menu|new",{kind:e,options:n,handler:t})}class N extends d{get id(){return n(this,R,"f")}get kind(){return n(this,S,"f")}constructor(e,n,i){super(e),R.set(this,void 0),S.set(this,void 0),t(this,R,n,"f"),t(this,S,i,"f")}}R=new WeakMap,S=new WeakMap;class O extends N{constructor(e,n){super(e,n,"MenuItem")}static async new(e){return W("MenuItem",e).then((([e,n])=>new O(e,n)))}async text(){return c("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return c("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return c("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return c("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return c("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}}class F extends N{constructor(e,n){super(e,n,"Check")}static async new(e){return W("Check",e).then((([e,n])=>new F(e,n)))}async text(){return c("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return c("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return c("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return c("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return c("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async isChecked(){return c("plugin:menu|is_checked",{rid:this.rid})}async setChecked(e){return c("plugin:menu|set_checked",{rid:this.rid,checked:e})}}!function(e){e.Add="Add",e.Advanced="Advanced",e.Bluetooth="Bluetooth",e.Bookmarks="Bookmarks",e.Caution="Caution",e.ColorPanel="ColorPanel",e.ColumnView="ColumnView",e.Computer="Computer",e.EnterFullScreen="EnterFullScreen",e.Everyone="Everyone",e.ExitFullScreen="ExitFullScreen",e.FlowView="FlowView",e.Folder="Folder",e.FolderBurnable="FolderBurnable",e.FolderSmart="FolderSmart",e.FollowLinkFreestanding="FollowLinkFreestanding",e.FontPanel="FontPanel",e.GoLeft="GoLeft",e.GoRight="GoRight",e.Home="Home",e.IChatTheater="IChatTheater",e.IconView="IconView",e.Info="Info",e.InvalidDataFreestanding="InvalidDataFreestanding",e.LeftFacingTriangle="LeftFacingTriangle",e.ListView="ListView",e.LockLocked="LockLocked",e.LockUnlocked="LockUnlocked",e.MenuMixedState="MenuMixedState",e.MenuOnState="MenuOnState",e.MobileMe="MobileMe",e.MultipleDocuments="MultipleDocuments",e.Network="Network",e.Path="Path",e.PreferencesGeneral="PreferencesGeneral",e.QuickLook="QuickLook",e.RefreshFreestanding="RefreshFreestanding",e.Refresh="Refresh",e.Remove="Remove",e.RevealFreestanding="RevealFreestanding",e.RightFacingTriangle="RightFacingTriangle",e.Share="Share",e.Slideshow="Slideshow",e.SmartBadge="SmartBadge",e.StatusAvailable="StatusAvailable",e.StatusNone="StatusNone",e.StatusPartiallyAvailable="StatusPartiallyAvailable",e.StatusUnavailable="StatusUnavailable",e.StopProgressFreestanding="StopProgressFreestanding",e.StopProgress="StopProgress",e.TrashEmpty="TrashEmpty",e.TrashFull="TrashFull",e.User="User",e.UserAccounts="UserAccounts",e.UserGroup="UserGroup",e.UserGuest="UserGuest"}(L||(L={}));class M extends N{constructor(e,n){super(e,n,"Icon")}static async new(e){return W("Icon",e).then((([e,n])=>new M(e,n)))}async text(){return c("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return c("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return c("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return c("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return c("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async setIcon(e){return c("plugin:menu|set_icon",{rid:this.rid,icon:w(e)})}}class U extends N{constructor(e,n){super(e,n,"Predefined")}static async new(e){return W("Predefined",e).then((([e,n])=>new U(e,n)))}async text(){return c("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return c("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}}!function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"}(P||(P={}));class B{constructor(e){this._preventDefault=!1,this.event=e.event,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}}function j(){return new H(window.__TAURI_INTERNALS__.metadata.currentWindow.label,{skip:!0})}async function V(){return c("plugin:window|get_all_windows").then((e=>e.map((e=>new H(e,{skip:!0})))))}!function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"}(x||(x={}));const G=["tauri://created","tauri://error"];class H{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||c("plugin:window|create",{options:{...n,parent:"string"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;return null!==(n=(await V()).find((n=>n.label===e)))&&void 0!==n?n:null}static getCurrent(){return j()}static async getAll(){return V()}static async getFocusedWindow(){for(const e of await V())if(await e.isFocused())return e;return null}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:E(e,n,{target:{kind:"Window",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:D(e,n,{target:{kind:"Window",label:this.label}})}async emit(e,n){if(!G.includes(e))return T(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!G.includes(n))return I(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!G.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async scaleFactor(){return c("plugin:window|scale_factor",{label:this.label})}async innerPosition(){return c("plugin:window|inner_position",{label:this.label}).then((({x:e,y:n})=>new v(e,n)))}async outerPosition(){return c("plugin:window|outer_position",{label:this.label}).then((({x:e,y:n})=>new v(e,n)))}async innerSize(){return c("plugin:window|inner_size",{label:this.label}).then((({width:e,height:n})=>new b(e,n)))}async outerSize(){return c("plugin:window|outer_size",{label:this.label}).then((({width:e,height:n})=>new b(e,n)))}async isFullscreen(){return c("plugin:window|is_fullscreen",{label:this.label})}async isMinimized(){return c("plugin:window|is_minimized",{label:this.label})}async isMaximized(){return c("plugin:window|is_maximized",{label:this.label})}async isFocused(){return c("plugin:window|is_focused",{label:this.label})}async isDecorated(){return c("plugin:window|is_decorated",{label:this.label})}async isResizable(){return c("plugin:window|is_resizable",{label:this.label})}async isMaximizable(){return c("plugin:window|is_maximizable",{label:this.label})}async isMinimizable(){return c("plugin:window|is_minimizable",{label:this.label})}async isClosable(){return c("plugin:window|is_closable",{label:this.label})}async isVisible(){return c("plugin:window|is_visible",{label:this.label})}async title(){return c("plugin:window|title",{label:this.label})}async theme(){return c("plugin:window|theme",{label:this.label})}async center(){return c("plugin:window|center",{label:this.label})}async requestUserAttention(e){let n=null;return e&&(n=e===P.Critical?{type:"Critical"}:{type:"Informational"}),c("plugin:window|request_user_attention",{label:this.label,value:n})}async setResizable(e){return c("plugin:window|set_resizable",{label:this.label,value:e})}async setEnabled(e){return c("plugin:window|set_enabled",{label:this.label,value:e})}async isEnabled(){return c("plugin:window|is_enabled",{label:this.label})}async setMaximizable(e){return c("plugin:window|set_maximizable",{label:this.label,value:e})}async setMinimizable(e){return c("plugin:window|set_minimizable",{label:this.label,value:e})}async setClosable(e){return c("plugin:window|set_closable",{label:this.label,value:e})}async setTitle(e){return c("plugin:window|set_title",{label:this.label,value:e})}async maximize(){return c("plugin:window|maximize",{label:this.label})}async unmaximize(){return c("plugin:window|unmaximize",{label:this.label})}async toggleMaximize(){return c("plugin:window|toggle_maximize",{label:this.label})}async minimize(){return c("plugin:window|minimize",{label:this.label})}async unminimize(){return c("plugin:window|unminimize",{label:this.label})}async show(){return c("plugin:window|show",{label:this.label})}async hide(){return c("plugin:window|hide",{label:this.label})}async close(){return c("plugin:window|close",{label:this.label})}async destroy(){return c("plugin:window|destroy",{label:this.label})}async setDecorations(e){return c("plugin:window|set_decorations",{label:this.label,value:e})}async setShadow(e){return c("plugin:window|set_shadow",{label:this.label,value:e})}async setEffects(e){return c("plugin:window|set_effects",{label:this.label,value:e})}async clearEffects(){return c("plugin:window|set_effects",{label:this.label,value:null})}async setAlwaysOnTop(e){return c("plugin:window|set_always_on_top",{label:this.label,value:e})}async setAlwaysOnBottom(e){return c("plugin:window|set_always_on_bottom",{label:this.label,value:e})}async setContentProtected(e){return c("plugin:window|set_content_protected",{label:this.label,value:e})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");const n={};return n[`${e.type}`]={width:e.width,height:e.height},c("plugin:window|set_size",{label:this.label,value:n})}async setMinSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");let n=null;return e&&(n={},n[`${e.type}`]={width:e.width,height:e.height}),c("plugin:window|set_min_size",{label:this.label,value:n})}async setMaxSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");let n=null;return e&&(n={},n[`${e.type}`]={width:e.width,height:e.height}),c("plugin:window|set_max_size",{label:this.label,value:n})}async setSizeConstraints(e){function n(e){return e?{Logical:e}:null}return c("plugin:window|set_size_constraints",{label:this.label,value:{minWidth:n(null==e?void 0:e.minWidth),minHeight:n(null==e?void 0:e.minHeight),maxWidth:n(null==e?void 0:e.maxWidth),maxHeight:n(null==e?void 0:e.maxHeight)}})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");const n={};return n[`${e.type}`]={x:e.x,y:e.y},c("plugin:window|set_position",{label:this.label,value:n})}async setFullscreen(e){return c("plugin:window|set_fullscreen",{label:this.label,value:e})}async setFocus(){return c("plugin:window|set_focus",{label:this.label})}async setIcon(e){return c("plugin:window|set_icon",{label:this.label,value:w(e)})}async setSkipTaskbar(e){return c("plugin:window|set_skip_taskbar",{label:this.label,value:e})}async setCursorGrab(e){return c("plugin:window|set_cursor_grab",{label:this.label,value:e})}async setCursorVisible(e){return c("plugin:window|set_cursor_visible",{label:this.label,value:e})}async setCursorIcon(e){return c("plugin:window|set_cursor_icon",{label:this.label,value:e})}async setCursorPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");const n={};return n[`${e.type}`]={x:e.x,y:e.y},c("plugin:window|set_cursor_position",{label:this.label,value:n})}async setIgnoreCursorEvents(e){return c("plugin:window|set_ignore_cursor_events",{label:this.label,value:e})}async startDragging(){return c("plugin:window|start_dragging",{label:this.label})}async startResizeDragging(e){return c("plugin:window|start_resize_dragging",{label:this.label,value:e})}async setProgressBar(e){return c("plugin:window|set_progress_bar",{label:this.label,value:e})}async setVisibleOnAllWorkspaces(e){return c("plugin:window|set_visible_on_all_workspaces",{label:this.label,value:e})}async setTitleBarStyle(e){return c("plugin:window|set_title_bar_style",{label:this.label,value:e})}async setTheme(e){return c("plugin:window|set_theme",{label:this.label,value:e})}async onResized(e){return this.listen(f.WINDOW_RESIZED,(n=>{n.payload=J(n.payload),e(n)}))}async onMoved(e){return this.listen(f.WINDOW_MOVED,(n=>{n.payload=Z(n.payload),e(n)}))}async onCloseRequested(e){return this.listen(f.WINDOW_CLOSE_REQUESTED,(async n=>{const t=new B(n);await e(t),t.isPreventDefault()||await this.destroy()}))}async onDragDropEvent(e){const n=await this.listen(f.DRAG_ENTER,(n=>{e({...n,payload:{type:"enter",paths:n.payload.paths,position:Z(n.payload.position)}})})),t=await this.listen(f.DRAG_OVER,(n=>{e({...n,payload:{type:"over",position:Z(n.payload.position)}})})),i=await this.listen(f.DRAG_DROP,(n=>{e({...n,payload:{type:"drop",paths:n.payload.paths,position:Z(n.payload.position)}})})),r=await this.listen(f.DRAG_LEAVE,(n=>{e({...n,payload:{type:"leave"}})}));return()=>{n(),i(),t(),r()}}async onFocusChanged(e){const n=await this.listen(f.WINDOW_FOCUS,(n=>{e({...n,payload:!0})})),t=await this.listen(f.WINDOW_BLUR,(n=>{e({...n,payload:!1})}));return()=>{n(),t()}}async onScaleChanged(e){return this.listen(f.WINDOW_SCALE_FACTOR_CHANGED,e)}async onThemeChanged(e){return this.listen(f.WINDOW_THEME_CHANGED,e)}}var $,q;function Q(e){return null===e?null:{name:e.name,scaleFactor:e.scaleFactor,position:Z(e.position),size:J(e.size)}}function Z(e){return new v(e.x,e.y)}function J(e){return new b(e.width,e.height)}!function(e){e.AppearanceBased="appearanceBased",e.Light="light",e.Dark="dark",e.MediumLight="mediumLight",e.UltraDark="ultraDark",e.Titlebar="titlebar",e.Selection="selection",e.Menu="menu",e.Popover="popover",e.Sidebar="sidebar",e.HeaderView="headerView",e.Sheet="sheet",e.WindowBackground="windowBackground",e.HudWindow="hudWindow",e.FullScreenUI="fullScreenUI",e.Tooltip="tooltip",e.ContentBackground="contentBackground",e.UnderWindowBackground="underWindowBackground",e.UnderPageBackground="underPageBackground",e.Mica="mica",e.Blur="blur",e.Acrylic="acrylic",e.Tabbed="tabbed",e.TabbedDark="tabbedDark",e.TabbedLight="tabbedLight"}($||($={})),function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"}(q||(q={}));var K=Object.freeze({__proto__:null,CloseRequestedEvent:B,get Effect(){return $},get EffectState(){return q},LogicalPosition:m,LogicalSize:_,PhysicalPosition:v,PhysicalSize:b,get ProgressBarStatus(){return x},get UserAttentionType(){return P},Window:H,availableMonitors:async function(){return c("plugin:window|available_monitors").then((e=>e.map(Q)))},currentMonitor:async function(){return c("plugin:window|current_monitor").then(Q)},cursorPosition:async function(){return c("plugin:window|cursor_position").then(Z)},getAllWindows:V,getCurrentWindow:j,monitorFromPoint:async function(e,n){return c("plugin:window|monitor_from_point",{x:e,y:n}).then(Q)},primaryMonitor:async function(){return c("plugin:window|primary_monitor").then(Q)}});function Y([e,n,t]){switch(t){case"Submenu":return new X(e,n);case"Predefined":return new U(e,n);case"Check":return new F(e,n);case"Icon":return new M(e,n);default:return new O(e,n)}}class X extends N{constructor(e,n){super(e,n,"Submenu")}static async new(e){return W("Submenu",e).then((([e,n])=>new X(e,n)))}async text(){return c("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return c("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return c("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return c("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async append(e){return c("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return c("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,n){return c("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:n})}async remove(e){return c("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return c("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(Y)}async items(){return c("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(Y)))}async get(e){return c("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?Y(e):null))}async popup(e,n){var t;let i=null;return e&&(i={},i[""+(e instanceof v?"Physical":"Logical")]={x:e.x,y:e.y}),c("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:i})}async setAsWindowsMenuForNSApp(){return c("plugin:menu|set_as_windows_menu_for_nsapp",{rid:this.rid})}async setAsHelpMenuForNSApp(){return c("plugin:menu|set_as_help_menu_for_nsapp",{rid:this.rid})}}function ee([e,n,t]){switch(t){case"Submenu":return new X(e,n);case"Predefined":return new U(e,n);case"Check":return new F(e,n);case"Icon":return new M(e,n);default:return new O(e,n)}}class ne extends N{constructor(e,n){super(e,n,"Menu")}static async new(e){return W("Menu",e).then((([e,n])=>new ne(e,n)))}static async default(){return c("plugin:menu|create_default").then((([e,n])=>new ne(e,n)))}async append(e){return c("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return c("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,n){return c("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:n})}async remove(e){return c("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return c("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(ee)}async items(){return c("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(ee)))}async get(e){return c("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?ee(e):null))}async popup(e,n){var t;let i=null;return e&&(i={},i[""+(e instanceof v?"Physical":"Logical")]={x:e.x,y:e.y}),c("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:i})}async setAsAppMenu(){return c("plugin:menu|set_as_app_menu",{rid:this.rid}).then((e=>e?new ne(e[0],e[1]):null))}async setAsWindowMenu(e){var n;return c("plugin:menu|set_as_window_menu",{rid:this.rid,window:null!==(n=null==e?void 0:e.label)&&void 0!==n?n:null}).then((e=>e?new ne(e[0],e[1]):null))}}var te=Object.freeze({__proto__:null,CheckMenuItem:F,IconMenuItem:M,Menu:ne,MenuItem:O,get NativeIcon(){return L},PredefinedMenuItem:U,Submenu:X});function ie(){var e;window.__TAURI_INTERNALS__=null!==(e=window.__TAURI_INTERNALS__)&&void 0!==e?e:{}}var re,ae=Object.freeze({__proto__:null,clearMocks:function(){var e,n,t;"object"==typeof window.__TAURI_INTERNALS__&&((null===(e=window.__TAURI_INTERNALS__)||void 0===e?void 0:e.convertFileSrc)&&delete window.__TAURI_INTERNALS__.convertFileSrc,(null===(n=window.__TAURI_INTERNALS__)||void 0===n?void 0:n.invoke)&&delete window.__TAURI_INTERNALS__.invoke,(null===(t=window.__TAURI_INTERNALS__)||void 0===t?void 0:t.metadata)&&delete window.__TAURI_INTERNALS__.metadata)},mockConvertFileSrc:function(e){ie(),window.__TAURI_INTERNALS__.convertFileSrc=function(n,t="asset"){const i=encodeURIComponent(n);return"windows"===e?`http://${t}.localhost/${i}`:`${t}://localhost/${i}`}},mockIPC:function(e){ie(),window.__TAURI_INTERNALS__.transformCallback=function(e,n=!1){const t=window.crypto.getRandomValues(new Uint32Array(1))[0],i=`_${t}`;return Object.defineProperty(window,i,{value:t=>(n&&Reflect.deleteProperty(window,i),e&&e(t)),writable:!1,configurable:!0}),t},window.__TAURI_INTERNALS__.invoke=function(n,t,i){return e(n,t)}},mockWindows:function(e,...n){ie(),window.__TAURI_INTERNALS__.metadata={currentWindow:{label:e},currentWebview:{windowLabel:e,label:e}}}});!function(e){e[e.Audio=1]="Audio",e[e.Cache=2]="Cache",e[e.Config=3]="Config",e[e.Data=4]="Data",e[e.LocalData=5]="LocalData",e[e.Document=6]="Document",e[e.Download=7]="Download",e[e.Picture=8]="Picture",e[e.Public=9]="Public",e[e.Video=10]="Video",e[e.Resource=11]="Resource",e[e.Temp=12]="Temp",e[e.AppConfig=13]="AppConfig",e[e.AppData=14]="AppData",e[e.AppLocalData=15]="AppLocalData",e[e.AppCache=16]="AppCache",e[e.AppLog=17]="AppLog",e[e.Desktop=18]="Desktop",e[e.Executable=19]="Executable",e[e.Font=20]="Font",e[e.Home=21]="Home",e[e.Runtime=22]="Runtime",e[e.Template=23]="Template"}(re||(re={}));var se=Object.freeze({__proto__:null,get BaseDirectory(){return re},appCacheDir:async function(){return c("plugin:path|resolve_directory",{directory:re.AppCache})},appConfigDir:async function(){return c("plugin:path|resolve_directory",{directory:re.AppConfig})},appDataDir:async function(){return c("plugin:path|resolve_directory",{directory:re.AppData})},appLocalDataDir:async function(){return c("plugin:path|resolve_directory",{directory:re.AppLocalData})},appLogDir:async function(){return c("plugin:path|resolve_directory",{directory:re.AppLog})},audioDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Audio})},basename:async function(e,n){return c("plugin:path|basename",{path:e,ext:n})},cacheDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Cache})},configDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Config})},dataDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Data})},delimiter:function(){return window.__TAURI_INTERNALS__.plugins.path.delimiter},desktopDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Desktop})},dirname:async function(e){return c("plugin:path|dirname",{path:e})},documentDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Document})},downloadDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Download})},executableDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Executable})},extname:async function(e){return c("plugin:path|extname",{path:e})},fontDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Font})},homeDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Home})},isAbsolute:async function(e){return c("plugin:path|isAbsolute",{path:e})},join:async function(...e){return c("plugin:path|join",{paths:e})},localDataDir:async function(){return c("plugin:path|resolve_directory",{directory:re.LocalData})},normalize:async function(e){return c("plugin:path|normalize",{path:e})},pictureDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Picture})},publicDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Public})},resolve:async function(...e){return c("plugin:path|resolve",{paths:e})},resolveResource:async function(e){return c("plugin:path|resolve_directory",{directory:re.Resource,path:e})},resourceDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Resource})},runtimeDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Runtime})},sep:function(){return window.__TAURI_INTERNALS__.plugins.path.sep},tempDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Temp})},templateDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Template})},videoDir:async function(){return c("plugin:path|resolve_directory",{directory:re.Video})}});class le extends d{constructor(e,n){super(e),this.id=n}static async getById(e){return c("plugin:tray|get_by_id",{id:e}).then((n=>n?new le(n,e):null))}static async removeById(e){return c("plugin:tray|remove_by_id",{id:e})}static async new(e){(null==e?void 0:e.menu)&&(e.menu=[e.menu.rid,e.menu.kind]),(null==e?void 0:e.icon)&&(e.icon=w(e.icon));const n=new o;if(null==e?void 0:e.action){const t=e.action;n.onmessage=e=>t(function(e){const n=e;return n.position=new v(e.position.x,e.position.y),n.rect.position=new v(e.rect.position.Physical.x,e.rect.position.Physical.y),n.rect.size=new b(e.rect.size.Physical.width,e.rect.size.Physical.height),n}(e)),delete e.action}return c("plugin:tray|new",{options:null!=e?e:{},handler:n}).then((([e,n])=>new le(e,n)))}async setIcon(e){let n=null;return e&&(n=w(e)),c("plugin:tray|set_icon",{rid:this.rid,icon:n})}async setMenu(e){return e&&(e=[e.rid,e.kind]),c("plugin:tray|set_menu",{rid:this.rid,menu:e})}async setTooltip(e){return c("plugin:tray|set_tooltip",{rid:this.rid,tooltip:e})}async setTitle(e){return c("plugin:tray|set_title",{rid:this.rid,title:e})}async setVisible(e){return c("plugin:tray|set_visible",{rid:this.rid,visible:e})}async setTempDirPath(e){return c("plugin:tray|set_temp_dir_path",{rid:this.rid,path:e})}async setIconAsTemplate(e){return c("plugin:tray|set_icon_as_template",{rid:this.rid,asTemplate:e})}async setMenuOnLeftClick(e){return c("plugin:tray|set_show_menu_on_left_click",{rid:this.rid,onLeft:e})}}var oe=Object.freeze({__proto__:null,TrayIcon:le});function ue(){return new pe(j(),window.__TAURI_INTERNALS__.metadata.currentWebview.label,{skip:!0})}async function ce(){return c("plugin:webview|get_all_webviews").then((e=>e.map((e=>new pe(new H(e.windowLabel,{skip:!0}),e.label,{skip:!0})))))}const de=["tauri://created","tauri://error"];class pe{constructor(e,n,t){this.window=e,this.label=n,this.listeners=Object.create(null),(null==t?void 0:t.skip)||c("plugin:webview|create_webview",{windowLabel:e.label,label:n,options:t}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;return null!==(n=(await ce()).find((n=>n.label===e)))&&void 0!==n?n:null}static getCurrent(){return ue()}static async getAll(){return ce()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:E(e,n,{target:{kind:"Webview",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:D(e,n,{target:{kind:"Webview",label:this.label}})}async emit(e,n){if(!de.includes(e))return T(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!de.includes(n))return I(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!de.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async position(){return c("plugin:webview|webview_position",{label:this.label}).then((({x:e,y:n})=>new v(e,n)))}async size(){return c("plugin:webview|webview_size",{label:this.label}).then((({width:e,height:n})=>new b(e,n)))}async close(){return c("plugin:webview|close",{label:this.label})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");const n={};return n[`${e.type}`]={width:e.width,height:e.height},c("plugin:webview|set_webview_size",{label:this.label,value:n})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");const n={};return n[`${e.type}`]={x:e.x,y:e.y},c("plugin:webview|set_webview_position",{label:this.label,value:n})}async setFocus(){return c("plugin:webview|set_webview_focus",{label:this.label})}async hide(){return c("plugin:webview|webview_hide",{label:this.label})}async show(){return c("plugin:webview|webview_show",{label:this.label})}async setZoom(e){return c("plugin:webview|set_webview_zoom",{label:this.label,value:e})}async reparent(e){return c("plugin:webview|reparent",{label:this.label,window:"string"==typeof e?e:e.label})}async clearAllBrowsingData(){return c("plugin:webview|clear_all_browsing_data")}async onDragDropEvent(e){const n=await this.listen(f.DRAG_ENTER,(n=>{e({...n,payload:{type:"enter",paths:n.payload.paths,position:he(n.payload.position)}})})),t=await this.listen(f.DRAG_OVER,(n=>{e({...n,payload:{type:"over",position:he(n.payload.position)}})})),i=await this.listen(f.DRAG_DROP,(n=>{e({...n,payload:{type:"drop",paths:n.payload.paths,position:he(n.payload.position)}})})),r=await this.listen(f.DRAG_LEAVE,(n=>{e({...n,payload:{type:"leave"}})}));return()=>{n(),i(),t(),r()}}}function he(e){return new v(e.x,e.y)}var we,ye,ge=Object.freeze({__proto__:null,Webview:pe,getAllWebviews:ce,getCurrentWebview:ue});function _e(){const e=ue();return new me(e.label,{skip:!0})}async function be(){return c("plugin:window|get_all_windows").then((e=>e.map((e=>new me(e,{skip:!0})))))}class me{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||c("plugin:webview|create_webview_window",{options:{...n,parent:"string"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;const t=null!==(n=(await be()).find((n=>n.label===e)))&&void 0!==n?n:null;return t?new me(t.label,{skip:!0}):null}static getCurrent(){return _e()}static async getAll(){return be()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:E(e,n,{target:{kind:"WebviewWindow",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:D(e,n,{target:{kind:"WebviewWindow",label:this.label}})}}we=me,ye=[H,pe],(Array.isArray(ye)?ye:[ye]).forEach((e=>{Object.getOwnPropertyNames(e.prototype).forEach((n=>{var t;"object"==typeof we.prototype&&we.prototype&&n in we.prototype||Object.defineProperty(we.prototype,n,null!==(t=Object.getOwnPropertyDescriptor(e.prototype,n))&&void 0!==t?t:Object.create(null))}))}));var ve=Object.freeze({__proto__:null,WebviewWindow:me,getAllWebviewWindows:be,getCurrentWebviewWindow:_e});return e.app=g,e.core=p,e.dpi=k,e.event=z,e.image=y,e.menu=te,e.mocks=ae,e.path=se,e.tray=oe,e.webview=ge,e.webviewWindow=ve,e.window=K,e}({});window.__TAURI__=__TAURI_IIFE__; +var __TAURI_IIFE__=function(e){"use strict";function n(e,n,t,i){if("a"===t&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof n?e!==n||!i:!n.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?i:"a"===t?i.call(e):i?i.value:n.get(e)}function t(e,n,t,i,r){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof n?e!==n||!r:!n.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?r.call(e,t):r?r.value=t:n.set(e,t),t}var i,r,s,a;"function"==typeof SuppressedError&&SuppressedError;const l="__TAURI_TO_IPC_KEY__";function o(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}class u{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),r.set(this,0),s.set(this,{}),this.id=o((({message:e,id:a})=>{if(a===n(this,r,"f")){t(this,r,a+1,"f"),n(this,i,"f").call(this,e);const l=Object.keys(n(this,s,"f"));if(l.length>0){let e=a+1;for(const t of l.sort()){if(parseInt(t)!==e)break;{const r=n(this,s,"f")[t];delete n(this,s,"f")[t],n(this,i,"f").call(this,r),e+=1}}t(this,r,e,"f")}}else n(this,s,"f")[a.toString()]=e}))}set onmessage(e){t(this,i,e,"f")}get onmessage(){return n(this,i,"f")}[(i=new WeakMap,r=new WeakMap,s=new WeakMap,l)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[l]()}}class c{constructor(e,n,t){this.plugin=e,this.event=n,this.channelId=t}async unregister(){return d(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function d(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}class h{get rid(){return n(this,a,"f")}constructor(e){a.set(this,void 0),t(this,a,e,"f")}async close(){return d("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;var p=Object.freeze({__proto__:null,Channel:u,PluginListener:c,Resource:h,SERIALIZE_TO_IPC_FN:l,addPluginListener:async function(e,n,t){const i=new u;return i.onmessage=t,d(`plugin:${e}|registerListener`,{event:n,handler:i}).then((()=>new c(e,n,i.id)))},checkPermissions:async function(e){return d(`plugin:${e}|check_permissions`)},convertFileSrc:function(e,n="asset"){return window.__TAURI_INTERNALS__.convertFileSrc(e,n)},invoke:d,isTauri:function(){return"isTauri"in window&&!!window.isTauri},requestPermissions:async function(e){return d(`plugin:${e}|request_permissions`)},transformCallback:o});class w extends h{constructor(e){super(e)}static async new(e,n,t){return d("plugin:image|new",{rgba:y(e),width:n,height:t}).then((e=>new w(e)))}static async fromBytes(e){return d("plugin:image|from_bytes",{bytes:y(e)}).then((e=>new w(e)))}static async fromPath(e){return d("plugin:image|from_path",{path:e}).then((e=>new w(e)))}async rgba(){return d("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return d("plugin:image|size",{rid:this.rid})}}function y(e){return null==e?null:"string"==typeof e?e:e instanceof w?e.rid:e}var _=Object.freeze({__proto__:null,Image:w,transformImage:y});var g=Object.freeze({__proto__:null,defaultWindowIcon:async function(){return d("plugin:app|default_window_icon").then((e=>e?new w(e):null))},getName:async function(){return d("plugin:app|name")},getTauriVersion:async function(){return d("plugin:app|tauri_version")},getVersion:async function(){return d("plugin:app|version")},hide:async function(){return d("plugin:app|app_hide")},setTheme:async function(e){return d("plugin:app|set_app_theme",{theme:e})},show:async function(){return d("plugin:app|app_show")}});class b{constructor(...e){this.type="Logical",1===e.length?"Logical"in e[0]?(this.width=e[0].Logical.width,this.height=e[0].Logical.height):(this.width=e[0].width,this.height=e[0].height):(this.width=e[0],this.height=e[1])}toPhysical(e){return new m(this.width*e,this.height*e)}[l](){return{width:this.width,height:this.height}}toJSON(){return this[l]()}}class m{constructor(...e){this.type="Physical",1===e.length?"Physical"in e[0]?(this.width=e[0].Physical.width,this.height=e[0].Physical.height):(this.width=e[0].width,this.height=e[0].height):(this.width=e[0],this.height=e[1])}toLogical(e){return new b(this.width/e,this.height/e)}[l](){return{width:this.width,height:this.height}}toJSON(){return this[l]()}}class v{constructor(e){this.size=e}toLogical(e){return this.size instanceof b?this.size:this.size.toLogical(e)}toPhysical(e){return this.size instanceof m?this.size:this.size.toPhysical(e)}[l](){return{[`${this.size.type}`]:{width:this.size.width,height:this.size.height}}}toJSON(){return this[l]()}}class f{constructor(...e){this.type="Logical",1===e.length?"Logical"in e[0]?(this.x=e[0].Logical.x,this.y=e[0].Logical.y):(this.x=e[0].x,this.y=e[0].y):(this.x=e[0],this.y=e[1])}toPhysical(e){return new k(this.x*e,this.y*e)}[l](){return{x:this.x,y:this.y}}toJSON(){return this[l]()}}class k{constructor(...e){this.type="Physical",1===e.length?"Physical"in e[0]?(this.x=e[0].Physical.x,this.y=e[0].Physical.y):(this.x=e[0].x,this.y=e[0].y):(this.x=e[0],this.y=e[1])}toLogical(e){return new f(this.x/e,this.y/e)}[l](){return{x:this.x,y:this.y}}toJSON(){return this[l]()}}class A{constructor(e){this.position=e}toLogical(e){return this.position instanceof f?this.position:this.position.toLogical(e)}toPhysical(e){return this.position instanceof k?this.position:this.position.toPhysical(e)}[l](){return{[`${this.position.type}`]:{x:this.position.x,y:this.position.y}}}toJSON(){return this[l]()}}var E,T=Object.freeze({__proto__:null,LogicalPosition:f,LogicalSize:b,PhysicalPosition:k,PhysicalSize:m,Position:A,Size:v});async function D(e,n){await d("plugin:event|unlisten",{event:e,eventId:n})}async function I(e,n,t){var i;const r="string"==typeof(null==t?void 0:t.target)?{kind:"AnyLabel",label:t.target}:null!==(i=null==t?void 0:t.target)&&void 0!==i?i:{kind:"Any"};return d("plugin:event|listen",{event:e,target:r,handler:o(n)}).then((n=>async()=>D(e,n)))}async function R(e,n,t){return I(e,(t=>{D(e,t.id),n(t)}),t)}async function S(e,n){await d("plugin:event|emit",{event:e,payload:n})}async function L(e,n,t){const i="string"==typeof e?{kind:"AnyLabel",label:e}:e;await d("plugin:event|emit_to",{target:i,event:n,payload:t})}!function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(E||(E={}));var x,C,N,P=Object.freeze({__proto__:null,get TauriEvent(){return E},emit:S,emitTo:L,listen:I,once:R});function z(e){var n;if("items"in e)e.items=null===(n=e.items)||void 0===n?void 0:n.map((e=>"rid"in e?e:z(e)));else if("action"in e&&e.action){const n=new u;return n.onmessage=e.action,delete e.action,{...e,handler:n}}return e}async function W(e,n){const t=new u;if(n&&"object"==typeof n&&("action"in n&&n.action&&(t.onmessage=n.action,delete n.action),"items"in n&&n.items)){function i(e){var n;return"rid"in e?[e.rid,e.kind]:("item"in e&&"object"==typeof e.item&&(null===(n=e.item.About)||void 0===n?void 0:n.icon)&&(e.item.About.icon=y(e.item.About.icon)),"icon"in e&&e.icon&&(e.icon=y(e.icon)),"items"in e&&e.items&&(e.items=e.items.map(i)),z(e))}n.items=n.items.map(i)}return d("plugin:menu|new",{kind:e,options:n,handler:t})}class O extends h{get id(){return n(this,x,"f")}get kind(){return n(this,C,"f")}constructor(e,n,i){super(e),x.set(this,void 0),C.set(this,void 0),t(this,x,n,"f"),t(this,C,i,"f")}}x=new WeakMap,C=new WeakMap;class F extends O{constructor(e,n){super(e,n,"MenuItem")}static async new(e){return W("MenuItem",e).then((([e,n])=>new F(e,n)))}async text(){return d("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return d("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return d("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return d("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return d("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}}class M extends O{constructor(e,n){super(e,n,"Check")}static async new(e){return W("Check",e).then((([e,n])=>new M(e,n)))}async text(){return d("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return d("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return d("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return d("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return d("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async isChecked(){return d("plugin:menu|is_checked",{rid:this.rid})}async setChecked(e){return d("plugin:menu|set_checked",{rid:this.rid,checked:e})}}!function(e){e.Add="Add",e.Advanced="Advanced",e.Bluetooth="Bluetooth",e.Bookmarks="Bookmarks",e.Caution="Caution",e.ColorPanel="ColorPanel",e.ColumnView="ColumnView",e.Computer="Computer",e.EnterFullScreen="EnterFullScreen",e.Everyone="Everyone",e.ExitFullScreen="ExitFullScreen",e.FlowView="FlowView",e.Folder="Folder",e.FolderBurnable="FolderBurnable",e.FolderSmart="FolderSmart",e.FollowLinkFreestanding="FollowLinkFreestanding",e.FontPanel="FontPanel",e.GoLeft="GoLeft",e.GoRight="GoRight",e.Home="Home",e.IChatTheater="IChatTheater",e.IconView="IconView",e.Info="Info",e.InvalidDataFreestanding="InvalidDataFreestanding",e.LeftFacingTriangle="LeftFacingTriangle",e.ListView="ListView",e.LockLocked="LockLocked",e.LockUnlocked="LockUnlocked",e.MenuMixedState="MenuMixedState",e.MenuOnState="MenuOnState",e.MobileMe="MobileMe",e.MultipleDocuments="MultipleDocuments",e.Network="Network",e.Path="Path",e.PreferencesGeneral="PreferencesGeneral",e.QuickLook="QuickLook",e.RefreshFreestanding="RefreshFreestanding",e.Refresh="Refresh",e.Remove="Remove",e.RevealFreestanding="RevealFreestanding",e.RightFacingTriangle="RightFacingTriangle",e.Share="Share",e.Slideshow="Slideshow",e.SmartBadge="SmartBadge",e.StatusAvailable="StatusAvailable",e.StatusNone="StatusNone",e.StatusPartiallyAvailable="StatusPartiallyAvailable",e.StatusUnavailable="StatusUnavailable",e.StopProgressFreestanding="StopProgressFreestanding",e.StopProgress="StopProgress",e.TrashEmpty="TrashEmpty",e.TrashFull="TrashFull",e.User="User",e.UserAccounts="UserAccounts",e.UserGroup="UserGroup",e.UserGuest="UserGuest"}(N||(N={}));class U extends O{constructor(e,n){super(e,n,"Icon")}static async new(e){return W("Icon",e).then((([e,n])=>new U(e,n)))}async text(){return d("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return d("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return d("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return d("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return d("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async setIcon(e){return d("plugin:menu|set_icon",{rid:this.rid,icon:y(e)})}}class B extends O{constructor(e,n){super(e,n,"Predefined")}static async new(e){return W("Predefined",e).then((([e,n])=>new B(e,n)))}async text(){return d("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return d("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}}function j([e,n,t]){switch(t){case"Submenu":return new V(e,n);case"Predefined":return new B(e,n);case"Check":return new M(e,n);case"Icon":return new U(e,n);default:return new F(e,n)}}class V extends O{constructor(e,n){super(e,n,"Submenu")}static async new(e){return W("Submenu",e).then((([e,n])=>new V(e,n)))}async text(){return d("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return d("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return d("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return d("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async append(e){return d("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return d("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,n){return d("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:n})}async remove(e){return d("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return d("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(j)}async items(){return d("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(j)))}async get(e){return d("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?j(e):null))}async popup(e,n){var t;return d("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:e instanceof A?e:e?new A(e):null})}async setAsWindowsMenuForNSApp(){return d("plugin:menu|set_as_windows_menu_for_nsapp",{rid:this.rid})}async setAsHelpMenuForNSApp(){return d("plugin:menu|set_as_help_menu_for_nsapp",{rid:this.rid})}}function G([e,n,t]){switch(t){case"Submenu":return new V(e,n);case"Predefined":return new B(e,n);case"Check":return new M(e,n);case"Icon":return new U(e,n);default:return new F(e,n)}}class H extends O{constructor(e,n){super(e,n,"Menu")}static async new(e){return W("Menu",e).then((([e,n])=>new H(e,n)))}static async default(){return d("plugin:menu|create_default").then((([e,n])=>new H(e,n)))}async append(e){return d("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return d("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,n){return d("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:n})}async remove(e){return d("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return d("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(G)}async items(){return d("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(G)))}async get(e){return d("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?G(e):null))}async popup(e,n){var t;return d("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:null!==(t=null==n?void 0:n.label)&&void 0!==t?t:null,at:e instanceof A?e:e?new A(e):null})}async setAsAppMenu(){return d("plugin:menu|set_as_app_menu",{rid:this.rid}).then((e=>e?new H(e[0],e[1]):null))}async setAsWindowMenu(e){var n;return d("plugin:menu|set_as_window_menu",{rid:this.rid,window:null!==(n=null==e?void 0:e.label)&&void 0!==n?n:null}).then((e=>e?new H(e[0],e[1]):null))}}var $=Object.freeze({__proto__:null,CheckMenuItem:M,IconMenuItem:U,Menu:H,MenuItem:F,get NativeIcon(){return N},PredefinedMenuItem:B,Submenu:V});function q(){var e;window.__TAURI_INTERNALS__=null!==(e=window.__TAURI_INTERNALS__)&&void 0!==e?e:{}}var J,Q=Object.freeze({__proto__:null,clearMocks:function(){var e,n,t;"object"==typeof window.__TAURI_INTERNALS__&&((null===(e=window.__TAURI_INTERNALS__)||void 0===e?void 0:e.convertFileSrc)&&delete window.__TAURI_INTERNALS__.convertFileSrc,(null===(n=window.__TAURI_INTERNALS__)||void 0===n?void 0:n.invoke)&&delete window.__TAURI_INTERNALS__.invoke,(null===(t=window.__TAURI_INTERNALS__)||void 0===t?void 0:t.metadata)&&delete window.__TAURI_INTERNALS__.metadata)},mockConvertFileSrc:function(e){q(),window.__TAURI_INTERNALS__.convertFileSrc=function(n,t="asset"){const i=encodeURIComponent(n);return"windows"===e?`http://${t}.localhost/${i}`:`${t}://localhost/${i}`}},mockIPC:function(e){q(),window.__TAURI_INTERNALS__.transformCallback=function(e,n=!1){const t=window.crypto.getRandomValues(new Uint32Array(1))[0],i=`_${t}`;return Object.defineProperty(window,i,{value:t=>(n&&Reflect.deleteProperty(window,i),e&&e(t)),writable:!1,configurable:!0}),t},window.__TAURI_INTERNALS__.invoke=function(n,t,i){return e(n,t)}},mockWindows:function(e,...n){q(),window.__TAURI_INTERNALS__.metadata={currentWindow:{label:e},currentWebview:{windowLabel:e,label:e}}}});!function(e){e[e.Audio=1]="Audio",e[e.Cache=2]="Cache",e[e.Config=3]="Config",e[e.Data=4]="Data",e[e.LocalData=5]="LocalData",e[e.Document=6]="Document",e[e.Download=7]="Download",e[e.Picture=8]="Picture",e[e.Public=9]="Public",e[e.Video=10]="Video",e[e.Resource=11]="Resource",e[e.Temp=12]="Temp",e[e.AppConfig=13]="AppConfig",e[e.AppData=14]="AppData",e[e.AppLocalData=15]="AppLocalData",e[e.AppCache=16]="AppCache",e[e.AppLog=17]="AppLog",e[e.Desktop=18]="Desktop",e[e.Executable=19]="Executable",e[e.Font=20]="Font",e[e.Home=21]="Home",e[e.Runtime=22]="Runtime",e[e.Template=23]="Template"}(J||(J={}));var Z=Object.freeze({__proto__:null,get BaseDirectory(){return J},appCacheDir:async function(){return d("plugin:path|resolve_directory",{directory:J.AppCache})},appConfigDir:async function(){return d("plugin:path|resolve_directory",{directory:J.AppConfig})},appDataDir:async function(){return d("plugin:path|resolve_directory",{directory:J.AppData})},appLocalDataDir:async function(){return d("plugin:path|resolve_directory",{directory:J.AppLocalData})},appLogDir:async function(){return d("plugin:path|resolve_directory",{directory:J.AppLog})},audioDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Audio})},basename:async function(e,n){return d("plugin:path|basename",{path:e,ext:n})},cacheDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Cache})},configDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Config})},dataDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Data})},delimiter:function(){return window.__TAURI_INTERNALS__.plugins.path.delimiter},desktopDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Desktop})},dirname:async function(e){return d("plugin:path|dirname",{path:e})},documentDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Document})},downloadDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Download})},executableDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Executable})},extname:async function(e){return d("plugin:path|extname",{path:e})},fontDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Font})},homeDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Home})},isAbsolute:async function(e){return d("plugin:path|isAbsolute",{path:e})},join:async function(...e){return d("plugin:path|join",{paths:e})},localDataDir:async function(){return d("plugin:path|resolve_directory",{directory:J.LocalData})},normalize:async function(e){return d("plugin:path|normalize",{path:e})},pictureDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Picture})},publicDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Public})},resolve:async function(...e){return d("plugin:path|resolve",{paths:e})},resolveResource:async function(e){return d("plugin:path|resolve_directory",{directory:J.Resource,path:e})},resourceDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Resource})},runtimeDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Runtime})},sep:function(){return window.__TAURI_INTERNALS__.plugins.path.sep},tempDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Temp})},templateDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Template})},videoDir:async function(){return d("plugin:path|resolve_directory",{directory:J.Video})}});class K extends h{constructor(e,n){super(e),this.id=n}static async getById(e){return d("plugin:tray|get_by_id",{id:e}).then((n=>n?new K(n,e):null))}static async removeById(e){return d("plugin:tray|remove_by_id",{id:e})}static async new(e){(null==e?void 0:e.menu)&&(e.menu=[e.menu.rid,e.menu.kind]),(null==e?void 0:e.icon)&&(e.icon=y(e.icon));const n=new u;if(null==e?void 0:e.action){const t=e.action;n.onmessage=e=>t(function(e){const n=e;return n.position=new k(e.position),n.rect.position=new k(e.rect.position),n.rect.size=new m(e.rect.size),n}(e)),delete e.action}return d("plugin:tray|new",{options:null!=e?e:{},handler:n}).then((([e,n])=>new K(e,n)))}async setIcon(e){let n=null;return e&&(n=y(e)),d("plugin:tray|set_icon",{rid:this.rid,icon:n})}async setMenu(e){return e&&(e=[e.rid,e.kind]),d("plugin:tray|set_menu",{rid:this.rid,menu:e})}async setTooltip(e){return d("plugin:tray|set_tooltip",{rid:this.rid,tooltip:e})}async setTitle(e){return d("plugin:tray|set_title",{rid:this.rid,title:e})}async setVisible(e){return d("plugin:tray|set_visible",{rid:this.rid,visible:e})}async setTempDirPath(e){return d("plugin:tray|set_temp_dir_path",{rid:this.rid,path:e})}async setIconAsTemplate(e){return d("plugin:tray|set_icon_as_template",{rid:this.rid,asTemplate:e})}async setMenuOnLeftClick(e){return d("plugin:tray|set_show_menu_on_left_click",{rid:this.rid,onLeft:e})}}var Y,X,ee=Object.freeze({__proto__:null,TrayIcon:K});!function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"}(Y||(Y={}));class ne{constructor(e){this._preventDefault=!1,this.event=e.event,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}}function te(){return new se(window.__TAURI_INTERNALS__.metadata.currentWindow.label,{skip:!0})}async function ie(){return d("plugin:window|get_all_windows").then((e=>e.map((e=>new se(e,{skip:!0})))))}!function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"}(X||(X={}));const re=["tauri://created","tauri://error"];class se{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||d("plugin:window|create",{options:{...n,parent:"string"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;return null!==(n=(await ie()).find((n=>n.label===e)))&&void 0!==n?n:null}static getCurrent(){return te()}static async getAll(){return ie()}static async getFocusedWindow(){for(const e of await ie())if(await e.isFocused())return e;return null}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:I(e,n,{target:{kind:"Window",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:R(e,n,{target:{kind:"Window",label:this.label}})}async emit(e,n){if(!re.includes(e))return S(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!re.includes(n))return L(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!re.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async scaleFactor(){return d("plugin:window|scale_factor",{label:this.label})}async innerPosition(){return d("plugin:window|inner_position",{label:this.label}).then((e=>new k(e)))}async outerPosition(){return d("plugin:window|outer_position",{label:this.label}).then((e=>new k(e)))}async innerSize(){return d("plugin:window|inner_size",{label:this.label}).then((e=>new m(e)))}async outerSize(){return d("plugin:window|outer_size",{label:this.label}).then((e=>new m(e)))}async isFullscreen(){return d("plugin:window|is_fullscreen",{label:this.label})}async isMinimized(){return d("plugin:window|is_minimized",{label:this.label})}async isMaximized(){return d("plugin:window|is_maximized",{label:this.label})}async isFocused(){return d("plugin:window|is_focused",{label:this.label})}async isDecorated(){return d("plugin:window|is_decorated",{label:this.label})}async isResizable(){return d("plugin:window|is_resizable",{label:this.label})}async isMaximizable(){return d("plugin:window|is_maximizable",{label:this.label})}async isMinimizable(){return d("plugin:window|is_minimizable",{label:this.label})}async isClosable(){return d("plugin:window|is_closable",{label:this.label})}async isVisible(){return d("plugin:window|is_visible",{label:this.label})}async title(){return d("plugin:window|title",{label:this.label})}async theme(){return d("plugin:window|theme",{label:this.label})}async center(){return d("plugin:window|center",{label:this.label})}async requestUserAttention(e){let n=null;return e&&(n=e===Y.Critical?{type:"Critical"}:{type:"Informational"}),d("plugin:window|request_user_attention",{label:this.label,value:n})}async setResizable(e){return d("plugin:window|set_resizable",{label:this.label,value:e})}async setEnabled(e){return d("plugin:window|set_enabled",{label:this.label,value:e})}async isEnabled(){return d("plugin:window|is_enabled",{label:this.label})}async setMaximizable(e){return d("plugin:window|set_maximizable",{label:this.label,value:e})}async setMinimizable(e){return d("plugin:window|set_minimizable",{label:this.label,value:e})}async setClosable(e){return d("plugin:window|set_closable",{label:this.label,value:e})}async setTitle(e){return d("plugin:window|set_title",{label:this.label,value:e})}async maximize(){return d("plugin:window|maximize",{label:this.label})}async unmaximize(){return d("plugin:window|unmaximize",{label:this.label})}async toggleMaximize(){return d("plugin:window|toggle_maximize",{label:this.label})}async minimize(){return d("plugin:window|minimize",{label:this.label})}async unminimize(){return d("plugin:window|unminimize",{label:this.label})}async show(){return d("plugin:window|show",{label:this.label})}async hide(){return d("plugin:window|hide",{label:this.label})}async close(){return d("plugin:window|close",{label:this.label})}async destroy(){return d("plugin:window|destroy",{label:this.label})}async setDecorations(e){return d("plugin:window|set_decorations",{label:this.label,value:e})}async setShadow(e){return d("plugin:window|set_shadow",{label:this.label,value:e})}async setEffects(e){return d("plugin:window|set_effects",{label:this.label,value:e})}async clearEffects(){return d("plugin:window|set_effects",{label:this.label,value:null})}async setAlwaysOnTop(e){return d("plugin:window|set_always_on_top",{label:this.label,value:e})}async setAlwaysOnBottom(e){return d("plugin:window|set_always_on_bottom",{label:this.label,value:e})}async setContentProtected(e){return d("plugin:window|set_content_protected",{label:this.label,value:e})}async setSize(e){return d("plugin:window|set_size",{label:this.label,value:e instanceof v?e:new v(e)})}async setMinSize(e){return d("plugin:window|set_min_size",{label:this.label,value:e instanceof v?e:e?new v(e):null})}async setMaxSize(e){return d("plugin:window|set_max_size",{label:this.label,value:e instanceof v?e:e?new v(e):null})}async setSizeConstraints(e){function n(e){return e?{Logical:e}:null}return d("plugin:window|set_size_constraints",{label:this.label,value:{minWidth:n(null==e?void 0:e.minWidth),minHeight:n(null==e?void 0:e.minHeight),maxWidth:n(null==e?void 0:e.maxWidth),maxHeight:n(null==e?void 0:e.maxHeight)}})}async setPosition(e){return d("plugin:window|set_position",{label:this.label,value:e instanceof A?e:new A(e)})}async setFullscreen(e){return d("plugin:window|set_fullscreen",{label:this.label,value:e})}async setFocus(){return d("plugin:window|set_focus",{label:this.label})}async setIcon(e){return d("plugin:window|set_icon",{label:this.label,value:y(e)})}async setSkipTaskbar(e){return d("plugin:window|set_skip_taskbar",{label:this.label,value:e})}async setCursorGrab(e){return d("plugin:window|set_cursor_grab",{label:this.label,value:e})}async setCursorVisible(e){return d("plugin:window|set_cursor_visible",{label:this.label,value:e})}async setCursorIcon(e){return d("plugin:window|set_cursor_icon",{label:this.label,value:e})}async setBackgroundColor(e){return d("plugin:window|set_background_color",{color:e})}async setCursorPosition(e){return d("plugin:window|set_cursor_position",{label:this.label,value:e instanceof A?e:new A(e)})}async setIgnoreCursorEvents(e){return d("plugin:window|set_ignore_cursor_events",{label:this.label,value:e})}async startDragging(){return d("plugin:window|start_dragging",{label:this.label})}async startResizeDragging(e){return d("plugin:window|start_resize_dragging",{label:this.label,value:e})}async setProgressBar(e){return d("plugin:window|set_progress_bar",{label:this.label,value:e})}async setVisibleOnAllWorkspaces(e){return d("plugin:window|set_visible_on_all_workspaces",{label:this.label,value:e})}async setTitleBarStyle(e){return d("plugin:window|set_title_bar_style",{label:this.label,value:e})}async setTheme(e){return d("plugin:window|set_theme",{label:this.label,value:e})}async onResized(e){return this.listen(E.WINDOW_RESIZED,(n=>{n.payload=new m(n.payload),e(n)}))}async onMoved(e){return this.listen(E.WINDOW_MOVED,(n=>{n.payload=new k(n.payload),e(n)}))}async onCloseRequested(e){return this.listen(E.WINDOW_CLOSE_REQUESTED,(async n=>{const t=new ne(n);await e(t),t.isPreventDefault()||await this.destroy()}))}async onDragDropEvent(e){const n=await this.listen(E.DRAG_ENTER,(n=>{e({...n,payload:{type:"enter",paths:n.payload.paths,position:new k(n.payload.position)}})})),t=await this.listen(E.DRAG_OVER,(n=>{e({...n,payload:{type:"over",position:new k(n.payload.position)}})})),i=await this.listen(E.DRAG_DROP,(n=>{e({...n,payload:{type:"drop",paths:n.payload.paths,position:new k(n.payload.position)}})})),r=await this.listen(E.DRAG_LEAVE,(n=>{e({...n,payload:{type:"leave"}})}));return()=>{n(),i(),t(),r()}}async onFocusChanged(e){const n=await this.listen(E.WINDOW_FOCUS,(n=>{e({...n,payload:!0})})),t=await this.listen(E.WINDOW_BLUR,(n=>{e({...n,payload:!1})}));return()=>{n(),t()}}async onScaleChanged(e){return this.listen(E.WINDOW_SCALE_FACTOR_CHANGED,e)}async onThemeChanged(e){return this.listen(E.WINDOW_THEME_CHANGED,e)}}var ae,le;function oe(e){return null===e?null:{name:e.name,scaleFactor:e.scaleFactor,position:new k(e.position),size:new m(e.size)}}!function(e){e.AppearanceBased="appearanceBased",e.Light="light",e.Dark="dark",e.MediumLight="mediumLight",e.UltraDark="ultraDark",e.Titlebar="titlebar",e.Selection="selection",e.Menu="menu",e.Popover="popover",e.Sidebar="sidebar",e.HeaderView="headerView",e.Sheet="sheet",e.WindowBackground="windowBackground",e.HudWindow="hudWindow",e.FullScreenUI="fullScreenUI",e.Tooltip="tooltip",e.ContentBackground="contentBackground",e.UnderWindowBackground="underWindowBackground",e.UnderPageBackground="underPageBackground",e.Mica="mica",e.Blur="blur",e.Acrylic="acrylic",e.Tabbed="tabbed",e.TabbedDark="tabbedDark",e.TabbedLight="tabbedLight"}(ae||(ae={})),function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"}(le||(le={}));var ue=Object.freeze({__proto__:null,CloseRequestedEvent:ne,get Effect(){return ae},get EffectState(){return le},LogicalPosition:f,LogicalSize:b,PhysicalPosition:k,PhysicalSize:m,get ProgressBarStatus(){return X},get UserAttentionType(){return Y},Window:se,availableMonitors:async function(){return d("plugin:window|available_monitors").then((e=>e.map(oe)))},currentMonitor:async function(){return d("plugin:window|current_monitor").then(oe)},cursorPosition:async function(){return d("plugin:window|cursor_position").then((e=>new k(e)))},getAllWindows:ie,getCurrentWindow:te,monitorFromPoint:async function(e,n){return d("plugin:window|monitor_from_point",{x:e,y:n}).then(oe)},primaryMonitor:async function(){return d("plugin:window|primary_monitor").then(oe)}});function ce(){return new pe(te(),window.__TAURI_INTERNALS__.metadata.currentWebview.label,{skip:!0})}async function de(){return d("plugin:webview|get_all_webviews").then((e=>e.map((e=>new pe(new se(e.windowLabel,{skip:!0}),e.label,{skip:!0})))))}const he=["tauri://created","tauri://error"];class pe{constructor(e,n,t){this.window=e,this.label=n,this.listeners=Object.create(null),(null==t?void 0:t.skip)||d("plugin:webview|create_webview",{windowLabel:e.label,label:n,options:t}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;return null!==(n=(await de()).find((n=>n.label===e)))&&void 0!==n?n:null}static getCurrent(){return ce()}static async getAll(){return de()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:I(e,n,{target:{kind:"Webview",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:R(e,n,{target:{kind:"Webview",label:this.label}})}async emit(e,n){if(!he.includes(e))return S(e,n);for(const t of this.listeners[e]||[])t({event:e,id:-1,payload:n})}async emitTo(e,n,t){if(!he.includes(n))return L(e,n,t);for(const e of this.listeners[n]||[])e({event:n,id:-1,payload:t})}_handleTauriEvent(e,n){return!!he.includes(e)&&(e in this.listeners?this.listeners[e].push(n):this.listeners[e]=[n],!0)}async position(){return d("plugin:webview|webview_position",{label:this.label}).then((e=>new k(e)))}async size(){return d("plugin:webview|webview_size",{label:this.label}).then((e=>new m(e)))}async close(){return d("plugin:webview|close",{label:this.label})}async setSize(e){return d("plugin:webview|set_webview_size",{label:this.label,value:e instanceof v?e:new v(e)})}async setPosition(e){return d("plugin:webview|set_webview_position",{label:this.label,value:e instanceof A?e:new A(e)})}async setFocus(){return d("plugin:webview|set_webview_focus",{label:this.label})}async hide(){return d("plugin:webview|webview_hide",{label:this.label})}async show(){return d("plugin:webview|webview_show",{label:this.label})}async setZoom(e){return d("plugin:webview|set_webview_zoom",{label:this.label,value:e})}async reparent(e){return d("plugin:webview|reparent",{label:this.label,window:"string"==typeof e?e:e.label})}async clearAllBrowsingData(){return d("plugin:webview|clear_all_browsing_data")}async setBackgroundColor(e){return d("plugin:webview|set_webview_background_color",{color:e})}async onDragDropEvent(e){const n=await this.listen(E.DRAG_ENTER,(n=>{e({...n,payload:{type:"enter",paths:n.payload.paths,position:new k(n.payload.position)}})})),t=await this.listen(E.DRAG_OVER,(n=>{e({...n,payload:{type:"over",position:new k(n.payload.position)}})})),i=await this.listen(E.DRAG_DROP,(n=>{e({...n,payload:{type:"drop",paths:n.payload.paths,position:new k(n.payload.position)}})})),r=await this.listen(E.DRAG_LEAVE,(n=>{e({...n,payload:{type:"leave"}})}));return()=>{n(),i(),t(),r()}}}var we,ye,_e=Object.freeze({__proto__:null,Webview:pe,getAllWebviews:de,getCurrentWebview:ce});function ge(){const e=ce();return new me(e.label,{skip:!0})}async function be(){return d("plugin:window|get_all_windows").then((e=>e.map((e=>new me(e,{skip:!0})))))}class me{constructor(e,n={}){var t;this.label=e,this.listeners=Object.create(null),(null==n?void 0:n.skip)||d("plugin:webview|create_webview_window",{options:{...n,parent:"string"==typeof n.parent?n.parent:null===(t=n.parent)||void 0===t?void 0:t.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static async getByLabel(e){var n;const t=null!==(n=(await be()).find((n=>n.label===e)))&&void 0!==n?n:null;return t?new me(t.label,{skip:!0}):null}static getCurrent(){return ge()}static async getAll(){return be()}async listen(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:I(e,n,{target:{kind:"WebviewWindow",label:this.label}})}async once(e,n){return this._handleTauriEvent(e,n)?()=>{const t=this.listeners[e];t.splice(t.indexOf(n),1)}:R(e,n,{target:{kind:"WebviewWindow",label:this.label}})}async setBackgroundColor(e){return d("plugin:window|set_background_color",{color:e}).then((()=>d("plugin:webview|set_webview_background_color",{color:e})))}}we=me,ye=[se,pe],(Array.isArray(ye)?ye:[ye]).forEach((e=>{Object.getOwnPropertyNames(e.prototype).forEach((n=>{var t;"object"==typeof we.prototype&&we.prototype&&n in we.prototype||Object.defineProperty(we.prototype,n,null!==(t=Object.getOwnPropertyDescriptor(e.prototype,n))&&void 0!==t?t:Object.create(null))}))}));var ve=Object.freeze({__proto__:null,WebviewWindow:me,getAllWebviewWindows:be,getCurrentWebviewWindow:ge});return e.app=g,e.core=p,e.dpi=T,e.event=P,e.image=_,e.menu=$,e.mocks=Q,e.path=Z,e.tray=ee,e.webview=_e,e.webviewWindow=ve,e.window=ue,e}({});window.__TAURI__=__TAURI_IIFE__; diff --git a/crates/tauri/scripts/core.js b/crates/tauri/scripts/core.js index 6f4523c8d67a..b68b6039195b 100644 --- a/crates/tauri/scripts/core.js +++ b/crates/tauri/scripts/core.js @@ -8,12 +8,13 @@ } const osName = __TEMPLATE_os_name__ + const protocolScheme = __TEMPLATE_protocol_scheme__ Object.defineProperty(window.__TAURI_INTERNALS__, 'convertFileSrc', { value: function (filePath, protocol = 'asset') { const path = encodeURIComponent(filePath) return osName === 'windows' || osName === 'android' - ? `http://${protocol}.localhost/${path}` + ? `${protocolScheme}://${protocol}.localhost/${path}` : `${protocol}://localhost/${path}` } }) diff --git a/crates/tauri/scripts/ipc.js b/crates/tauri/scripts/ipc.js index 28cf331509e6..84013187d55e 100644 --- a/crates/tauri/scripts/ipc.js +++ b/crates/tauri/scripts/ipc.js @@ -88,6 +88,51 @@ return } + // `postMessage` uses `structuredClone` to serialize the data before sending it + // unlike `JSON.stringify`, we can't extend a type with a method similar to `toJSON` + // so that `structuredClone` would use, so until https://github.com/whatwg/html/issues/7428 + // we manually call `toIPC` + function serializeIpcPayload(data) { + // if this value changes, make sure to update it in: + // 1. process-ipc-message-fn.js + // 2. core.ts + const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__' + + if ( + typeof data === 'object' && + data !== null && + 'constructor' in data && + data.constructor === Array + ) { + return data.map((v) => serializeIpcPayload(v)) + } + + if ( + typeof data === 'object' && + data !== null && + SERIALIZE_TO_IPC_FN in data + ) { + return data[SERIALIZE_TO_IPC_FN]() + } + + if ( + typeof data === 'object' && + data !== null && + 'constructor' in data && + data.constructor === Object + ) { + const acc = {} + Object.entries(data).forEach(([k, v]) => { + acc[k] = serializeIpcPayload(v) + }) + return acc + } + + return data + } + + data.payload = serializeIpcPayload(data.payload) + isolation.frame.contentWindow.postMessage( data, '*' /* todo: set this to the secure origin */ diff --git a/crates/tauri/scripts/process-ipc-message-fn.js b/crates/tauri/scripts/process-ipc-message-fn.js index 42af59df9c1f..2a0ad768b8d9 100644 --- a/crates/tauri/scripts/process-ipc-message-fn.js +++ b/crates/tauri/scripts/process-ipc-message-fn.js @@ -16,21 +16,26 @@ } } else { const data = JSON.stringify(message, (_k, val) => { + // if this value changes, make sure to update it in: + // 1. ipc.js + // 2. core.ts + const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__' + if (val instanceof Map) { - let o = {} - val.forEach((v, k) => (o[k] = v)) - return o + return Object.fromEntries(val.entries()) } else if (val instanceof Uint8Array) { return Array.from(val) } else if (val instanceof ArrayBuffer) { return Array.from(new Uint8Array(val)) - } else if ( + } else if (typeof val === "object" && val !== null && SERIALIZE_TO_IPC_FN in val) { + return val[SERIALIZE_TO_IPC_FN]() + } else if ( val instanceof Object && '__TAURI_CHANNEL_MARKER__' in val && typeof val.id === 'number' ) { return `__CHANNEL__:${val.id}` - } else { + } else { return val } }) diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index b513ed7f8964..509ba340c8f4 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -268,6 +268,10 @@ pub struct AssetResolver { impl AssetResolver { /// Gets the app asset associated with the given path. /// + /// By default it tries to infer your application's URL scheme in production by checking if all webviews + /// were configured with [`crate::webview::WebviewBuilder::use_https_scheme`] or `tauri.conf.json > app > windows > useHttpsScheme`. + /// If you are resolving an asset for a webview with a more dynamic configuration, see [`AssetResolver::get_for_scheme`]. + /// /// Resolves to the embedded asset that is part of the app /// in dev when [`devUrl`](https://v2.tauri.app/reference/config/#devurl) points to a folder in your filesystem /// or in production when [`frontendDist`](https://v2.tauri.app/reference/config/#frontenddist) @@ -276,6 +280,19 @@ impl AssetResolver { /// Fallbacks to reading the asset from the [distDir] folder so the behavior is consistent in development. /// Note that the dist directory must exist so you might need to build your frontend assets first. pub fn get(&self, path: String) -> Option { + let use_https_scheme = self + .manager + .webviews() + .values() + .all(|webview| webview.use_https_scheme()); + self.get_for_scheme(path, use_https_scheme) + } + + /// Same as [AssetResolver::get] but resolves the custom protocol scheme based on a parameter. + /// + /// - `use_https_scheme`: If `true` when using [`Pattern::Isolation`](tauri::Pattern::Isolation), + /// the csp header will contain `https://tauri.localhost` instead of `http://tauri.localhost` + pub fn get_for_scheme(&self, path: String, use_https_scheme: bool) -> Option { #[cfg(dev)] { // on dev if the devPath is a path to a directory we have the embedded assets @@ -306,7 +323,7 @@ impl AssetResolver { } } - self.manager.get_asset(path).ok() + self.manager.get_asset(path, use_https_scheme).ok() } /// Iterate on all assets. diff --git a/crates/tauri/src/lib.rs b/crates/tauri/src/lib.rs index ee9f58f9a517..e37df1624d07 100644 --- a/crates/tauri/src/lib.rs +++ b/crates/tauri/src/lib.rs @@ -1011,6 +1011,15 @@ pub(crate) mod sealed { } } +struct UnsafeSend(T); +unsafe impl Send for UnsafeSend {} + +impl UnsafeSend { + fn take(self) -> T { + self.0 + } +} + #[allow(unused)] macro_rules! run_main_thread { ($handle:ident, $ex:expr) => {{ diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 7353c3ae36f4..81f0eaae284d 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -340,9 +340,10 @@ impl AppManager { self.config.build.dev_url.as_ref() } - pub(crate) fn protocol_url(&self) -> Cow<'_, Url> { + pub(crate) fn protocol_url(&self, https: bool) -> Cow<'_, Url> { if cfg!(windows) || cfg!(target_os = "android") { - Cow::Owned(Url::parse("http://tauri.localhost").unwrap()) + let scheme = if https { "https" } else { "http" }; + Cow::Owned(Url::parse(&format!("{scheme}://tauri.localhost")).unwrap()) } else { Cow::Owned(Url::parse("tauri://localhost").unwrap()) } @@ -351,10 +352,10 @@ impl AppManager { /// Get the base URL to use for webview requests. /// /// In dev mode, this will be based on the `devUrl` configuration value. - pub(crate) fn get_url(&self) -> Cow<'_, Url> { + pub(crate) fn get_url(&self, https: bool) -> Cow<'_, Url> { match self.base_path() { Some(url) => Cow::Borrowed(url), - _ => self.protocol_url(), + _ => self.protocol_url(https), } } @@ -372,7 +373,11 @@ impl AppManager { } } - pub fn get_asset(&self, mut path: String) -> Result> { + pub fn get_asset( + &self, + mut path: String, + use_https_schema: bool, + ) -> Result> { let assets = &self.assets; if path.ends_with('/') { path.pop(); @@ -435,7 +440,7 @@ impl AppManager { let default_src = csp_map .entry("default-src".into()) .or_insert_with(Default::default); - default_src.push(crate::pattern::format_real_schema(schema)); + default_src.push(crate::pattern::format_real_schema(schema, use_https_schema)); } csp_header.replace(Csp::DirectiveMap(csp_map).to_string()); @@ -597,7 +602,31 @@ impl AppManager { } => self.emit_filter(event, payload, |t| match t { EventTarget::Window { label } | EventTarget::Webview { label } - | EventTarget::WebviewWindow { label } => label == &target_label, + | EventTarget::WebviewWindow { label } + | EventTarget::AnyLabel { label } => label == &target_label, + _ => false, + }), + + EventTarget::Window { + label: target_label, + } => self.emit_filter(event, payload, |t| match t { + EventTarget::AnyLabel { label } | EventTarget::Window { label } => label == &target_label, + _ => false, + }), + + EventTarget::Webview { + label: target_label, + } => self.emit_filter(event, payload, |t| match t { + EventTarget::AnyLabel { label } | EventTarget::Webview { label } => label == &target_label, + _ => false, + }), + + EventTarget::WebviewWindow { + label: target_label, + } => self.emit_filter(event, payload, |t| match t { + EventTarget::AnyLabel { label } | EventTarget::WebviewWindow { label } => { + label == &target_label + } _ => false, }), @@ -747,17 +776,25 @@ mod test { #[cfg(custom_protocol)] { assert_eq!( - manager.get_url().to_string(), + manager.get_url(false).to_string(), if cfg!(windows) || cfg!(target_os = "android") { "http://tauri.localhost/" } else { "tauri://localhost" } ); + assert_eq!( + manager.get_url(true).to_string(), + if cfg!(windows) || cfg!(target_os = "android") { + "https://tauri.localhost/" + } else { + "tauri://localhost" + } + ); } #[cfg(dev)] - assert_eq!(manager.get_url().to_string(), "http://localhost:4000/"); + assert_eq!(manager.get_url(false).to_string(), "http://localhost:4000/"); } struct EventSetup { diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 5eb4ba8de2a3..8773600dca2e 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -137,10 +137,14 @@ impl WebviewManager { let mut webview_attributes = pending.webview_attributes; + let use_https_scheme = webview_attributes.use_https_scheme; + let ipc_init = IpcJavascript { isolation_origin: &match &*app_manager.pattern { #[cfg(feature = "isolation")] - crate::Pattern::Isolation { schema, .. } => crate::pattern::format_real_schema(schema), + crate::Pattern::Isolation { schema, .. } => { + crate::pattern::format_real_schema(schema, use_https_scheme) + } _ => "".to_string(), }, } @@ -180,6 +184,7 @@ impl WebviewManager { &ipc_init.into_string(), &pattern_init.into_string(), is_init_global, + use_https_scheme, )?); for plugin_init_script in plugin_init_scripts { @@ -190,7 +195,7 @@ impl WebviewManager { if let crate::Pattern::Isolation { schema, .. } = &*app_manager.pattern { webview_attributes = webview_attributes.initialization_script( &IsolationJavascript { - isolation_src: &crate::pattern::format_real_schema(schema), + isolation_src: &crate::pattern::format_real_schema(schema, use_https_scheme), style: tauri_utils::pattern::isolation::IFRAME_STYLE, } .render_default(&Default::default())? @@ -232,7 +237,8 @@ impl WebviewManager { && window_url.scheme() != "http" && window_url.scheme() != "https" { - format!("http://{}.localhost", window_url.scheme()) + let https = if use_https_scheme { "https" } else { "http" }; + format!("{https}://{}.localhost", window_url.scheme()) } else if let Some(host) = window_url.host() { format!( "{}://{}{}", @@ -320,6 +326,7 @@ impl WebviewManager { assets.clone(), *crypto_keys.aes_gcm().raw(), window_origin, + use_https_scheme, ); pending.register_uri_scheme_protocol(schema, move |webview_id, request, responder| { protocol(webview_id, request, UriSchemeResponder(responder)) @@ -335,6 +342,7 @@ impl WebviewManager { ipc_script: &str, pattern_script: &str, with_global_tauri: bool, + use_https_scheme: bool, ) -> crate::Result { #[derive(Template)] #[default_template("../../scripts/init.js")] @@ -357,6 +365,7 @@ impl WebviewManager { #[default_template("../../scripts/core.js")] struct CoreJavascript<'a> { os_name: &'a str, + protocol_scheme: &'a str, invoke_key: &'a str, } @@ -378,6 +387,7 @@ impl WebviewManager { bundle_script, core_script: &CoreJavascript { os_name: std::env::consts::OS, + protocol_scheme: if use_https_scheme { "https" } else { "http" }, invoke_key: self.invoke_key(), } .render_default(&Default::default())? @@ -411,7 +421,7 @@ impl WebviewManager { let url = if PROXY_DEV_SERVER { Cow::Owned(Url::parse("tauri://localhost").unwrap()) } else { - app_manager.get_url() + app_manager.get_url(pending.webview_attributes.use_https_scheme) }; // ignore "index.html" just to simplify the url if path.to_str() != Some("index.html") { @@ -425,7 +435,7 @@ impl WebviewManager { } } WebviewUrl::External(url) => { - let config_url = app_manager.get_url(); + let config_url = app_manager.get_url(pending.webview_attributes.use_https_scheme); let is_local = config_url.make_relative(url).is_some(); let mut url = url.clone(); if is_local && PROXY_DEV_SERVER { @@ -572,8 +582,9 @@ impl WebviewManager { &self, window: Window, webview: DetachedWebview, + use_https_scheme: bool, ) -> Webview { - let webview = Webview::new(window, webview); + let webview = Webview::new(window, webview, use_https_scheme); let webview_event_listeners = self.event_listeners.clone(); let webview_ = webview.clone(); diff --git a/crates/tauri/src/menu/mod.rs b/crates/tauri/src/menu/mod.rs index 2797a5543ea2..2ae33d971b89 100644 --- a/crates/tauri/src/menu/mod.rs +++ b/crates/tauri/src/menu/mod.rs @@ -75,7 +75,6 @@ macro_rules! gen_wrappers { app_handle: $crate::AppHandle, } - /// # Safety /// /// We make sure it always runs on the main thread. @@ -96,11 +95,9 @@ macro_rules! gen_wrappers { impl Drop for $inner { fn drop(&mut self) { - struct SafeSend(T); - unsafe impl Send for SafeSend {} - let inner = self.inner.take(); - let inner = SafeSend(inner); + // SAFETY: inner was created on main thread and is being dropped on main thread + let inner = $crate::UnsafeSend(inner); let _ = self.app_handle.run_on_main_thread(move || { drop(inner); }); diff --git a/crates/tauri/src/path/mod.rs b/crates/tauri/src/path/mod.rs index 28854fb547ff..575fa926bfd6 100644 --- a/crates/tauri/src/path/mod.rs +++ b/crates/tauri/src/path/mod.rs @@ -85,61 +85,60 @@ pub enum BaseDirectory { /// The Audio directory. Audio = 1, /// The Cache directory. - Cache, + Cache = 2, /// The Config directory. - Config, + Config = 3, /// The Data directory. - Data, + Data = 4, /// The LocalData directory. - LocalData, + LocalData = 5, /// The Document directory. - Document, + Document = 6, /// The Download directory. - Download, + Download = 7, /// The Picture directory. - Picture, + Picture = 8, /// The Public directory. - Public, + Public = 9, /// The Video directory. - Video, + Video = 10, /// The Resource directory. - Resource, + Resource = 11, /// A temporary directory. Resolves to [`std::env::temp_dir`]. - Temp, + Temp = 12, /// The default app config directory. /// Resolves to [`BaseDirectory::Config`]`/{bundle_identifier}`. - AppConfig, + AppConfig = 13, /// The default app data directory. /// Resolves to [`BaseDirectory::Data`]`/{bundle_identifier}`. - AppData, + AppData = 14, /// The default app local data directory. /// Resolves to [`BaseDirectory::LocalData`]`/{bundle_identifier}`. - AppLocalData, + AppLocalData = 15, /// The default app cache directory. /// Resolves to [`BaseDirectory::Cache`]`/{bundle_identifier}`. - AppCache, + AppCache = 16, /// The default app log directory. /// Resolves to [`BaseDirectory::Home`]`/Library/Logs/{bundle_identifier}` on macOS /// and [`BaseDirectory::Config`]`/{bundle_identifier}/logs` on linux and Windows. - AppLog, - /// The Home directory. - Home, - + AppLog = 17, /// The Desktop directory. #[cfg(not(target_os = "android"))] - Desktop, + Desktop = 18, /// The Executable directory. #[cfg(not(target_os = "android"))] - Executable, + Executable = 19, /// The Font directory. #[cfg(not(target_os = "android"))] - Font, + Font = 20, + /// The Home directory. + Home = 21, /// The Runtime directory. #[cfg(not(target_os = "android"))] - Runtime, + Runtime = 22, /// The Template directory. #[cfg(not(target_os = "android"))] - Template, + Template = 23, } impl BaseDirectory { diff --git a/crates/tauri/src/pattern.rs b/crates/tauri/src/pattern.rs index 221fce9fb1c2..d0cd132c21b9 100644 --- a/crates/tauri/src/pattern.rs +++ b/crates/tauri/src/pattern.rs @@ -85,9 +85,10 @@ pub(crate) struct PatternJavascript { } #[allow(dead_code)] -pub(crate) fn format_real_schema(schema: &str) -> String { +pub(crate) fn format_real_schema(schema: &str, https: bool) -> String { if cfg!(windows) || cfg!(target_os = "android") { - format!("http://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") + let scheme = if https { "https" } else { "http" }; + format!("{scheme}://{schema}.{ISOLATION_IFRAME_SRC_DOMAIN}") } else { format!("{schema}://{ISOLATION_IFRAME_SRC_DOMAIN}") } diff --git a/crates/tauri/src/protocol/isolation.rs b/crates/tauri/src/protocol/isolation.rs index 6d546d288695..53c5d9bb236a 100644 --- a/crates/tauri/src/protocol/isolation.rs +++ b/crates/tauri/src/protocol/isolation.rs @@ -5,7 +5,10 @@ use crate::Assets; use http::header::CONTENT_TYPE; use serialize_to_javascript::Template; -use tauri_utils::{assets::EmbeddedAssets, config::Csp}; +use tauri_utils::{ + assets::EmbeddedAssets, + config::{Csp, HeaderAddition}, +}; use std::sync::Arc; @@ -21,9 +24,11 @@ pub fn get( assets: Arc, aes_gcm_key: [u8; 32], window_origin: String, + use_https_scheme: bool, ) -> UriSchemeProtocolHandler { let frame_src = if cfg!(any(windows, target_os = "android")) { - format!("http://{schema}.localhost") + let https = if use_https_scheme { "https" } else { "http" }; + format!("{https}://{schema}.localhost") } else { format!("{schema}:") }; @@ -51,6 +56,7 @@ pub fn get( }; match template.render(asset.as_ref(), &Default::default()) { Ok(asset) => http::Response::builder() + .add_configured_headers(manager.config.app.security.headers.as_ref()) .header(CONTENT_TYPE, mime::TEXT_HTML.as_ref()) .header("Content-Security-Policy", csp) .body(asset.into_string().as_bytes().to_vec()), diff --git a/crates/tauri/src/protocol/tauri.rs b/crates/tauri/src/protocol/tauri.rs index 2d33c4a00f7f..cded6f615455 100644 --- a/crates/tauri/src/protocol/tauri.rs +++ b/crates/tauri/src/protocol/tauri.rs @@ -5,6 +5,7 @@ use std::{borrow::Cow, sync::Arc}; use http::{header::CONTENT_TYPE, Request, Response as HttpResponse, StatusCode}; +use tauri_utils::config::HeaderAddition; use crate::{ manager::{webview::PROXY_DEV_SERVER, AppManager}, @@ -30,7 +31,10 @@ pub fn get( ) -> UriSchemeProtocolHandler { #[cfg(all(dev, mobile))] let url = { - let mut url = manager.get_url().as_str().to_string(); + let mut url = manager + .get_url(window_origin.starts_with("https")) + .as_str() + .to_string(); if url.ends_with('/') { url.pop(); } @@ -95,7 +99,9 @@ fn get_response( // where `$P` is not `localhost/*` .unwrap_or_else(|| "".to_string()); - let mut builder = HttpResponse::builder().header("Access-Control-Allow-Origin", window_origin); + let mut builder = HttpResponse::builder() + .add_configured_headers(manager.config.app.security.headers.as_ref()) + .header("Access-Control-Allow-Origin", window_origin); #[cfg(all(dev, mobile))] let mut response = { @@ -152,7 +158,8 @@ fn get_response( #[cfg(not(all(dev, mobile)))] let mut response = { - let asset = manager.get_asset(path)?; + let use_https_scheme = request.uri().scheme() == Some(&http::uri::Scheme::HTTPS); + let asset = manager.get_asset(path, use_https_scheme)?; builder = builder.header(CONTENT_TYPE, &asset.mime_type); if let Some(csp) = &asset.csp_header { builder = builder.header("Content-Security-Policy", csp); diff --git a/crates/tauri/src/test/mock_runtime.rs b/crates/tauri/src/test/mock_runtime.rs index 80c32e26d8ed..a1488ae38d5b 100644 --- a/crates/tauri/src/test/mock_runtime.rs +++ b/crates/tauri/src/test/mock_runtime.rs @@ -9,8 +9,10 @@ use tauri_runtime::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, monitor::Monitor, webview::{DetachedWebview, PendingWebview}, - window::{CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, WindowId}, - window::{WindowBuilder, WindowBuilderBase}, + window::{ + CursorIcon, DetachedWindow, DetachedWindowWebview, PendingWindow, RawWindow, WindowBuilder, + WindowBuilderBase, WindowEvent, WindowId, + }, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, ProgressBarState, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch, WindowDispatch, WindowEventId, @@ -158,14 +160,17 @@ impl RuntimeHandle for MockRuntimeHandle { }, ); - let webview = webview_id.map(|id| DetachedWebview { - label: pending.label.clone(), - dispatcher: MockWebviewDispatcher { - id, - context: self.context.clone(), - url: Arc::new(Mutex::new(pending.webview.unwrap().url)), - last_evaluated_script: Default::default(), + let webview = webview_id.map(|id| DetachedWindowWebview { + webview: DetachedWebview { + label: pending.label.clone(), + dispatcher: MockWebviewDispatcher { + id, + context: self.context.clone(), + url: Arc::new(Mutex::new(pending.webview.unwrap().url)), + last_evaluated_script: Default::default(), + }, }, + use_https_scheme: false, }); Ok(DetachedWindow { @@ -416,6 +421,10 @@ impl WindowBuilder for MockWindowBuilder { self } + fn window_classname>(self, classname: S) -> Self { + self + } + fn shadow(self, enable: bool) -> Self { self } @@ -477,6 +486,10 @@ impl WindowBuilder for MockWindowBuilder { fn get_theme(&self) -> Option { None } + + fn background_color(self, _color: tauri_utils::config::Color) -> Self { + self + } } impl WebviewDispatch for MockWebviewDispatcher { @@ -588,6 +601,10 @@ impl WebviewDispatch for MockWebviewDispatcher { fn show(&self) -> Result<()> { Ok(()) } + + fn set_background_color(&self, color: Option) -> Result<()> { + Ok(()) + } } impl WindowDispatch for MockWindowDispatcher { @@ -773,14 +790,17 @@ impl WindowDispatch for MockWindowDispatcher { }, ); - let webview = webview_id.map(|id| DetachedWebview { - label: pending.label.clone(), - dispatcher: MockWebviewDispatcher { - id, - context: self.context.clone(), - url: Arc::new(Mutex::new(pending.webview.unwrap().url)), - last_evaluated_script: Default::default(), + let webview = webview_id.map(|id| DetachedWindowWebview { + webview: DetachedWebview { + label: pending.label.clone(), + dispatcher: MockWebviewDispatcher { + id, + context: self.context.clone(), + url: Arc::new(Mutex::new(pending.webview.unwrap().url)), + last_evaluated_script: Default::default(), + }, }, + use_https_scheme: false, }); Ok(DetachedWindow { @@ -979,6 +999,10 @@ impl WindowDispatch for MockWindowDispatcher { fn is_enabled(&self) -> Result { Ok(true) } + + fn set_background_color(&self, color: Option) -> Result<()> { + Ok(()) + } } #[derive(Debug, Clone)] @@ -1065,14 +1089,17 @@ impl Runtime for MockRuntime { }, ); - let webview = webview_id.map(|id| DetachedWebview { - label: pending.label.clone(), - dispatcher: MockWebviewDispatcher { - id, - context: self.context.clone(), - url: Arc::new(Mutex::new(pending.webview.unwrap().url)), - last_evaluated_script: Default::default(), + let webview = webview_id.map(|id| DetachedWindowWebview { + webview: DetachedWebview { + label: pending.label.clone(), + dispatcher: MockWebviewDispatcher { + id, + context: self.context.clone(), + url: Arc::new(Mutex::new(pending.webview.unwrap().url)), + last_evaluated_script: Default::default(), + }, }, + use_https_scheme: false, }); Ok(DetachedWindow { diff --git a/crates/tauri/src/tray/mod.rs b/crates/tauri/src/tray/mod.rs index 21623ff2b7d5..5c053f52b66d 100644 --- a/crates/tauri/src/tray/mod.rs +++ b/crates/tauri/src/tray/mod.rs @@ -10,6 +10,7 @@ use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener}; use crate::menu::ContextMenu; use crate::menu::MenuEvent; use crate::resources::Resource; +use crate::UnsafeSend; use crate::{ image::Image, menu::run_item_main_thread, AppHandle, Manager, PhysicalPosition, Rect, Runtime, }; @@ -342,10 +343,24 @@ impl TrayIconBuilder { /// Builds and adds a new [`TrayIcon`] to the system tray. pub fn build>(self, manager: &M) -> crate::Result> { let id = self.id().clone(); - let inner = self.inner.build()?; + + // SAFETY: + // the menu within this builder was created on main thread + // and will be accessed on the main thread + let unsafe_builder = UnsafeSend(self.inner); + + let (tx, rx) = std::sync::mpsc::channel(); + let unsafe_tray = manager + .app_handle() + .run_on_main_thread(move || { + // SAFETY: will only be accessed on main thread + let _ = tx.send(unsafe_builder.take().build().map(UnsafeSend)); + }) + .and_then(|_| rx.recv().map_err(|_| crate::Error::FailedToReceiveMessage))??; + let icon = TrayIcon { id, - inner, + inner: unsafe_tray.take(), app_handle: manager.app_handle().clone(), }; diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index e0df92ecea08..feb818162910 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -22,6 +22,7 @@ use tauri_runtime::{ webview::{DetachedWebview, PendingWebview, WebviewAttributes}, WebviewDispatch, }; +pub use tauri_utils::config::Color; use tauri_utils::config::{WebviewUrl, WindowConfig}; pub use url::Url; @@ -41,7 +42,7 @@ use crate::{ use std::{ borrow::Cow, hash::{Hash, Hasher}, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, Mutex, MutexGuard}, }; @@ -605,11 +606,17 @@ tauri::Builder::default() pending.webview_attributes.bounds = Some(tauri_runtime::Rect { size, position }); + let use_https_scheme = pending.webview_attributes.use_https_scheme; + let webview = match &mut window.runtime() { RuntimeOrDispatch::Dispatch(dispatcher) => dispatcher.create_webview(pending), _ => unimplemented!(), } - .map(|webview| app_manager.webview.attach_webview(window.clone(), webview))?; + .map(|webview| { + app_manager + .webview + .attach_webview(window.clone(), webview, use_https_scheme) + })?; Ok(webview) } @@ -754,6 +761,13 @@ fn main() { self } + /// Whether the webview should be focused or not. + #[must_use] + pub fn focused(mut self, focus: bool) -> Self { + self.webview_attributes.focus = focus; + self + } + /// Sets the webview to automatically grow and shrink its size and position when the parent window resizes. #[must_use] pub fn auto_resize(mut self) -> Self { @@ -787,6 +801,61 @@ fn main() { self.webview_attributes.browser_extensions_enabled = enabled; self } + + /// Set the path from which to load extensions from. Extensions stored in this path should be unpacked Chrome extensions on Windows, and compiled `.so` extensions on Linux. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled) + /// - **MacOS / iOS / Android** - Unsupported. + #[must_use] + pub fn extensions_path(mut self, path: impl AsRef) -> Self { + self.webview_attributes.extensions_path = Some(path.as_ref().to_path_buf()); + self + } + + /// Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + /// + /// ## Note + /// + /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + /// + /// ## Warning + /// + /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data. + #[must_use] + pub fn use_https_scheme(mut self, enabled: bool) -> Self { + self.webview_attributes.use_https_scheme = enabled; + self + } + + /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default. + /// + /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + /// + /// ## Platform-specific + /// + /// - macOS: This will call private functions on **macOS** + /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + #[must_use] + pub fn devtools(mut self, enabled: bool) -> Self { + self.webview_attributes.devtools.replace(enabled); + self + } + + /// Set the webview background color. + /// + /// ## Platform-specific: + /// + /// - **macOS / iOS**: Not implemented. + /// - **Windows**: On Windows 7, alpha channel is ignored. + /// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + #[must_use] + pub fn background_color(mut self, color: Color) -> Self { + self.webview_attributes.background_color = Some(color); + self + } } /// Webview. @@ -799,6 +868,7 @@ pub struct Webview { pub(crate) manager: Arc>, pub(crate) app_handle: AppHandle, pub(crate) resources_table: Arc>, + use_https_scheme: bool, } impl std::fmt::Debug for Webview { @@ -806,6 +876,7 @@ impl std::fmt::Debug for Webview { f.debug_struct("Window") .field("window", &self.window.lock().unwrap()) .field("webview", &self.webview) + .field("use_https_scheme", &self.use_https_scheme) .finish() } } @@ -818,6 +889,7 @@ impl Clone for Webview { manager: self.manager.clone(), app_handle: self.app_handle.clone(), resources_table: self.resources_table.clone(), + use_https_scheme: self.use_https_scheme, } } } @@ -840,13 +912,18 @@ impl PartialEq for Webview { /// Base webview functions. impl Webview { /// Create a new webview that is attached to the window. - pub(crate) fn new(window: Window, webview: DetachedWebview) -> Self { + pub(crate) fn new( + window: Window, + webview: DetachedWebview, + use_https_scheme: bool, + ) -> Self { Self { manager: window.manager.clone(), app_handle: window.app_handle.clone(), window: Arc::new(Mutex::new(window)), webview, resources_table: Default::default(), + use_https_scheme, } } @@ -873,6 +950,11 @@ impl Webview { &self.webview.label } + /// Whether the webview was configured to use the HTTPS scheme or not. + pub(crate) fn use_https_scheme(&self) -> bool { + self.use_https_scheme + } + /// Registers a window event listener. pub fn on_webview_event(&self, f: F) { self @@ -1173,9 +1255,11 @@ fn main() { } fn is_local_url(&self, current_url: &Url) -> bool { + let uses_https = current_url.scheme() == "https"; + // if from `tauri://` custom protocol ({ - let protocol_url = self.manager().protocol_url(); + let protocol_url = self.manager().protocol_url(uses_https); current_url.scheme() == protocol_url.scheme() && current_url.domain() == protocol_url.domain() }) || @@ -1183,7 +1267,7 @@ fn main() { // or if relative to `devUrl` or `frontendDist` self .manager() - .get_url() + .get_url(uses_https) .make_relative(current_url) .is_some() @@ -1199,7 +1283,7 @@ fn main() { // so we check using the first part of the domain #[cfg(any(windows, target_os = "android"))] let local = { - let protocol_url = self.manager().protocol_url(); + let protocol_url = self.manager().protocol_url(uses_https); let maybe_protocol = current_url .domain() .and_then(|d| d .split_once('.')) @@ -1561,6 +1645,22 @@ tauri::Builder::default() .map_err(Into::into) } + /// Specify the webview background color. + /// + /// ## Platfrom-specific: + /// + /// - **macOS / iOS**: Not implemented. + /// - **Windows**: + /// - On Windows 7, transparency is not supported and the alpha value will be ignored. + /// - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255` + pub fn set_background_color(&self, color: Option) -> crate::Result<()> { + self + .webview + .dispatcher + .set_background_color(color) + .map_err(Into::into) + } + /// Clear all browsing data for this webview. pub fn clear_all_browsing_data(&self) -> crate::Result<()> { self diff --git a/crates/tauri/src/webview/plugin.rs b/crates/tauri/src/webview/plugin.rs index ca8fd24eea6d..aa92050e361f 100644 --- a/crates/tauri/src/webview/plugin.rs +++ b/crates/tauri/src/webview/plugin.rs @@ -18,10 +18,14 @@ mod desktop_commands { use super::*; use crate::{ - command, sealed::ManagerBase, utils::config::WindowEffectsConfig, AppHandle, Webview, - WebviewWindowBuilder, + command, sealed::ManagerBase, utils::config::WindowEffectsConfig, webview::Color, AppHandle, + Webview, WebviewWindowBuilder, }; + fn default_true() -> bool { + true + } + #[derive(Debug, PartialEq, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WebviewConfig { @@ -35,6 +39,8 @@ mod desktop_commands { height: f64, #[serde(default)] transparent: bool, + #[serde(default = "default_true")] + focus: bool, #[serde(default)] accept_first_mouse: bool, window_effects: Option, @@ -44,6 +50,23 @@ mod desktop_commands { zoom_hotkeys_enabled: bool, } + #[cfg(feature = "unstable")] + impl crate::webview::WebviewBuilder { + fn from_webview_config(label: String, config: WebviewConfig) -> Self { + let mut builder = Self::new(label, config.url); + builder.webview_attributes.user_agent = config.user_agent; + builder.webview_attributes.drag_drop_handler_enabled = + config.drag_drop_enabled.unwrap_or(true); + builder.webview_attributes.transparent = config.transparent; + builder.webview_attributes.focus = config.focus; + builder.webview_attributes.accept_first_mouse = config.accept_first_mouse; + builder.webview_attributes.window_effects = config.window_effects; + builder.webview_attributes.incognito = config.incognito; + builder.webview_attributes.zoom_hotkeys_enabled = config.zoom_hotkeys_enabled; + builder + } + } + #[derive(Serialize)] pub struct WebviewRef { window_label: String, @@ -89,21 +112,18 @@ mod desktop_commands { .manager() .get_window(&window_label) .ok_or(crate::Error::WindowNotFound)?; - let mut builder = crate::webview::WebviewBuilder::new(label, options.url); - builder.webview_attributes.user_agent = options.user_agent; - builder.webview_attributes.drag_drop_handler_enabled = - options.drag_drop_enabled.unwrap_or(true); - builder.webview_attributes.transparent = options.transparent; - builder.webview_attributes.accept_first_mouse = options.accept_first_mouse; - builder.webview_attributes.window_effects = options.window_effects; - builder.webview_attributes.incognito = options.incognito; - builder.webview_attributes.zoom_hotkeys_enabled = options.zoom_hotkeys_enabled; + let x = options.x; + let y = options.y; + let width = options.width; + let height = options.height; + + let builder = crate::webview::WebviewBuilder::from_webview_config(label, options); window.add_child( builder, - tauri_runtime::dpi::LogicalPosition::new(options.x, options.y), - tauri_runtime::dpi::LogicalSize::new(options.width, options.height), + tauri_runtime::dpi::LogicalPosition::new(x, y), + tauri_runtime::dpi::LogicalSize::new(width, height), )?; Ok(()) @@ -179,6 +199,11 @@ mod desktop_commands { setter!(webview_hide, hide); setter!(webview_show, show); setter!(set_webview_zoom, set_zoom, f64); + setter!( + set_webview_background_color, + set_background_color, + Option + ); setter!(clear_all_browsing_data, clear_all_browsing_data); #[command(root = "crate")] @@ -262,6 +287,7 @@ pub fn init() -> TauriPlugin { desktop_commands::set_webview_size, desktop_commands::set_webview_position, desktop_commands::set_webview_focus, + desktop_commands::set_webview_background_color, desktop_commands::set_webview_zoom, desktop_commands::webview_hide, desktop_commands::webview_show, diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index ee1878e72252..b95e6a90564c 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -6,7 +6,7 @@ use std::{ borrow::Cow, - path::PathBuf, + path::{Path, PathBuf}, sync::{Arc, MutexGuard}, }; @@ -29,7 +29,7 @@ use crate::{ }; use serde::Serialize; use tauri_utils::{ - config::{WebviewUrl, WindowConfig}, + config::{Color, WebviewUrl, WindowConfig}, Theme, }; use url::Url; @@ -476,10 +476,11 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { #[must_use] #[deprecated( since = "1.2.0", - note = "The window is automatically focused by default. This function Will be removed in 2.0.0. Use `focused` instead." + note = "The window is automatically focused by default. This function Will be removed in 3.0.0. Use `focused` instead." )] pub fn focus(mut self) -> Self { self.window_builder = self.window_builder.focused(true); + self.webview_builder = self.webview_builder.focused(true); self } @@ -487,6 +488,7 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { #[must_use] pub fn focused(mut self, focused: bool) -> Self { self.window_builder = self.window_builder.focused(focused); + self.webview_builder = self.webview_builder.focused(focused); self } @@ -569,6 +571,13 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { self } + /// Sets custom name for Windows' window class. **Windows only**. + #[must_use] + pub fn window_classname>(mut self, classname: S) -> Self { + self.window_builder = self.window_builder.window_classname(classname); + self + } + /// Sets whether or not the window has shadow. /// /// ## Platform-specific @@ -896,6 +905,65 @@ impl<'a, R: Runtime, M: Manager> WebviewWindowBuilder<'a, R, M> { self.webview_builder = self.webview_builder.browser_extensions_enabled(enabled); self } + + /// Set the path from which to load extensions from. Extensions stored in this path should be unpacked Chrome extensions on Windows, and compiled `.so` extensions on Linux. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Browser extensions must first be enabled. See [`browser_extensions_enabled`](Self::browser_extensions_enabled) + /// - **MacOS / iOS / Android** - Unsupported. + #[must_use] + pub fn extensions_path(mut self, path: impl AsRef) -> Self { + self.webview_builder = self.webview_builder.extensions_path(path); + self + } + + /// Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + /// + /// ## Note + /// + /// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + /// + /// ## Warning + /// + /// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data. + #[must_use] + pub fn use_https_scheme(mut self, enabled: bool) -> Self { + self.webview_builder = self.webview_builder.use_https_scheme(enabled); + self + } + + /// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default. + /// + /// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + /// + /// ## Platform-specific + /// + /// - macOS: This will call private functions on **macOS**. + /// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + /// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + #[must_use] + pub fn devtools(mut self, enabled: bool) -> Self { + self.webview_builder = self.webview_builder.devtools(enabled); + self + } + + /// Set the window and webview background color. + /// + /// ## Platform-specific: + /// + /// - **Android / iOS:** Unsupported for the window layer. + /// - **macOS / iOS**: Not implemented for the webview layer. + /// - **Windows**: + /// - alpha channel is ignored for the window layer. + /// - On Windows 7, alpha channel is ignored for the webview layer. + /// - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + #[must_use] + pub fn background_color(mut self, color: Color) -> Self { + self.window_builder = self.window_builder.background_color(color); + self.webview_builder = self.webview_builder.background_color(color); + self + } } /// A type that wraps a [`Window`] together with a [`Webview`]. @@ -1578,6 +1646,21 @@ impl WebviewWindow { self.window.set_icon(icon) } + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **iOS / Android:** Unsupported. + /// - **macOS**: Not implemented for the webview layer.. + /// - **Windows**: + /// - alpha channel is ignored for the window layer. + /// - On Windows 7, transparency is not supported and the alpha value will be ignored for the webview layer.. + /// - On Windows 8 and newer: translucent colors are not supported so any alpha value other than `0` will be replaced by `255` for the webview layer. + pub fn set_background_color(&self, color: Option) -> crate::Result<()> { + self.window.set_background_color(color)?; + self.webview.set_background_color(color) + } + /// Whether to hide the window icon from the taskbar or not. /// /// ## Platform-specific diff --git a/crates/tauri/src/window/mod.rs b/crates/tauri/src/window/mod.rs index ab0e8cc7fe10..93310fcc9d78 100644 --- a/crates/tauri/src/window/mod.rs +++ b/crates/tauri/src/window/mod.rs @@ -381,7 +381,11 @@ tauri::Builder::default() ); if let Some(webview) = detached_window.webview { - app_manager.webview.attach_webview(window.clone(), webview); + app_manager.webview.attach_webview( + window.clone(), + webview.webview, + webview.use_https_scheme, + ); } window @@ -531,7 +535,7 @@ impl<'a, R: Runtime, M: Manager> WindowBuilder<'a, R, M> { #[must_use] #[deprecated( since = "1.2.0", - note = "The window is automatically focused by default. This function Will be removed in 2.0.0. Use `focused` instead." + note = "The window is automatically focused by default. This function Will be removed in 3.0.0. Use `focused` instead." )] pub fn focus(mut self) -> Self { self.window_builder = self.window_builder.focused(true); @@ -641,6 +645,13 @@ impl<'a, R: Runtime, M: Manager> WindowBuilder<'a, R, M> { self } + /// Sets custom name for Windows' window class. **Windows only**. + #[must_use] + pub fn window_classname>(mut self, classname: S) -> Self { + self.window_builder = self.window_builder.window_classname(classname); + self + } + /// Sets whether or not the window has shadow. /// /// ## Platform-specific @@ -835,6 +846,18 @@ impl<'a, R: Runtime, M: Manager> WindowBuilder<'a, R, M> { } } +impl<'a, R: Runtime, M: Manager> WindowBuilder<'a, R, M> { + /// Set the window and webview background color. + /// + /// ## Platform-specific: + /// + /// - **Windows**: alpha channel is ignored. + #[must_use] + pub fn background_color(mut self, color: Color) -> Self { + self.window_builder = self.window_builder.background_color(color); + self + } +} /// A wrapper struct to hold the window menu state /// and whether it is global per-app or specific to this window. #[cfg(desktop)] @@ -1806,6 +1829,20 @@ tauri::Builder::default() .map_err(Into::into) } + /// Sets the window background color. + /// + /// ## Platform-specific: + /// + /// - **Windows:** alpha channel is ignored. + /// - **iOS / Android:** Unsupported. + pub fn set_background_color(&self, color: Option) -> crate::Result<()> { + self + .window + .dispatcher + .set_background_color(color) + .map_err(Into::into) + } + /// Prevents the window contents from being captured by other apps. pub fn set_content_protected(&self, protected: bool) -> crate::Result<()> { self @@ -2041,7 +2078,7 @@ tauri::Builder::default() docsrs, doc(cfg(any(target_os = "macos", target_os = "linux", windows))) )] -#[derive(serde::Deserialize)] +#[derive(serde::Deserialize, Debug)] pub struct ProgressBarState { /// The progress bar status. pub status: Option, diff --git a/crates/tauri/src/window/plugin.rs b/crates/tauri/src/window/plugin.rs index a25229e6dd69..edb44b20fc97 100644 --- a/crates/tauri/src/window/plugin.rs +++ b/crates/tauri/src/window/plugin.rs @@ -19,6 +19,7 @@ mod desktop_commands { command, sealed::ManagerBase, utils::config::{WindowConfig, WindowEffectsConfig}, + window::Color, window::{ProgressBarState, WindowBuilder}, AppHandle, CursorIcon, Manager, Monitor, PhysicalPosition, PhysicalSize, Position, Size, Theme, UserAttentionType, Webview, Window, @@ -130,6 +131,7 @@ mod desktop_commands { setter!(set_skip_taskbar, bool); setter!(set_cursor_grab, bool); setter!(set_cursor_visible, bool); + setter!(set_background_color, Option); setter!(set_cursor_icon, CursorIcon); setter!(set_cursor_position, Position); setter!(set_ignore_cursor_events, bool); @@ -291,6 +293,7 @@ pub fn init() -> TauriPlugin { desktop_commands::set_progress_bar, desktop_commands::set_icon, desktop_commands::set_visible_on_all_workspaces, + desktop_commands::set_background_color, desktop_commands::set_title_bar_style, desktop_commands::set_theme, desktop_commands::toggle_maximize, diff --git a/crates/tauri/test/fixture/src-tauri/tauri.conf.json b/crates/tauri/test/fixture/src-tauri/tauri.conf.json index 4ae174e8e124..f5b75e3eb055 100644 --- a/crates/tauri/test/fixture/src-tauri/tauri.conf.json +++ b/crates/tauri/test/fixture/src-tauri/tauri.conf.json @@ -12,7 +12,8 @@ } ], "security": { - "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost" + "csp": "default-src blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self'; connect-src ipc: http://ipc.localhost", + "headers": null } }, "bundle": { diff --git a/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml b/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml index 095c0bd03802..15b0f050016d 100644 --- a/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml +++ b/examples/api/src-tauri/tauri-plugin-sample/Cargo.toml @@ -8,7 +8,7 @@ links = "tauri-plugin-sample" tauri = { path = "../../../../crates/tauri" } log = "0.4" serde = "1" -thiserror = "1" +thiserror = "2" [build-dependencies] tauri-plugin = { path = "../../../../crates/tauri-plugin", features = [ diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 7a551431760f..d7238884e69f 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## \[2.1.1] + +### Bug Fixes + +- [`7f81f0523`](https://www.github.com/tauri-apps/tauri/commit/7f81f052365675721312aafba297a7b67fb872d2) Fix regression in `toLogical` and `toPhysical` for position types in `dpi` module returning incorrect `y` value. +- [`e8a50f6d7`](https://www.github.com/tauri-apps/tauri/commit/e8a50f6d760fad4529e7abb400302a1b487f11dd) ([#11645](https://www.github.com/tauri-apps/tauri/pull/11645)) Fix integer values of `BasDirectory.Home` and `BaseDirectory.Font` regression which broke path APIs in JS. + +## \[2.1.0] + +### New Features + +- [`5c4b83084`](https://www.github.com/tauri-apps/tauri/commit/5c4b830843ab085f8ff9db9e08d832223b027e4e) ([#11191](https://www.github.com/tauri-apps/tauri/pull/11191) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Improved support for `dpi` module types to allow these types to be used without manual conversions with `invoke`: + + - Added `SERIALIZE_TO_IPC_FN` const in `core` module which can be used to implement custom IPC serialization for types passed to `invoke`. + - Added `Size` and `Position` classes in `dpi` module. + - Implementd `SERIALIZE_TO_IPC_FN` method on `PhysicalSize`, `PhysicalPosition`, `LogicalSize` and `LogicalPosition` to convert it into a valid IPC-compatible value that can be deserialized correctly on the Rust side into its equivalent struct. +- [`4d545ab3c`](https://www.github.com/tauri-apps/tauri/commit/4d545ab3ca228c8a21b966b709f84a0da2864479) ([#11486](https://www.github.com/tauri-apps/tauri/pull/11486) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `Webview::setBackgroundColor`, `WebviewWindow::setBackgroundColor` APIs to set the window background color dynamically + and a `backgroundColor` window option to set the background color on window creation. +- [`cbc095ec5`](https://www.github.com/tauri-apps/tauri/commit/cbc095ec5fe7de29b5c9265576d4e071ec159c1c) ([#11451](https://www.github.com/tauri-apps/tauri/pull/11451) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `app > windows > devtools` config option and when creating the webview from JS, to enable or disable devtools for a specific webview. +- [`2a75c64b5`](https://www.github.com/tauri-apps/tauri/commit/2a75c64b5431284e7340e8743d4ea56a62c75466) ([#11469](https://www.github.com/tauri-apps/tauri/pull/11469) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Added `windowClassname` option, when constructing a `Webview` or `WebviewWindow`, to specify the name of the window class on Windows. + +### Bug Fixes + +- [`54cbf59b5`](https://www.github.com/tauri-apps/tauri/commit/54cbf59b5a572570a47237a3b5e6505f2a9e5d5d) ([#11441](https://www.github.com/tauri-apps/tauri/pull/11441) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix submenu created as a menu item instead of a submenu when created by using an object in the `items` field in the options object passed to `Menu.new` or `Submenu.new`. + ## \[2.0.3] ### Bug Fixes diff --git a/packages/api/eslint.config.js b/packages/api/eslint.config.js index 71aecc1136eb..803c0178107a 100644 --- a/packages/api/eslint.config.js +++ b/packages/api/eslint.config.js @@ -8,7 +8,7 @@ import prettierConfig from 'eslint-config-prettier' import securityPlugin from 'eslint-plugin-security' import tseslint from 'typescript-eslint' -/** @type {import('eslint').Linter.FlatConfig[]} */ +/** @type {import('eslint').Linter.Config} */ export default [ eslint.configs.recommended, prettierConfig, diff --git a/packages/api/package.json b/packages/api/package.json index 6cc4702fd46f..e861252f8518 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/api", - "version": "2.0.3", + "version": "2.1.1", "description": "Tauri API definitions", "funding": { "type": "opencollective", @@ -40,21 +40,21 @@ "npm-pack": "pnpm build && cd ./dist && npm pack", "npm-publish": "pnpm build && cd ./dist && pnpm publish --access public --loglevel silly --no-git-checks", "ts:check": "tsc --noEmit", - "eslint:check": "eslint src/**.ts", - "eslint:fix": "eslint src/**.ts --fix" + "eslint:check": "eslint src/**/*.ts", + "eslint:fix": "eslint src/**/*.ts --fix" }, "devDependencies": { "@eslint/js": "^9.4.0", "@rollup/plugin-terser": "0.4.4", "@rollup/plugin-typescript": "12.1.1", "@types/eslint": "^9.0.0", - "@types/node": "20.17.0", + "@types/node": "22.9.0", "eslint": "^9.4.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-security": "3.0.1", "fast-glob": "3.3.2", "globals": "^15.4.0", - "rollup": "4.24.0", + "rollup": "4.24.4", "tslib": "^2.6.3", "typescript": "^5.4.5", "typescript-eslint": "^8.1.0" diff --git a/packages/api/src/core.ts b/packages/api/src/core.ts index 5986e68abb72..995e5c07b21a 100644 --- a/packages/api/src/core.ts +++ b/packages/api/src/core.ts @@ -9,6 +9,56 @@ * @module */ +/** + * A key to be used to implement a special function + * on your types that define how your type should be serialized + * when passing across the IPC. + * @example + * Given a type in Rust that looks like this + * ```rs + * #[derive(serde::Serialize, serde::Deserialize) + * enum UserId { + * String(String), + * Number(u32), + * } + * ``` + * `UserId::String("id")` would be serialized into `{ String: "id" }` + * and so we need to pass the same structure back to Rust + * ```ts + * import { SERIALIZE_TO_IPC_FN } from "@tauri-apps/api/core" + * + * class UserIdString { + * id + * constructor(id) { + * this.id = id + * } + * + * [SERIALIZE_TO_IPC_FN]() { + * return { String: this.id } + * } + * } + * + * class UserIdNumber { + * id + * constructor(id) { + * this.id = id + * } + * + * [SERIALIZE_TO_IPC_FN]() { + * return { Number: this.id } + * } + * } + * + * + * type UserId = UserIdString | UserIdNumber + * ``` + * + */ +// if this value changes, make sure to update it in: +// 1. ipc.js +// 2. process-ipc-message-fn.js +export const SERIALIZE_TO_IPC_FN = '__TAURI_TO_IPC_KEY__' + /** * Transforms a callback function to a string identifier that can be passed to the backend. * The backend uses the identifier to `eval()` the callback. @@ -80,9 +130,14 @@ class Channel { return this.#onmessage } - toJSON(): string { + [SERIALIZE_TO_IPC_FN]() { return `__CHANNEL__:${this.id}` } + + toJSON(): string { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } } class PluginListener { diff --git a/packages/api/src/dpi.ts b/packages/api/src/dpi.ts index e82d7b6add41..714694d60afb 100644 --- a/packages/api/src/dpi.ts +++ b/packages/api/src/dpi.ts @@ -2,26 +2,48 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +import { SERIALIZE_TO_IPC_FN } from './core' + /** * A size represented in logical pixels. * * @since 2.0.0 */ class LogicalSize { - type = 'Logical' + readonly type = 'Logical' width: number height: number - constructor(width: number, height: number) { - this.width = width - this.height = height + constructor(width: number, height: number) + constructor(object: { Logical: { width: number; height: number } }) + constructor(object: { width: number; height: number }) + constructor( + ...args: + | [number, number] + | [{ width: number; height: number }] + | [{ Logical: { width: number; height: number } }] + ) { + if (args.length === 1) { + if ('Logical' in args[0]) { + this.width = args[0].Logical.width + this.height = args[0].Logical.height + } else { + this.width = args[0].width + this.height = args[0].height + } + } else { + this.width = args[0] + this.height = args[1] + } } /** * Converts the logical size to a physical one. * @example * ```typescript + * import { LogicalSize } from '@tauri-apps/api/dpi'; * import { getCurrentWindow } from '@tauri-apps/api/window'; + * * const appWindow = getCurrentWindow(); * const factor = await appWindow.scaleFactor(); * const size = new LogicalSize(400, 500); @@ -33,6 +55,18 @@ class LogicalSize { toPhysical(scaleFactor: number): PhysicalSize { return new PhysicalSize(this.width * scaleFactor, this.height * scaleFactor) } + + [SERIALIZE_TO_IPC_FN]() { + return { + width: this.width, + height: this.height + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } } /** @@ -41,13 +75,31 @@ class LogicalSize { * @since 2.0.0 */ class PhysicalSize { - type = 'Physical' + readonly type = 'Physical' width: number height: number - constructor(width: number, height: number) { - this.width = width - this.height = height + constructor(width: number, height: number) + constructor(object: { Physical: { width: number; height: number } }) + constructor(object: { width: number; height: number }) + constructor( + ...args: + | [number, number] + | [{ width: number; height: number }] + | [{ Physical: { width: number; height: number } }] + ) { + if (args.length === 1) { + if ('Physical' in args[0]) { + this.width = args[0].Physical.width + this.height = args[0].Physical.height + } else { + this.width = args[0].width + this.height = args[0].height + } + } else { + this.width = args[0] + this.height = args[1] + } } /** @@ -57,13 +109,90 @@ class PhysicalSize { * import { getCurrentWindow } from '@tauri-apps/api/window'; * const appWindow = getCurrentWindow(); * const factor = await appWindow.scaleFactor(); - * const size = await appWindow.innerSize(); + * const size = await appWindow.innerSize(); // PhysicalSize * const logical = size.toLogical(factor); * ``` */ toLogical(scaleFactor: number): LogicalSize { return new LogicalSize(this.width / scaleFactor, this.height / scaleFactor) } + + [SERIALIZE_TO_IPC_FN]() { + return { + width: this.width, + height: this.height + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } +} + +/** + * A size represented either in physical or in logical pixels. + * + * This type is basically a union type of {@linkcode LogicalSize} and {@linkcode PhysicalSize} + * but comes in handy when using `tauri::Size` in Rust as an argument to a command, as this class + * automatically serializes into a valid format so it can be deserialized correctly into `tauri::Size` + * + * So instead of + * ```typescript + * import { invoke } from '@tauri-apps/api/core'; + * import { LogicalSize, PhysicalSize } from '@tauri-apps/api/dpi'; + * + * const size: LogicalSize | PhysicalSize = someFunction(); // where someFunction returns either LogicalSize or PhysicalSize + * const validSize = size instanceof LogicalSize + * ? { Logical: { width: size.width, height: size.height } } + * : { Physical: { width: size.width, height: size.height } } + * await invoke("do_something_with_size", { size: validSize }); + * ``` + * + * You can just use {@linkcode Size} + * ```typescript + * import { invoke } from '@tauri-apps/api/core'; + * import { LogicalSize, PhysicalSize, Size } from '@tauri-apps/api/dpi'; + * + * const size: LogicalSize | PhysicalSize = someFunction(); // where someFunction returns either LogicalSize or PhysicalSize + * const validSize = new Size(size); + * await invoke("do_something_with_size", { size: validSize }); + * ``` + * + * @since 2.1.0 + */ +class Size { + size: LogicalSize | PhysicalSize + + constructor(size: LogicalSize | PhysicalSize) { + this.size = size + } + + toLogical(scaleFactor: number): LogicalSize { + return this.size instanceof LogicalSize + ? this.size + : this.size.toLogical(scaleFactor) + } + + toPhysical(scaleFactor: number): PhysicalSize { + return this.size instanceof PhysicalSize + ? this.size + : this.size.toPhysical(scaleFactor) + } + + [SERIALIZE_TO_IPC_FN]() { + return { + [`${this.size.type}`]: { + width: this.size.width, + height: this.size.height + } + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } } /** @@ -72,20 +201,40 @@ class PhysicalSize { * @since 2.0.0 */ class LogicalPosition { - type = 'Logical' + readonly type = 'Logical' x: number y: number - constructor(x: number, y: number) { - this.x = x - this.y = y + constructor(x: number, y: number) + constructor(object: { Logical: { x: number; y: number } }) + constructor(object: { x: number; y: number }) + constructor( + ...args: + | [number, number] + | [{ x: number; y: number }] + | [{ Logical: { x: number; y: number } }] + ) { + if (args.length === 1) { + if ('Logical' in args[0]) { + this.x = args[0].Logical.x + this.y = args[0].Logical.y + } else { + this.x = args[0].x + this.y = args[0].y + } + } else { + this.x = args[0] + this.y = args[1] + } } /** * Converts the logical position to a physical one. * @example * ```typescript + * import { LogicalPosition } from '@tauri-apps/api/dpi'; * import { getCurrentWindow } from '@tauri-apps/api/window'; + * * const appWindow = getCurrentWindow(); * const factor = await appWindow.scaleFactor(); * const position = new LogicalPosition(400, 500); @@ -95,7 +244,19 @@ class LogicalPosition { * @since 2.0.0 */ toPhysical(scaleFactor: number): PhysicalPosition { - return new PhysicalPosition(this.x * scaleFactor, this.x * scaleFactor) + return new PhysicalPosition(this.x * scaleFactor, this.y * scaleFactor) + } + + [SERIALIZE_TO_IPC_FN]() { + return { + x: this.x, + y: this.y + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() } } @@ -105,29 +266,135 @@ class LogicalPosition { * @since 2.0.0 */ class PhysicalPosition { - type = 'Physical' + readonly type = 'Physical' x: number y: number - constructor(x: number, y: number) { - this.x = x - this.y = y + constructor(x: number, y: number) + constructor(object: { Physical: { x: number; y: number } }) + constructor(object: { x: number; y: number }) + constructor( + ...args: + | [number, number] + | [{ x: number; y: number }] + | [{ Physical: { x: number; y: number } }] + ) { + if (args.length === 1) { + if ('Physical' in args[0]) { + this.x = args[0].Physical.x + this.y = args[0].Physical.y + } else { + this.x = args[0].x + this.y = args[0].y + } + } else { + this.x = args[0] + this.y = args[1] + } } /** * Converts the physical position to a logical one. * @example * ```typescript + * import { PhysicalPosition } from '@tauri-apps/api/dpi'; * import { getCurrentWindow } from '@tauri-apps/api/window'; + * * const appWindow = getCurrentWindow(); * const factor = await appWindow.scaleFactor(); - * const position = await appWindow.innerPosition(); - * const logical = position.toLogical(factor); + * const position = new PhysicalPosition(400, 500); + * const physical = position.toLogical(factor); * ``` + * + * @since 2.0.0 */ toLogical(scaleFactor: number): LogicalPosition { return new LogicalPosition(this.x / scaleFactor, this.y / scaleFactor) } + + [SERIALIZE_TO_IPC_FN]() { + return { + x: this.x, + y: this.y + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } } -export { LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize } +/** + * A position represented either in physical or in logical pixels. + * + * This type is basically a union type of {@linkcode LogicalSize} and {@linkcode PhysicalSize} + * but comes in handy when using `tauri::Position` in Rust as an argument to a command, as this class + * automatically serializes into a valid format so it can be deserialized correctly into `tauri::Position` + * + * So instead of + * ```typescript + * import { invoke } from '@tauri-apps/api/core'; + * import { LogicalPosition, PhysicalPosition } from '@tauri-apps/api/dpi'; + * + * const position: LogicalPosition | PhysicalPosition = someFunction(); // where someFunction returns either LogicalPosition or PhysicalPosition + * const validPosition = position instanceof LogicalPosition + * ? { Logical: { x: position.x, y: position.y } } + * : { Physical: { x: position.x, y: position.y } } + * await invoke("do_something_with_position", { position: validPosition }); + * ``` + * + * You can just use {@linkcode Position} + * ```typescript + * import { invoke } from '@tauri-apps/api/core'; + * import { LogicalPosition, PhysicalPosition, Position } from '@tauri-apps/api/dpi'; + * + * const position: LogicalPosition | PhysicalPosition = someFunction(); // where someFunction returns either LogicalPosition or PhysicalPosition + * const validPosition = new Position(position); + * await invoke("do_something_with_position", { position: validPosition }); + * ``` + * + * @since 2.1.0 + */ +class Position { + position: LogicalPosition | PhysicalPosition + + constructor(position: LogicalPosition | PhysicalPosition) { + this.position = position + } + + toLogical(scaleFactor: number): LogicalPosition { + return this.position instanceof LogicalPosition + ? this.position + : this.position.toLogical(scaleFactor) + } + + toPhysical(scaleFactor: number): PhysicalPosition { + return this.position instanceof PhysicalPosition + ? this.position + : this.position.toPhysical(scaleFactor) + } + + [SERIALIZE_TO_IPC_FN]() { + return { + [`${this.position.type}`]: { + x: this.position.x, + y: this.position.y + } + } + } + + toJSON() { + // eslint-disable-next-line security/detect-object-injection + return this[SERIALIZE_TO_IPC_FN]() + } +} + +export { + LogicalPosition, + LogicalSize, + Size, + PhysicalPosition, + PhysicalSize, + Position +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 11ddc5e4670f..03639e81c688 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -10,6 +10,16 @@ * ```typescript * import { event, window, path } from '@tauri-apps/api' * ``` + * + * ### Vanilla JS API + * + * The above import syntax is for JavaScript/TypeScript with a bundler. If you're using vanilla JavaScript, you can use the global `window.__TAURI__` object instead. It requires `app.withGlobalTauri` configuration option enabled. + * + * @example + * ```js + * const { event, window: tauriWindow, path } = window.__TAURI__; + * ``` + * * @module */ diff --git a/packages/api/src/menu/menu.ts b/packages/api/src/menu/menu.ts index ae16701d438c..0b703c965125 100644 --- a/packages/api/src/menu/menu.ts +++ b/packages/api/src/menu/menu.ts @@ -14,7 +14,7 @@ import { CheckMenuItem } from './checkMenuItem' import { IconMenuItem } from './iconMenuItem' import { PredefinedMenuItem } from './predefinedMenuItem' import { Submenu } from './submenu' -import { type LogicalPosition, PhysicalPosition } from '../dpi' +import { type LogicalPosition, PhysicalPosition, Position } from '../dpi' import { type Window } from '../window' import { invoke } from '../core' import { type ItemKind, MenuItemBase, newMenu } from './base' @@ -244,22 +244,14 @@ export class Menu extends MenuItemBase { * If the position, is provided, it is relative to the window's top-left corner. */ async popup( - at?: PhysicalPosition | LogicalPosition, + at?: PhysicalPosition | LogicalPosition | Position, window?: Window ): Promise { - let atValue = null - if (at) { - atValue = {} as Record - atValue[`${at instanceof PhysicalPosition ? 'Physical' : 'Logical'}`] = { - x: at.x, - y: at.y - } - } return invoke('plugin:menu|popup', { rid: this.rid, kind: this.kind, window: window?.label ?? null, - at: atValue + at: at instanceof Position ? at : at ? new Position(at) : null }) } diff --git a/packages/api/src/menu/submenu.ts b/packages/api/src/menu/submenu.ts index edfd090d1246..ca594a69fdbe 100644 --- a/packages/api/src/menu/submenu.ts +++ b/packages/api/src/menu/submenu.ts @@ -15,6 +15,7 @@ import { invoke } from '../core' import { type LogicalPosition, PhysicalPosition, type Window } from '../window' import { type ItemKind, MenuItemBase, newMenu } from './base' import { type MenuOptions } from './menu' +import { Position } from '../dpi' function itemFromKind([rid, id, kind]: [number, string, ItemKind]): | Submenu @@ -243,19 +244,11 @@ export class Submenu extends MenuItemBase { at?: PhysicalPosition | LogicalPosition, window?: Window ): Promise { - let atValue = null - if (at) { - atValue = {} as Record - atValue[`${at instanceof PhysicalPosition ? 'Physical' : 'Logical'}`] = { - x: at.x, - y: at.y - } - } return invoke('plugin:menu|popup', { rid: this.rid, kind: this.kind, window: window?.label ?? null, - at: atValue + at: at instanceof Position ? at : at ? new Position(at) : null }) } diff --git a/packages/api/src/path.ts b/packages/api/src/path.ts index dd81affd49eb..9e8e5e034756 100644 --- a/packages/api/src/path.ts +++ b/packages/api/src/path.ts @@ -18,29 +18,28 @@ import { invoke } from './core' */ enum BaseDirectory { Audio = 1, - Cache, - Config, - Data, - LocalData, - Document, - Download, - Picture, - Public, - Video, - Resource, - Temp, - AppConfig, - AppData, - AppLocalData, - AppCache, - AppLog, - - Desktop, - Executable, - Font, - Home, - Runtime, - Template + Cache = 2, + Config = 3, + Data = 4, + LocalData = 5, + Document = 6, + Download = 7, + Picture = 8, + Public = 9, + Video = 10, + Resource = 11, + Temp = 12, + AppConfig = 13, + AppData = 14, + AppLocalData = 15, + AppCache = 16, + AppLog = 17, + Desktop = 18, + Executable = 19, + Font = 20, + Home = 21, + Runtime = 22, + Template = 23 } /** diff --git a/packages/api/src/tray.ts b/packages/api/src/tray.ts index b919d8fc6124..91f748bc5a68 100644 --- a/packages/api/src/tray.ts +++ b/packages/api/src/tray.ts @@ -290,16 +290,9 @@ export class TrayIcon extends Resource { function mapEvent(e: RustTrayIconEvent): TrayIconEvent { const out = e as unknown as TrayIconEvent - out.position = new PhysicalPosition(e.position.x, e.position.y) - - out.rect.position = new PhysicalPosition( - e.rect.position.Physical.x, - e.rect.position.Physical.y - ) - out.rect.size = new PhysicalSize( - e.rect.size.Physical.width, - e.rect.size.Physical.height - ) + out.position = new PhysicalPosition(e.position) + out.rect.position = new PhysicalPosition(e.rect.position) + out.rect.size = new PhysicalSize(e.rect.size) return out } diff --git a/packages/api/src/webview.ts b/packages/api/src/webview.ts index 5b16f058b48e..28bed5b3fed8 100644 --- a/packages/api/src/webview.ts +++ b/packages/api/src/webview.ts @@ -18,6 +18,7 @@ import { PhysicalPosition, PhysicalSize } from './dpi' import type { LogicalPosition, LogicalSize } from './dpi' +import { Position, Size } from './dpi' import type { EventName, EventCallback, UnlistenFn } from './event' import { TauriEvent, @@ -29,7 +30,7 @@ import { once } from './event' import { invoke } from './core' -import { Window, getCurrentWindow } from './window' +import { Color, Window, getCurrentWindow } from './window' import { WebviewWindow } from './webviewWindow' /** The drag and drop event types. */ @@ -361,7 +362,7 @@ class Webview { async position(): Promise { return invoke<{ x: number; y: number }>('plugin:webview|webview_position', { label: this.label - }).then(({ x, y }) => new PhysicalPosition(x, y)) + }).then((p) => new PhysicalPosition(p)) } /** @@ -381,7 +382,7 @@ class Webview { { label: this.label } - ).then(({ width, height }) => new PhysicalSize(width, height)) + ).then((s) => new PhysicalSize(s)) } // Setters @@ -413,22 +414,10 @@ class Webview { * @param size The logical or physical size. * @returns A promise indicating the success or failure of the operation. */ - async setSize(size: LogicalSize | PhysicalSize): Promise { - if (!size || (size.type !== 'Logical' && size.type !== 'Physical')) { - throw new Error( - 'the `size` argument must be either a LogicalSize or a PhysicalSize instance' - ) - } - - const value = {} as Record - value[`${size.type}`] = { - width: size.width, - height: size.height - } - + async setSize(size: LogicalSize | PhysicalSize | Size): Promise { return invoke('plugin:webview|set_webview_size', { label: this.label, - value + value: size instanceof Size ? size : new Size(size) }) } @@ -444,26 +433,11 @@ class Webview { * @returns A promise indicating the success or failure of the operation. */ async setPosition( - position: LogicalPosition | PhysicalPosition + position: LogicalPosition | PhysicalPosition | Position ): Promise { - if ( - !position || - (position.type !== 'Logical' && position.type !== 'Physical') - ) { - throw new Error( - 'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance' - ) - } - - const value = {} as Record - value[`${position.type}`] = { - x: position.x, - y: position.y - } - return invoke('plugin:webview|set_webview_position', { label: this.label, - value + value: position instanceof Position ? position : new Position(position) }) } @@ -563,6 +537,24 @@ class Webview { return invoke('plugin:webview|clear_all_browsing_data') } + /** + * Specify the webview background color. + * + * #### Platfrom-specific: + * + * - **macOS / iOS**: Not implemented. + * - **Windows**: + * - On Windows 7, transparency is not supported and the alpha value will be ignored. + * - On Windows higher than 7: translucent colors are not supported so any alpha value other than `0` will be replaced by `255` + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.1.0 + */ + async setBackgroundColor(color: Color | null): Promise { + return invoke('plugin:webview|set_webview_background_color', { color }) + } + // Listeners /** @@ -574,8 +566,8 @@ class Webview { * ```typescript * import { getCurrentWebview } from "@tauri-apps/api/webview"; * const unlisten = await getCurrentWebview().onDragDropEvent((event) => { - * if (event.payload.type === 'hover') { - * console.log('User hovering', event.payload.paths); + * if (event.payload.type === 'over') { + * console.log('User hovering', event.payload.position); * } else if (event.payload.type === 'drop') { * console.log('User dropped', event.payload.paths); * } else { @@ -603,7 +595,7 @@ class Webview { payload: { type: 'enter', paths: event.payload.paths, - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -616,7 +608,7 @@ class Webview { ...event, payload: { type: 'over', - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -630,7 +622,7 @@ class Webview { payload: { type: 'drop', paths: event.payload.paths, - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -652,10 +644,6 @@ class Webview { } } -function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition { - return new PhysicalPosition(m.x, m.y) -} - /** * Configuration for the webview to create. * @@ -684,6 +672,12 @@ interface WebviewOptions { * WARNING: Using private APIs on `macOS` prevents your application from being accepted to the `App Store`. */ transparent?: boolean + /** + * Whether the webview should have focus or not + * + * @since 2.1.0 + */ + focus?: boolean /** * Whether the drag and drop is enabled or not on the webview. By default it is enabled. * @@ -728,8 +722,50 @@ interface WebviewOptions { * - **Android / iOS**: Unsupported. */ zoomHotkeysEnabled?: boolean + + /** + * Sets whether the custom protocols should use `https://.localhost` instead of the default `http://.localhost` on Windows and Android. Defaults to `false`. + * + * #### Note + * + * Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `://localhost` protocols used on macOS and Linux. + * + * #### Warning + * + * Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access them. + * + * @since 2.1.0 + */ + useHttpsScheme?: boolean + /** + * Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default. + * + * This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds. + * + * #### Platform-specific + * + * - macOS: This will call private functions on **macOS**. + * - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android. + * - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window. + * + * @since 2.1.0 + */ + devtools?: boolean + /** + * Set the window and webview background color. + * + * #### Platform-specific: + * + * - **macOS / iOS**: Not implemented. + * - **Windows**: + * - On Windows 7, alpha channel is ignored. + * - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + * + * @since 2.1.0 + */ + backgroundColor?: Color } export { Webview, getCurrentWebview, getAllWebviews } -export type { DragDropEvent, WebviewOptions } +export type { DragDropEvent, WebviewOptions, Color } diff --git a/packages/api/src/webviewWindow.ts b/packages/api/src/webviewWindow.ts index a103dd19ee6f..d2bfab8c28fc 100644 --- a/packages/api/src/webviewWindow.ts +++ b/packages/api/src/webviewWindow.ts @@ -13,7 +13,7 @@ import { Window } from './window' import { listen, once } from './event' import type { EventName, EventCallback, UnlistenFn } from './event' import { invoke } from './core' -import type { DragDropEvent } from './webview' +import type { Color, DragDropEvent } from './webview' /** * Get an instance of `Webview` for the current webview window. @@ -202,6 +202,28 @@ class WebviewWindow { target: { kind: 'WebviewWindow', label: this.label } }) } + + /** + * Set the window and webview background color. + * + * #### Platform-specific: + * + * - **Android / iOS:** Unsupported for the window layer. + * - **macOS / iOS**: Not implemented for the webview layer. + * - **Windows**: + * - alpha channel is ignored for the window layer. + * - On Windows 7, alpha channel is ignored for the webview layer. + * - On Windows 8 and newer, if alpha channel is not `0`, it will be ignored. + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.1.0 + */ + async setBackgroundColor(color: Color): Promise { + return invoke('plugin:window|set_background_color', { color }).then(() => { + return invoke('plugin:webview|set_webview_background_color', { color }) + }) + } } // Order matters, we use window APIs by default @@ -235,4 +257,4 @@ function applyMixins( } export { WebviewWindow, getCurrentWebviewWindow, getAllWebviewWindows } -export type { DragDropEvent } +export type { DragDropEvent, Color } diff --git a/packages/api/src/window.ts b/packages/api/src/window.ts index 923b5a3dc8c1..d9a02879c219 100644 --- a/packages/api/src/window.ts +++ b/packages/api/src/window.ts @@ -20,7 +20,9 @@ import { LogicalPosition, LogicalSize, PhysicalPosition, - PhysicalSize + PhysicalSize, + Position, + Size } from './dpi' import type { Event, EventName, EventCallback, UnlistenFn } from './event' import { @@ -530,7 +532,7 @@ class Window { async innerPosition(): Promise { return invoke<{ x: number; y: number }>('plugin:window|inner_position', { label: this.label - }).then(({ x, y }) => new PhysicalPosition(x, y)) + }).then((p) => new PhysicalPosition(p)) } /** @@ -546,7 +548,7 @@ class Window { async outerPosition(): Promise { return invoke<{ x: number; y: number }>('plugin:window|outer_position', { label: this.label - }).then(({ x, y }) => new PhysicalPosition(x, y)) + }).then((p) => new PhysicalPosition(p)) } /** @@ -566,7 +568,7 @@ class Window { { label: this.label } - ).then(({ width, height }) => new PhysicalSize(width, height)) + ).then((s) => new PhysicalSize(s)) } /** @@ -586,7 +588,7 @@ class Window { { label: this.label } - ).then(({ width, height }) => new PhysicalSize(width, height)) + ).then((s) => new PhysicalSize(s)) } /** @@ -1268,22 +1270,10 @@ class Window { * @param size The logical or physical inner size. * @returns A promise indicating the success or failure of the operation. */ - async setSize(size: LogicalSize | PhysicalSize): Promise { - if (!size || (size.type !== 'Logical' && size.type !== 'Physical')) { - throw new Error( - 'the `size` argument must be either a LogicalSize or a PhysicalSize instance' - ) - } - - const value = {} as Record - value[`${size.type}`] = { - width: size.width, - height: size.height - } - + async setSize(size: LogicalSize | PhysicalSize | Size): Promise { return invoke('plugin:window|set_size', { label: this.label, - value + value: size instanceof Size ? size : new Size(size) }) } @@ -1299,26 +1289,11 @@ class Window { * @returns A promise indicating the success or failure of the operation. */ async setMinSize( - size: LogicalSize | PhysicalSize | null | undefined + size: LogicalSize | PhysicalSize | Size | null | undefined ): Promise { - if (size && size.type !== 'Logical' && size.type !== 'Physical') { - throw new Error( - 'the `size` argument must be either a LogicalSize or a PhysicalSize instance' - ) - } - - let value = null as Record | null - if (size) { - value = {} - value[`${size.type}`] = { - width: size.width, - height: size.height - } - } - return invoke('plugin:window|set_min_size', { label: this.label, - value + value: size instanceof Size ? size : size ? new Size(size) : null }) } @@ -1334,26 +1309,11 @@ class Window { * @returns A promise indicating the success or failure of the operation. */ async setMaxSize( - size: LogicalSize | PhysicalSize | null | undefined + size: LogicalSize | PhysicalSize | Size | null | undefined ): Promise { - if (size && size.type !== 'Logical' && size.type !== 'Physical') { - throw new Error( - 'the `size` argument must be either a LogicalSize or a PhysicalSize instance' - ) - } - - let value = null as Record | null - if (size) { - value = {} - value[`${size.type}`] = { - width: size.width, - height: size.height - } - } - return invoke('plugin:window|set_max_size', { label: this.label, - value + value: size instanceof Size ? size : size ? new Size(size) : null }) } @@ -1398,26 +1358,11 @@ class Window { * @returns A promise indicating the success or failure of the operation. */ async setPosition( - position: LogicalPosition | PhysicalPosition + position: LogicalPosition | PhysicalPosition | Position ): Promise { - if ( - !position || - (position.type !== 'Logical' && position.type !== 'Physical') - ) { - throw new Error( - 'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance' - ) - } - - const value = {} as Record - value[`${position.type}`] = { - x: position.x, - y: position.y - } - return invoke('plugin:window|set_position', { label: this.label, - value + value: position instanceof Position ? position : new Position(position) }) } @@ -1572,6 +1517,22 @@ class Window { }) } + /** + * Sets the window background color. + * + * #### Platform-specific: + * + * - **Windows:** alpha channel is ignored. + * - **iOS / Android:** Unsupported. + * + * @returns A promise indicating the success or failure of the operation. + * + * @since 2.1.0 + */ + async setBackgroundColor(color: Color): Promise { + return invoke('plugin:window|set_background_color', { color }) + } + /** * Changes the position of the cursor in window coordinates. * @example @@ -1584,26 +1545,11 @@ class Window { * @returns A promise indicating the success or failure of the operation. */ async setCursorPosition( - position: LogicalPosition | PhysicalPosition + position: LogicalPosition | PhysicalPosition | Position ): Promise { - if ( - !position || - (position.type !== 'Logical' && position.type !== 'Physical') - ) { - throw new Error( - 'the `position` argument must be either a LogicalPosition or a PhysicalPosition instance' - ) - } - - const value = {} as Record - value[`${position.type}`] = { - x: position.x, - y: position.y - } - return invoke('plugin:window|set_cursor_position', { label: this.label, - value + value: position instanceof Position ? position : new Position(position) }) } @@ -1751,7 +1697,7 @@ class Window { */ async onResized(handler: EventCallback): Promise { return this.listen(TauriEvent.WINDOW_RESIZED, (e) => { - e.payload = mapPhysicalSize(e.payload) + e.payload = new PhysicalSize(e.payload) handler(e) }) } @@ -1775,7 +1721,7 @@ class Window { */ async onMoved(handler: EventCallback): Promise { return this.listen(TauriEvent.WINDOW_MOVED, (e) => { - e.payload = mapPhysicalPosition(e.payload) + e.payload = new PhysicalPosition(e.payload) handler(e) }) } @@ -1824,8 +1770,8 @@ class Window { * ```typescript * import { getCurrentWindow } from "@tauri-apps/api/webview"; * const unlisten = await getCurrentWindow().onDragDropEvent((event) => { - * if (event.payload.type === 'hover') { - * console.log('User hovering', event.payload.paths); + * if (event.payload.type === 'over') { + * console.log('User hovering', event.payload.position); * } else if (event.payload.type === 'drop') { * console.log('User dropped', event.payload.paths); * } else { @@ -1853,7 +1799,7 @@ class Window { payload: { type: 'enter', paths: event.payload.paths, - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -1866,7 +1812,7 @@ class Window { ...event, payload: { type: 'over', - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -1880,7 +1826,7 @@ class Window { payload: { type: 'drop', paths: event.payload.paths, - position: mapPhysicalPosition(event.payload.position) + position: new PhysicalPosition(event.payload.position) } }) } @@ -1990,11 +1936,17 @@ class Window { } /** - * an array RGBA colors. Each value has minimum of 0 and maximum of 255. + * An RGBA color. Each value has minimum of 0 and maximum of 255. + * + * It can be either a string `#ffffff`, an array of 3 or 4 elements or an object. * * @since 2.0.0 */ -type Color = [number, number, number, number] +type Color = + | [number, number, number] + | [number, number, number, number] + | { red: number; green: number; blue: number; alpha: number } + | string /** * Platform-specific window effects @@ -2291,6 +2243,28 @@ interface WindowOptions { * @since 2.0.0 */ visibleOnAllWorkspaces?: boolean + /** + * Window effects. + * + * Requires the window to be transparent. + * + * #### Platform-specific: + * + * - **Windows**: If using decorations or shadows, you may want to try this workaround + * - **Linux**: Unsupported + */ + windowEffects?: Effects + /** + * Set the window background color. + * + * #### Platform-specific: + * + * - **Android / iOS:** Unsupported. + * - **Windows**: alpha channel is ignored. + * + * @since 2.1.0 + */ + backgroundColor?: Color } function mapMonitor(m: Monitor | null): Monitor | null { @@ -2299,19 +2273,11 @@ function mapMonitor(m: Monitor | null): Monitor | null { : { name: m.name, scaleFactor: m.scaleFactor, - position: mapPhysicalPosition(m.position), - size: mapPhysicalSize(m.size) + position: new PhysicalPosition(m.position), + size: new PhysicalSize(m.size) } } -function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition { - return new PhysicalPosition(m.x, m.y) -} - -function mapPhysicalSize(m: PhysicalSize): PhysicalSize { - return new PhysicalSize(m.width, m.height) -} - /** * Returns the monitor on which the window currently resides. * Returns `null` if current monitor can't be detected. @@ -2391,7 +2357,7 @@ async function availableMonitors(): Promise { */ async function cursorPosition(): Promise { return invoke('plugin:window|cursor_position').then( - mapPhysicalPosition + (v) => new PhysicalPosition(v) ) } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 1a0b196ccec2..48a57a427eaf 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## \[2.1.0] + +### New Features + +- [`6bf917941`](https://www.github.com/tauri-apps/tauri/commit/6bf917941ff0fcc49e86b3ba427340b75f3ce49c) ([#11322](https://www.github.com/tauri-apps/tauri/pull/11322) by [@ShaunSHamilton](https://www.github.com/tauri-apps/tauri/../../ShaunSHamilton)) Add `tauri remove` to remove plugins from projects. +- [`058c0db72`](https://www.github.com/tauri-apps/tauri/commit/058c0db72f43fbe1574d0db654560e693755cd7e) ([#11584](https://www.github.com/tauri-apps/tauri/pull/11584) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add `bundle > linux > rpm > compression` config option to control RPM bundle compression type and level. + +### Enhancements + +- [`1f311832a`](https://www.github.com/tauri-apps/tauri/commit/1f311832ab5b2d62a533dfcf9b1d78bddf249ae8) ([#11405](https://www.github.com/tauri-apps/tauri/pull/11405) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Add more context for errors when decoding secret and public keys for signing updater artifacts. +- [`e0d1307d3`](https://www.github.com/tauri-apps/tauri/commit/e0d1307d3f78987d0059921a5ab01ea4b26e0ef1) ([#11414](https://www.github.com/tauri-apps/tauri/pull/11414) by [@Czxck001](https://www.github.com/tauri-apps/tauri/../../Czxck001)) Migrate the `$schema` Tauri configuration to the v2 format. +- [`c43d5df15`](https://www.github.com/tauri-apps/tauri/commit/c43d5df15828ecffa606482ea2b60350c488c981) ([#11512](https://www.github.com/tauri-apps/tauri/pull/11512) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Associate a newly created capability file with the `main` window on the `tauri add` and `tauri permission add` commands. + +### Bug Fixes + +- [`7af01ff2c`](https://www.github.com/tauri-apps/tauri/commit/7af01ff2ce623d727cd13a4c8a549c1c80031882) ([#11523](https://www.github.com/tauri-apps/tauri/pull/11523) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix `tauri migrate` failing to install NPM depenencies when running from Deno. +- [`100a4455a`](https://www.github.com/tauri-apps/tauri/commit/100a4455aa48df508510bbc08273215bdf70c012) ([#11529](https://www.github.com/tauri-apps/tauri/pull/11529) by [@amrbashir](https://www.github.com/tauri-apps/tauri/../../amrbashir)) Fix detecting yarn berry (v2 and higher) in various tauri cli commands. +- [`60e86d5f6`](https://www.github.com/tauri-apps/tauri/commit/60e86d5f6e0f0c769d34ef368cd8801a918d796d) ([#11624](https://www.github.com/tauri-apps/tauri/pull/11624) by [@lucasfernog](https://www.github.com/tauri-apps/tauri/../../lucasfernog)) Use the public network IP address on `android dev` by default on Windows. + +### Dependencies + +- Upgraded to `tauri-cli@2.1.0` + ## \[2.0.4] ### Enhancements diff --git a/packages/cli/package.json b/packages/cli/package.json index 7a68df5a3d67..14fe412a091b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@tauri-apps/cli", - "version": "2.0.4", + "version": "2.1.0", "description": "Command line interface for building Tauri apps", "funding": { "type": "opencollective", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e1c4b2dab5c..1b12458e13c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,7 +39,7 @@ importers: version: 1.1.14 '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + version: 3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) '@unocss/extractor-svelte': specifier: ^0.61.0 version: 0.61.9 @@ -48,10 +48,10 @@ importers: version: 4.2.19 unocss: specifier: ^0.61.0 - version: 0.61.9(postcss@8.4.47)(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + version: 0.61.9(postcss@8.4.47)(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) vite: specifier: ^5.4.7 - version: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + version: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) examples/file-associations: {} @@ -61,25 +61,25 @@ importers: devDependencies: '@eslint/js': specifier: ^9.4.0 - version: 9.13.0 + version: 9.14.0 '@rollup/plugin-terser': specifier: 0.4.4 - version: 0.4.4(rollup@4.24.0) + version: 0.4.4(rollup@4.24.4) '@rollup/plugin-typescript': specifier: 12.1.1 - version: 12.1.1(rollup@4.24.0)(tslib@2.8.0)(typescript@5.6.3) + version: 12.1.1(rollup@4.24.4)(tslib@2.8.1)(typescript@5.6.3) '@types/eslint': specifier: ^9.0.0 version: 9.6.1 '@types/node': - specifier: 20.17.0 - version: 20.17.0 + specifier: 22.9.0 + version: 22.9.0 eslint: specifier: ^9.4.0 - version: 9.13.0(jiti@1.21.6) + version: 9.14.0(jiti@1.21.6) eslint-config-prettier: specifier: 9.1.0 - version: 9.1.0(eslint@9.13.0(jiti@1.21.6)) + version: 9.1.0(eslint@9.14.0(jiti@1.21.6)) eslint-plugin-security: specifier: 3.0.1 version: 3.0.1 @@ -88,19 +88,19 @@ importers: version: 3.3.2 globals: specifier: ^15.4.0 - version: 15.11.0 + version: 15.12.0 rollup: - specifier: 4.24.0 - version: 4.24.0 + specifier: 4.24.4 + version: 4.24.4 tslib: specifier: ^2.6.3 - version: 2.8.0 + version: 2.8.1 typescript: specifier: ^5.4.5 version: 5.6.3 typescript-eslint: specifier: ^8.1.0 - version: 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + version: 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) packages/cli: devDependencies: @@ -728,14 +728,14 @@ packages: cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.1': - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.18.0': @@ -750,28 +750,28 @@ packages: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.13.0': - resolution: {integrity: sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==} + '@eslint/js@9.14.0': + resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.1': - resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==} + '@eslint/plugin-kit@0.2.2': + resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/busboy@2.1.1': resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - '@humanfs/core@0.19.0': - resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.5': - resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -782,6 +782,10 @@ packages: resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + '@iconify-json/codicon@1.1.51': resolution: {integrity: sha512-jNgy3mWL23r5uWdNIijlZvqXY7jARpPayNLl0qBDVfLiBbLHTFM8shPrb41z1usmSPxyKkZctsDmf5gCOzmEbw==} @@ -874,8 +878,8 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm-eabi@4.24.0': - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + '@rollup/rollup-android-arm-eabi@4.24.4': + resolution: {integrity: sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==} cpu: [arm] os: [android] @@ -884,8 +888,8 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.24.0': - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + '@rollup/rollup-android-arm64@4.24.4': + resolution: {integrity: sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==} cpu: [arm64] os: [android] @@ -894,8 +898,8 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-arm64@4.24.0': - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + '@rollup/rollup-darwin-arm64@4.24.4': + resolution: {integrity: sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==} cpu: [arm64] os: [darwin] @@ -904,18 +908,28 @@ packages: cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.24.0': - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + '@rollup/rollup-darwin-x64@4.24.4': + resolution: {integrity: sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==} cpu: [x64] os: [darwin] + '@rollup/rollup-freebsd-arm64@4.24.4': + resolution: {integrity: sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.24.4': + resolution: {integrity: sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.22.4': resolution: {integrity: sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': + resolution: {integrity: sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==} cpu: [arm] os: [linux] @@ -924,8 +938,8 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.24.0': - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + '@rollup/rollup-linux-arm-musleabihf@4.24.4': + resolution: {integrity: sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==} cpu: [arm] os: [linux] @@ -934,8 +948,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.24.0': - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + '@rollup/rollup-linux-arm64-gnu@4.24.4': + resolution: {integrity: sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==} cpu: [arm64] os: [linux] @@ -944,8 +958,8 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.24.0': - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + '@rollup/rollup-linux-arm64-musl@4.24.4': + resolution: {integrity: sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==} cpu: [arm64] os: [linux] @@ -954,8 +968,8 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': + resolution: {integrity: sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==} cpu: [ppc64] os: [linux] @@ -964,8 +978,8 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.24.0': - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + '@rollup/rollup-linux-riscv64-gnu@4.24.4': + resolution: {integrity: sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==} cpu: [riscv64] os: [linux] @@ -974,8 +988,8 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.24.0': - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + '@rollup/rollup-linux-s390x-gnu@4.24.4': + resolution: {integrity: sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==} cpu: [s390x] os: [linux] @@ -984,8 +998,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.24.0': - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + '@rollup/rollup-linux-x64-gnu@4.24.4': + resolution: {integrity: sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==} cpu: [x64] os: [linux] @@ -994,8 +1008,8 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.24.0': - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + '@rollup/rollup-linux-x64-musl@4.24.4': + resolution: {integrity: sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==} cpu: [x64] os: [linux] @@ -1004,8 +1018,8 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.24.0': - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + '@rollup/rollup-win32-arm64-msvc@4.24.4': + resolution: {integrity: sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==} cpu: [arm64] os: [win32] @@ -1014,8 +1028,8 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.24.0': - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + '@rollup/rollup-win32-ia32-msvc@4.24.4': + resolution: {integrity: sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==} cpu: [ia32] os: [win32] @@ -1024,8 +1038,8 @@ packages: cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.24.0': - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + '@rollup/rollup-win32-x64-msvc@4.24.4': + resolution: {integrity: sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==} cpu: [x64] os: [win32] @@ -1062,11 +1076,11 @@ packages: '@types/node@20.16.1': resolution: {integrity: sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==} - '@types/node@20.17.0': - resolution: {integrity: sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} - '@typescript-eslint/eslint-plugin@8.11.0': - resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==} + '@typescript-eslint/eslint-plugin@8.13.0': + resolution: {integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -1076,8 +1090,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.11.0': - resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==} + '@typescript-eslint/parser@8.13.0': + resolution: {integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1086,12 +1100,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.11.0': - resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==} + '@typescript-eslint/scope-manager@8.13.0': + resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.11.0': - resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==} + '@typescript-eslint/type-utils@8.13.0': + resolution: {integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -1099,12 +1113,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.11.0': - resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==} + '@typescript-eslint/types@8.13.0': + resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.11.0': - resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==} + '@typescript-eslint/typescript-estree@8.13.0': + resolution: {integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -1112,14 +1126,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.11.0': - resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==} + '@typescript-eslint/utils@8.13.0': + resolution: {integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.11.0': - resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==} + '@typescript-eslint/visitor-keys@8.13.0': + resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unocss/astro@0.61.9': @@ -1260,8 +1274,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.13.0: - resolution: {integrity: sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -1505,20 +1519,20 @@ packages: resolution: {integrity: sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.1.0: - resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.13.0: - resolution: {integrity: sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==} + eslint@9.14.0: + resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1527,8 +1541,8 @@ packages: jiti: optional: true - espree@10.2.0: - resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.6.0: @@ -1643,8 +1657,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.11.0: - resolution: {integrity: sha512-yeyNSjdbyVaWurlwCpcA6XNBrHTMIeDdj0/hnvX/OLJ9ekOXYbLsLinH/MucQyGvNnXhidTdNhTtJaffL2sMfw==} + globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} graphemer@1.4.0: @@ -2008,8 +2022,8 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + rollup@4.24.4: + resolution: {integrity: sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -2164,14 +2178,14 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' - tslib@2.8.0: - resolution: {integrity: sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tsx@4.17.0: resolution: {integrity: sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==} @@ -2182,8 +2196,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.11.0: - resolution: {integrity: sha512-cBRGnW3FSlxaYwU8KfAewxFK5uzeOAp0l2KebIlPDOT5olVi65KDG/yjBooPBG0kGW/HLkoz1c/iuBFehcS3IA==} + typescript-eslint@8.13.0: + resolution: {integrity: sha512-vIMpDRJrQd70au2G8w34mPps0ezFSPMEX4pXkTzUkrNbRX+36ais2ksGWN0esZL+ZMaFJEneOBHzCgSqle7DHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -2848,12 +2862,12 @@ snapshots: '@esbuild/win32-x64@0.23.0': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.13.0(jiti@1.21.6))': + '@eslint-community/eslint-utils@4.4.1(eslint@9.14.0(jiti@1.21.6))': dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.14.0(jiti@1.21.6) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.18.0': dependencies: @@ -2869,7 +2883,7 @@ snapshots: dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 10.2.0 + espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -2879,27 +2893,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.13.0': {} + '@eslint/js@9.14.0': {} '@eslint/object-schema@2.1.4': {} - '@eslint/plugin-kit@0.2.1': + '@eslint/plugin-kit@0.2.2': dependencies: levn: 0.4.1 '@fastify/busboy@2.1.1': {} - '@humanfs/core@0.19.0': {} + '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.5': + '@humanfs/node@0.16.6': dependencies: - '@humanfs/core': 0.19.0 + '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.1': {} + '@humanwhocodes/retry@0.4.1': {} + '@iconify-json/codicon@1.1.51': dependencies: '@iconify/types': 2.0.0 @@ -2965,147 +2981,153 @@ snapshots: '@polka/url@1.0.0-next.25': {} - '@rollup/plugin-terser@0.4.4(rollup@4.24.0)': + '@rollup/plugin-terser@0.4.4(rollup@4.24.4)': dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 terser: 5.31.6 optionalDependencies: - rollup: 4.24.0 + rollup: 4.24.4 - '@rollup/plugin-typescript@12.1.1(rollup@4.24.0)(tslib@2.8.0)(typescript@5.6.3)': + '@rollup/plugin-typescript@12.1.1(rollup@4.24.4)(tslib@2.8.1)(typescript@5.6.3)': dependencies: - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) resolve: 1.22.8 typescript: 5.6.3 optionalDependencies: - rollup: 4.24.0 - tslib: 2.8.0 + rollup: 4.24.4 + tslib: 2.8.1 - '@rollup/pluginutils@5.1.0(rollup@4.24.0)': + '@rollup/pluginutils@5.1.0(rollup@4.24.4)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 picomatch: 2.3.1 optionalDependencies: - rollup: 4.24.0 + rollup: 4.24.4 '@rollup/rollup-android-arm-eabi@4.22.4': optional: true - '@rollup/rollup-android-arm-eabi@4.24.0': + '@rollup/rollup-android-arm-eabi@4.24.4': optional: true '@rollup/rollup-android-arm64@4.22.4': optional: true - '@rollup/rollup-android-arm64@4.24.0': + '@rollup/rollup-android-arm64@4.24.4': optional: true '@rollup/rollup-darwin-arm64@4.22.4': optional: true - '@rollup/rollup-darwin-arm64@4.24.0': + '@rollup/rollup-darwin-arm64@4.24.4': optional: true '@rollup/rollup-darwin-x64@4.22.4': optional: true - '@rollup/rollup-darwin-x64@4.24.0': + '@rollup/rollup-darwin-x64@4.24.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.24.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.24.4': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + '@rollup/rollup-linux-arm-gnueabihf@4.24.4': optional: true '@rollup/rollup-linux-arm-musleabihf@4.22.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.24.0': + '@rollup/rollup-linux-arm-musleabihf@4.24.4': optional: true '@rollup/rollup-linux-arm64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.24.0': + '@rollup/rollup-linux-arm64-gnu@4.24.4': optional: true '@rollup/rollup-linux-arm64-musl@4.22.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.24.0': + '@rollup/rollup-linux-arm64-musl@4.24.4': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.22.4': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.24.4': optional: true '@rollup/rollup-linux-riscv64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.24.0': + '@rollup/rollup-linux-riscv64-gnu@4.24.4': optional: true '@rollup/rollup-linux-s390x-gnu@4.22.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.24.0': + '@rollup/rollup-linux-s390x-gnu@4.24.4': optional: true '@rollup/rollup-linux-x64-gnu@4.22.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.24.0': + '@rollup/rollup-linux-x64-gnu@4.24.4': optional: true '@rollup/rollup-linux-x64-musl@4.22.4': optional: true - '@rollup/rollup-linux-x64-musl@4.24.0': + '@rollup/rollup-linux-x64-musl@4.24.4': optional: true '@rollup/rollup-win32-arm64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.24.0': + '@rollup/rollup-win32-arm64-msvc@4.24.4': optional: true '@rollup/rollup-win32-ia32-msvc@4.22.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.24.0': + '@rollup/rollup-win32-ia32-msvc@4.24.4': optional: true '@rollup/rollup-win32-x64-msvc@4.22.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.24.0': + '@rollup/rollup-win32-x64-msvc@4.24.4': optional: true - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)))(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6))': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)))(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) debug: 4.3.6 svelte: 4.2.19 - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6))': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)))(svelte@4.2.19)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)))(svelte@4.2.19)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 4.2.19 svelte-hmr: 0.16.0(svelte@4.2.19) - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) - vitefu: 0.2.5(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) + vitefu: 0.2.5(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) transitivePeerDependencies: - supports-color @@ -3122,112 +3144,112 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.17.0 + '@types/node': 22.9.0 '@types/node@20.16.1': dependencies: undici-types: 6.19.8 - '@types/node@20.17.0': + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 - '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.11.0 - '@typescript-eslint/type-utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.11.0 - eslint: 9.13.0(jiti@1.21.6) + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/type-utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 + eslint: 9.14.0(jiti@1.21.6) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.11.0 - '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.11.0 + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 debug: 4.3.7 - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.14.0(jiti@1.21.6) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.11.0': + '@typescript-eslint/scope-manager@8.13.0': dependencies: - '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/visitor-keys': 8.11.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 - '@typescript-eslint/type-utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/type-utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) debug: 4.3.7 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.11.0': {} + '@typescript-eslint/types@8.13.0': {} - '@typescript-eslint/typescript-estree@8.11.0(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.13.0(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/visitor-keys': 8.11.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.3) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3)': + '@typescript-eslint/utils@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@1.21.6)) - '@typescript-eslint/scope-manager': 8.11.0 - '@typescript-eslint/types': 8.11.0 - '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) - eslint: 9.13.0(jiti@1.21.6) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + eslint: 9.14.0(jiti@1.21.6) transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.11.0': + '@typescript-eslint/visitor-keys@8.13.0': dependencies: - '@typescript-eslint/types': 8.11.0 + '@typescript-eslint/types': 8.13.0 eslint-visitor-keys: 3.4.3 - '@unocss/astro@0.61.9(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6))': + '@unocss/astro@0.61.9(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6))': dependencies: '@unocss/core': 0.61.9 '@unocss/reset': 0.61.9 - '@unocss/vite': 0.61.9(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + '@unocss/vite': 0.61.9(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) optionalDependencies: - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) transitivePeerDependencies: - rollup - supports-color - '@unocss/cli@0.61.9(rollup@4.24.0)': + '@unocss/cli@0.61.9(rollup@4.24.4)': dependencies: '@ampproject/remapping': 2.3.0 - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) '@unocss/config': 0.61.9 '@unocss/core': 0.61.9 '@unocss/preset-uno': 0.61.9 @@ -3358,10 +3380,10 @@ snapshots: dependencies: '@unocss/core': 0.61.9 - '@unocss/vite@0.61.9(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6))': + '@unocss/vite@0.61.9(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6))': dependencies: '@ampproject/remapping': 2.3.0 - '@rollup/pluginutils': 5.1.0(rollup@4.24.0) + '@rollup/pluginutils': 5.1.0(rollup@4.24.4) '@unocss/config': 0.61.9 '@unocss/core': 0.61.9 '@unocss/inspector': 0.61.9 @@ -3370,7 +3392,7 @@ snapshots: chokidar: 3.6.0 fast-glob: 3.3.2 magic-string: 0.30.11 - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) transitivePeerDependencies: - rollup - supports-color @@ -3419,9 +3441,9 @@ snapshots: dependencies: svelte: 4.2.19 - acorn-jsx@5.3.2(acorn@8.13.0): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.13.0 + acorn: 8.14.0 acorn-walk@8.3.3: dependencies: @@ -3429,7 +3451,7 @@ snapshots: acorn@8.12.1: {} - acorn@8.13.0: {} + acorn@8.14.0: {} ajv@6.12.6: dependencies: @@ -3507,7 +3529,7 @@ snapshots: capnp-ts@0.7.0: dependencies: debug: 4.3.7 - tslib: 2.8.0 + tslib: 2.8.1 transitivePeerDependencies: - supports-color @@ -3705,35 +3727,35 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@1.21.6)): + eslint-config-prettier@9.1.0(eslint@9.14.0(jiti@1.21.6)): dependencies: - eslint: 9.13.0(jiti@1.21.6) + eslint: 9.14.0(jiti@1.21.6) eslint-plugin-security@3.0.1: dependencies: safe-regex: 2.1.1 - eslint-scope@8.1.0: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.1.0: {} + eslint-visitor-keys@4.2.0: {} - eslint@9.13.0(jiti@1.21.6): + eslint@9.14.0(jiti@1.21.6): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@1.21.6)) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@1.21.6)) + '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.18.0 '@eslint/core': 0.7.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.13.0 - '@eslint/plugin-kit': 0.2.1 - '@humanfs/node': 0.16.5 + '@eslint/js': 9.14.0 + '@eslint/plugin-kit': 0.2.2 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.1 '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -3741,9 +3763,9 @@ snapshots: cross-spawn: 7.0.3 debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -3764,11 +3786,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.2.0: + espree@10.3.0: dependencies: - acorn: 8.13.0 - acorn-jsx: 5.3.2(acorn@8.13.0) - eslint-visitor-keys: 4.1.0 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 esquery@1.6.0: dependencies: @@ -3876,7 +3898,7 @@ snapshots: globals@14.0.0: {} - globals@15.11.0: {} + globals@15.12.0: {} graphemer@1.4.0: {} @@ -4221,26 +4243,28 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.22.4 fsevents: 2.3.3 - rollup@4.24.0: + rollup@4.24.4: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 + '@rollup/rollup-android-arm-eabi': 4.24.4 + '@rollup/rollup-android-arm64': 4.24.4 + '@rollup/rollup-darwin-arm64': 4.24.4 + '@rollup/rollup-darwin-x64': 4.24.4 + '@rollup/rollup-freebsd-arm64': 4.24.4 + '@rollup/rollup-freebsd-x64': 4.24.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.4 + '@rollup/rollup-linux-arm-musleabihf': 4.24.4 + '@rollup/rollup-linux-arm64-gnu': 4.24.4 + '@rollup/rollup-linux-arm64-musl': 4.24.4 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.4 + '@rollup/rollup-linux-riscv64-gnu': 4.24.4 + '@rollup/rollup-linux-s390x-gnu': 4.24.4 + '@rollup/rollup-linux-x64-gnu': 4.24.4 + '@rollup/rollup-linux-x64-musl': 4.24.4 + '@rollup/rollup-win32-arm64-msvc': 4.24.4 + '@rollup/rollup-win32-ia32-msvc': 4.24.4 + '@rollup/rollup-win32-x64-msvc': 4.24.4 fsevents: 2.3.3 run-parallel@1.2.0: @@ -4377,11 +4401,11 @@ snapshots: totalist@3.0.1: {} - ts-api-utils@1.3.0(typescript@5.6.3): + ts-api-utils@1.4.0(typescript@5.6.3): dependencies: typescript: 5.6.3 - tslib@2.8.0: {} + tslib@2.8.1: {} tsx@4.17.0: dependencies: @@ -4394,11 +4418,11 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3): + typescript-eslint@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/parser': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) - '@typescript-eslint/utils': 8.11.0(eslint@9.13.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/eslint-plugin': 8.13.0(@typescript-eslint/parser@8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3))(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/parser': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@9.14.0(jiti@1.21.6))(typescript@5.6.3) optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: @@ -4430,10 +4454,10 @@ snapshots: pathe: 1.1.2 ufo: 1.5.4 - unocss@0.61.9(postcss@8.4.47)(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)): + unocss@0.61.9(postcss@8.4.47)(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)): dependencies: - '@unocss/astro': 0.61.9(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) - '@unocss/cli': 0.61.9(rollup@4.24.0) + '@unocss/astro': 0.61.9(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) + '@unocss/cli': 0.61.9(rollup@4.24.4) '@unocss/core': 0.61.9 '@unocss/extractor-arbitrary-variants': 0.61.9 '@unocss/postcss': 0.61.9(postcss@8.4.47) @@ -4451,9 +4475,9 @@ snapshots: '@unocss/transformer-compile-class': 0.61.9 '@unocss/transformer-directives': 0.61.9 '@unocss/transformer-variant-group': 0.61.9 - '@unocss/vite': 0.61.9(rollup@4.24.0)(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)) + '@unocss/vite': 0.61.9(rollup@4.24.4)(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)) optionalDependencies: - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) transitivePeerDependencies: - postcss - rollup @@ -4490,7 +4514,7 @@ snapshots: dependencies: esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.24.0 + rollup: 4.24.4 optionalDependencies: '@types/node': 20.16.1 fsevents: 2.3.3 @@ -4508,20 +4532,20 @@ snapshots: sass: 1.77.8 terser: 5.31.6 - vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6): + vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.22.4 optionalDependencies: - '@types/node': 20.17.0 + '@types/node': 22.9.0 fsevents: 2.3.3 sass: 1.77.8 terser: 5.31.6 - vitefu@0.2.5(vite@5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6)): + vitefu@0.2.5(vite@5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6)): optionalDependencies: - vite: 5.4.7(@types/node@20.17.0)(sass@1.77.8)(terser@5.31.6) + vite: 5.4.7(@types/node@22.9.0)(sass@1.77.8)(terser@5.31.6) vitest@2.1.1(@types/node@20.16.1)(sass@1.77.8)(terser@5.31.6): dependencies: