From bd7bd97809f78161448b064a885d07fdf60f0bee Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 24 Oct 2024 15:46:20 +0530 Subject: [PATCH] feat: portable version of `hoppscotch-agent` (#4468) --- packages/hoppscotch-agent/devenv.lock | 32 +- .../src-tauri/.cargo/config.toml | 25 + .../hoppscotch-agent/src-tauri/Cargo.lock | 434 +++++++++++++++++- .../hoppscotch-agent/src-tauri/Cargo.toml | 13 +- .../src-tauri/capabilities/default.json | 11 + .../hoppscotch-agent/src-tauri/src/dialog.rs | 58 +++ .../hoppscotch-agent/src-tauri/src/lib.rs | 112 +++-- .../hoppscotch-agent/src-tauri/src/model.rs | 13 + .../hoppscotch-agent/src-tauri/src/updater.rs | 82 ++-- .../hoppscotch-agent/src-tauri/src/util.rs | 106 +++-- .../src-tauri/src/webview/error.rs | 15 + .../src-tauri/src/webview/mod.rs | 212 +++++++++ .../src-tauri/tauri.conf.json | 5 +- .../src-tauri/tauri.portable.conf.json | 48 ++ 14 files changed, 1018 insertions(+), 148 deletions(-) create mode 100644 packages/hoppscotch-agent/src-tauri/.cargo/config.toml create mode 100644 packages/hoppscotch-agent/src-tauri/src/dialog.rs create mode 100644 packages/hoppscotch-agent/src-tauri/src/webview/error.rs create mode 100644 packages/hoppscotch-agent/src-tauri/src/webview/mod.rs create mode 100644 packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json diff --git a/packages/hoppscotch-agent/devenv.lock b/packages/hoppscotch-agent/devenv.lock index 9167f71b8..d8303eb72 100644 --- a/packages/hoppscotch-agent/devenv.lock +++ b/packages/hoppscotch-agent/devenv.lock @@ -3,11 +3,10 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1727098005, + "lastModified": 1729277673, "owner": "cachix", "repo": "devenv", - "rev": "f318d27a4637aff765a378106d82dfded124c3b3", - "treeHash": "c77efa71afb25615542aed9d7e805a3b6216b6a2", + "rev": "3c3ab087b53d3e4699a43018ac71b5e1091ed73d", "type": "github" }, "original": { @@ -25,11 +24,10 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1727159616, + "lastModified": 1728973961, "owner": "nix-community", "repo": "fenix", - "rev": "4306d494985e00719573bbdeb863c27c6d83dc9c", - "treeHash": "53136d2b5e6c46a3abe585771e60cd0646f69cf7", + "rev": "d6a9ff4d1e60c347a23bc96ccdb058d37a810541", "type": "github" }, "original": { @@ -45,7 +43,6 @@ "owner": "edolstra", "repo": "flake-compat", "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb", "type": "github" }, "original": { @@ -66,7 +63,6 @@ "owner": "hercules-ci", "repo": "gitignore.nix", "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", - "treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9", "type": "github" }, "original": { @@ -77,11 +73,10 @@ }, "nixpkgs": { "locked": { - "lastModified": 1727089097, + "lastModified": 1729265718, "owner": "NixOS", "repo": "nixpkgs", - "rev": "568bfef547c14ca438c56a0bece08b8bb2b71a9c", - "treeHash": "2c4d922ed00a8d8d8b136ad40a904239e071dfc3", + "rev": "ccc0c2126893dd20963580b6478d1a10a4512185", "type": "github" }, "original": { @@ -93,11 +88,10 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1726969270, + "lastModified": 1729181673, "owner": "NixOS", "repo": "nixpkgs", - "rev": "23cbb250f3bf4f516a2d0bf03c51a30900848075", - "treeHash": "f150876866adcc3af432c103db116c2f516f49b1", + "rev": "4eb33fe664af7b41a4c446f87d20c9a0a6321fa3", "type": "github" }, "original": { @@ -117,11 +111,10 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1726745158, + "lastModified": 1729104314, "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74", - "treeHash": "56fbe2a9610b3ad9163a74011131e7624f6b3b81", + "rev": "3c3e88f0f544d6bb54329832616af7eb971b6be6", "type": "github" }, "original": { @@ -141,11 +134,10 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1727104575, + "lastModified": 1729259624, "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "3d0343251fe084b335b55c17a52bb4a3527b1bd0", - "treeHash": "67f4408ff2f6d7099cecc1dac6389b199fd980f9", + "rev": "3ddfb0da474bdf243a655a5d785de9b59c7f1397", "type": "github" }, "original": { diff --git a/packages/hoppscotch-agent/src-tauri/.cargo/config.toml b/packages/hoppscotch-agent/src-tauri/.cargo/config.toml new file mode 100644 index 000000000..9017258b4 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/.cargo/config.toml @@ -0,0 +1,25 @@ +# Enable static linking for C runtime library on Windows. +# +# Rust uses the msvc toolchain on Windows, +# which by default dynamically links the C runtime (CRT) to the binary. +# +# This creates a runtime dependency on the Visual C++ Redistributable (`vcredist`), +# meaning the target machine must have `vcredist` installed for the application to run. +# +# Since `portable` version doesn't have an installer, +# we can't rely on it to install dependencies, so this config. +# +# Basically: +# - The `+crt-static` flag instructs the Rust compiler to statically link the C runtime for Windows builds.\ +# - To avoids runtime errors related to missing `vcredist` installations. +# - Results in a larger binary size because the runtime is bundled directly into the executable. +# +# For MSVC targets specifically, it will compile code with `/MT` or static linkage. +# See: - RFC 1721: https://rust-lang.github.io/rfcs/1721-crt-static.html +# - Rust Reference - Runtime: https://doc.rust-lang.org/reference/runtime.html +# - MSVC Linking Options: https://docs.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library +# - Rust Issue #37406: https://github.com/rust-lang/rust/issues/37406 +# - Tauri Issue #3048: https://github.com/tauri-apps/tauri/issues/3048 +# - Rust Linkage: https://doc.rust-lang.org/reference/linkage.html +[target.'cfg(windows)'] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.lock b/packages/hoppscotch-agent/src-tauri/Cargo.lock index a7e2be4f7..07397c12b 100644 --- a/packages/hoppscotch-agent/src-tauri/Cargo.lock +++ b/packages/hoppscotch-agent/src-tauri/Cargo.lock @@ -155,6 +155,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "ashpd" version = "0.9.2" @@ -165,7 +171,7 @@ dependencies = [ "futures-channel", "futures-util", "rand 0.8.5", - "raw-window-handle", + "raw-window-handle 0.6.2", "serde", "serde_repr", "tokio", @@ -210,6 +216,43 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb8f1d480b0ea3783ab015936d2a55c87e219676f0c0b7dec61494043f21857" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + [[package]] name = "async-io" version = "2.3.4" @@ -708,6 +751,22 @@ dependencies = [ "inout", ] +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types 0.5.0", + "libc", + "objc", +] + [[package]] name = "cocoa" version = "0.26.0" @@ -716,14 +775,28 @@ checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" dependencies = [ "bitflags 2.6.0", "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", "foreign-types 0.5.0", "libc", "objc", ] +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + [[package]] name = "cocoa-foundation" version = "0.2.0" @@ -732,8 +805,8 @@ checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" dependencies = [ "bitflags 2.6.0", "block", - "core-foundation", - "core-graphics-types", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", "libc", "objc", ] @@ -779,6 +852,44 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +dependencies = [ + "cookie", + "idna 0.5.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -795,6 +906,19 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types 0.5.0", + "libc", +] + [[package]] name = "core-graphics" version = "0.24.0" @@ -802,12 +926,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.6.0", - "core-foundation", - "core-graphics-types", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", ] +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "core-graphics-types" version = "0.2.0" @@ -815,7 +950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.10.0", "libc", ] @@ -1010,6 +1145,12 @@ dependencies = [ "serde", ] +[[package]] +name = "data-url" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" + [[package]] name = "deranged" version = "0.3.11" @@ -1083,6 +1224,16 @@ dependencies = [ "dirs-sys 0.4.1", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -1106,6 +1257,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -1197,6 +1359,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "embed-resource" version = "2.5.0" @@ -1932,6 +2100,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "hoppscotch-agent" version = "0.1.1" @@ -1947,6 +2124,7 @@ dependencies = [ "lazy_static", "log", "mockito", + "native-dialog", "rand 0.8.5", "serde", "serde_json", @@ -1954,14 +2132,18 @@ dependencies = [ "tauri-build", "tauri-plugin-autostart", "tauri-plugin-dialog", + "tauri-plugin-http", "tauri-plugin-shell", + "tauri-plugin-single-instance", "tauri-plugin-store", "tauri-plugin-updater", + "tempfile", "thiserror", "tokio", "tokio-util", "tower-http", "uuid", + "winreg 0.52.0", "x25519-dalek", ] @@ -2145,6 +2327,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.5.0" @@ -2247,6 +2439,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -2545,6 +2746,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "minisign-verify" version = "0.2.2" @@ -2617,6 +2824,29 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "native-dialog" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e7038885d2aeab236bd60da9e159a5967b47cde3292da3b15ff1bec27c039f" +dependencies = [ + "ascii", + "block", + "cocoa 0.25.0", + "core-foundation 0.9.4", + "dirs-next", + "objc", + "objc-foundation", + "objc_id", + "once_cell", + "raw-window-handle 0.5.2", + "thiserror", + "versions", + "wfd", + "which", + "winapi", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2628,7 +2858,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "raw-window-handle", + "raw-window-handle 0.6.2", "thiserror", ] @@ -2671,6 +2901,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2716,6 +2956,17 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc-sys" version = "0.3.5" @@ -2935,6 +3186,15 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.36.5" @@ -3409,6 +3669,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psl-types" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" + +[[package]] +name = "publicsuffix" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" +dependencies = [ + "idna 0.3.0", + "psl-types", +] + [[package]] name = "quick-xml" version = "0.32.0" @@ -3565,6 +3841,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3626,10 +3908,15 @@ version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ + "async-compression", "base64 0.22.1", "bytes", + "cookie", + "cookie_store", + "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", @@ -3651,6 +3938,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration", "tokio", "tokio-rustls", "tokio-util", @@ -3680,7 +3968,7 @@ dependencies = [ "objc2", "objc2-app-kit", "objc2-foundation", - "raw-window-handle", + "raw-window-handle 0.6.2", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -4121,14 +4409,14 @@ checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" dependencies = [ "bytemuck", "cfg_aliases", - "core-graphics", + "core-graphics 0.24.0", "foreign-types 0.5.0", "js-sys", "log", "objc2", "objc2-foundation", "objc2-quartz-core", - "raw-window-handle", + "raw-window-handle 0.6.2", "redox_syscall", "wasm-bindgen", "web-sys", @@ -4265,6 +4553,27 @@ dependencies = [ "futures-core", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -4285,9 +4594,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0dbbebe82d02044dfa481adca1550d6dd7bd16e086bc34fa0fbecceb5a63751" dependencies = [ "bitflags 2.6.0", - "cocoa", - "core-foundation", - "core-graphics", + "cocoa 0.26.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", "crossbeam-channel", "dispatch", "dlopen2", @@ -4306,7 +4615,7 @@ dependencies = [ "objc", "once_cell", "parking_lot", - "raw-window-handle", + "raw-window-handle 0.6.2", "scopeguard", "tao-macros", "unicode-segmentation", @@ -4373,7 +4682,7 @@ dependencies = [ "objc2-foundation", "percent-encoding", "plist", - "raw-window-handle", + "raw-window-handle 0.6.2", "reqwest", "serde", "serde_json", @@ -4498,7 +4807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4307310e1d2c09ab110235834722e7c2b85099b683e1eb7342ab351b0be5ada3" dependencies = [ "log", - "raw-window-handle", + "raw-window-handle 0.6.2", "rfd", "serde", "serde_json", @@ -4530,6 +4839,28 @@ dependencies = [ "uuid", ] +[[package]] +name = "tauri-plugin-http" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c752aee1b00ec3c4d4f440095995d9bd2c640b478f2067d1fba388900b82eb96" +dependencies = [ + "data-url", + "http", + "regex", + "reqwest", + "schemars", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror", + "tokio", + "url", + "urlpattern", +] + [[package]] name = "tauri-plugin-shell" version = "2.0.2" @@ -4551,6 +4882,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25ac834491d089699a2bc9266a662faf373c9f779f05a2235bc6e4d9e61769a" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "thiserror", + "windows-sys 0.59.0", + "zbus", +] + [[package]] name = "tauri-plugin-store" version = "2.1.0" @@ -4607,7 +4953,7 @@ dependencies = [ "gtk", "http", "jni", - "raw-window-handle", + "raw-window-handle 0.6.2", "serde", "serde_json", "tauri-utils", @@ -4630,7 +4976,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.6.2", "softbuffer", "tao", "tauri-runtime", @@ -4977,7 +5323,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c92af36a182b46206723bdf8a7942e20838cde1cf062e5b97854d57eb01763b" dependencies = [ - "core-graphics", + "core-graphics 0.24.0", "crossbeam-channel", "dirs 5.0.1", "libappindicator", @@ -5112,7 +5458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -5179,6 +5525,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "versions" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73a36bc44e3039f51fbee93e39f41225f6b17b380eb70cc2aab942df06b34dd" +dependencies = [ + "itertools", + "nom", +] + [[package]] name = "vswhom" version = "0.1.0" @@ -5469,6 +5825,28 @@ dependencies = [ "windows-core 0.58.0", ] +[[package]] +name = "wfd" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e713040b67aae5bf1a0ae3e1ebba8cc29ab2b90da9aa1bff6e09031a8a41d7a8" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -5509,7 +5887,7 @@ dependencies = [ "objc2", "objc2-app-kit", "objc2-foundation", - "raw-window-handle", + "raw-window-handle 0.6.2", "windows-sys 0.59.0", "windows-version", ] @@ -5876,7 +6254,7 @@ dependencies = [ "objc2-web-kit", "once_cell", "percent-encoding", - "raw-window-handle", + "raw-window-handle 0.6.2", "sha2", "soup3", "tao-macros", @@ -5951,9 +6329,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b8e3d6ae3342792a6cc2340e4394334c7402f3d793b390d2c5494a4032b3030" dependencies = [ "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", "async-process", "async-recursion", + "async-task", "async-trait", + "blocking", "derivative", "enumflags2", "event-listener", diff --git a/packages/hoppscotch-agent/src-tauri/Cargo.toml b/packages/hoppscotch-agent/src-tauri/Cargo.toml index 3347a31fa..b08d061be 100644 --- a/packages/hoppscotch-agent/src-tauri/Cargo.toml +++ b/packages/hoppscotch-agent/src-tauri/Cargo.toml @@ -17,7 +17,7 @@ tauri-build = { version = "2.0.1", features = [] } [dependencies] tauri = { version = "2.0.4", features = ["tray-icon", "image-png"] } tauri-plugin-shell = "2.0.1" -tauri-plugin-autostart = "2.0.1" +tauri-plugin-autostart = { version = "2.0.1", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1.40.0", features = ["full"] } @@ -40,6 +40,17 @@ aes-gcm = { version = "0.10.3", features = ["aes"] } tauri-plugin-updater = "2.0.2" tauri-plugin-dialog = "2.0.1" lazy_static = "1.5.0" +tauri-plugin-single-instance = "2.0.1" +tauri-plugin-http = { version = "2.0.1", features = ["gzip"] } +native-dialog = "0.7.0" + +[target.'cfg(windows)'.dependencies] +tempfile = { version = "3.13.0" } +winreg = { version = "0.52.0", optional = true } [dev-dependencies] mockito = "1.5.0" + +[features] +default = ["tauri-plugin-autostart"] +portable = ["winreg"] diff --git a/packages/hoppscotch-agent/src-tauri/capabilities/default.json b/packages/hoppscotch-agent/src-tauri/capabilities/default.json index e706c0f21..fe0f3775c 100644 --- a/packages/hoppscotch-agent/src-tauri/capabilities/default.json +++ b/packages/hoppscotch-agent/src-tauri/capabilities/default.json @@ -4,6 +4,17 @@ "description": "Capability for the main window", "windows": ["main", "test"], "permissions": [ + { + "identifier": "http:default", + "allow": [ + { + "url": "https://*.tauri.app" + }, + { + "url": "https://*.microsoft.*" + } + ] + }, "core:default", "shell:allow-open", "core:window:allow-close", diff --git a/packages/hoppscotch-agent/src-tauri/src/dialog.rs b/packages/hoppscotch-agent/src-tauri/src/dialog.rs new file mode 100644 index 000000000..5b8a19ecc --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/dialog.rs @@ -0,0 +1,58 @@ +use native_dialog::{MessageDialog, MessageType}; + +pub fn panic(msg: &str) { + const FATAL_ERROR: &str = "Fatal error"; + + MessageDialog::new() + .set_type(MessageType::Error) + .set_title(FATAL_ERROR) + .set_text(msg) + .show_alert() + .unwrap_or_default(); + + log::error!("{}: {}", FATAL_ERROR, msg); + + panic!("{}: {}", FATAL_ERROR, msg); +} + +pub fn info(msg: &str) { + log::info!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Info) + .set_title("Info") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn warn(msg: &str) { + log::warn!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Warning) + .set_title("Warning") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn error(msg: &str) { + log::error!("{}", msg); + + MessageDialog::new() + .set_type(MessageType::Error) + .set_title("Error") + .set_text(msg) + .show_alert() + .unwrap_or_default(); +} + +pub fn confirm(title: &str, msg: &str, icon: MessageType) -> bool { + MessageDialog::new() + .set_type(icon) + .set_title(title) + .set_text(msg) + .show_confirm() + .unwrap_or_default() +} diff --git a/packages/hoppscotch-agent/src-tauri/src/lib.rs b/packages/hoppscotch-agent/src-tauri/src/lib.rs index f15ded43e..997a3d6de 100644 --- a/packages/hoppscotch-agent/src-tauri/src/lib.rs +++ b/packages/hoppscotch-agent/src-tauri/src/lib.rs @@ -1,4 +1,5 @@ pub mod controller; +pub mod dialog; pub mod error; pub mod model; pub mod route; @@ -7,13 +8,17 @@ pub mod state; pub mod tray; pub mod updater; pub mod util; +pub mod webview; -use state::AppState; +use log::{error, info}; use std::sync::Arc; -use tauri::{Listener, Manager, WebviewWindowBuilder}; +use tauri::{Emitter, Listener, Manager, WebviewWindowBuilder}; use tauri_plugin_updater::UpdaterExt; use tokio_util::sync::CancellationToken; +use model::Payload; +use state::AppState; + #[tauri::command] async fn get_otp(state: tauri::State<'_, Arc>) -> Result, ()> { Ok(state.active_registration_code.read().await.clone()) @@ -23,53 +28,79 @@ async fn get_otp(state: tauri::State<'_, Arc>) -> Result { - let app_state = window.state::>(); + match &event { + tauri::WindowEvent::CloseRequested { .. } => { + let app_state = window.state::>(); - let mut current_code = - app_state.active_registration_code.blocking_write(); + let mut current_code = app_state.active_registration_code.blocking_write(); - if current_code.is_some() { - *current_code = None; - } - }, - _ => {} - }; + if current_code.is_some() { + *current_code = None; + } + } + _ => {} + }; }) - .invoke_handler(tauri::generate_handler![ - get_otp - ]) + .invoke_handler(tauri::generate_handler![get_otp]) .build(tauri::generate_context!()) .expect("error while building tauri application") .run(|app_handle, event| match event { diff --git a/packages/hoppscotch-agent/src-tauri/src/model.rs b/packages/hoppscotch-agent/src-tauri/src/model.rs index ad988c99f..68589fe93 100644 --- a/packages/hoppscotch-agent/src-tauri/src/model.rs +++ b/packages/hoppscotch-agent/src-tauri/src/model.rs @@ -1,6 +1,19 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +/// Single instance payload. +#[derive(Clone, Serialize)] +pub struct Payload { + args: Vec, + cwd: String, +} + +impl Payload { + pub fn new(args: Vec, cwd: String) -> Self { + Self { args, cwd } + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct HandshakeResponse { #[allow(non_snake_case)] diff --git a/packages/hoppscotch-agent/src-tauri/src/updater.rs b/packages/hoppscotch-agent/src-tauri/src/updater.rs index 43aa01ceb..490e1f7aa 100644 --- a/packages/hoppscotch-agent/src-tauri/src/updater.rs +++ b/packages/hoppscotch-agent/src-tauri/src/updater.rs @@ -1,39 +1,67 @@ -#[cfg(desktop)] +use tauri::Manager; +use tauri_plugin_dialog::DialogExt; +use tauri_plugin_dialog::MessageDialogButtons; +use tauri_plugin_dialog::MessageDialogKind; + +#[cfg(feature = "portable")] +use {crate::dialog, crate::util, native_dialog::MessageType}; + pub async fn check_and_install_updates( app: tauri::AppHandle, updater: tauri_plugin_updater::Updater, ) { - use tauri::Manager; - use tauri_plugin_dialog::DialogExt; - use tauri_plugin_dialog::MessageDialogButtons; - use tauri_plugin_dialog::MessageDialogKind; - let update = updater.check().await; if let Ok(Some(update)) = update { - let do_update = app - .dialog() - .message(format!( - "Update to {} is available!{}", - update.version, - update - .body - .clone() - .map(|body| format!("\n\nRelease Notes: {}", body)) - .unwrap_or("".into()) - )) - .title("Update Available") - .kind(MessageDialogKind::Info) - .buttons(MessageDialogButtons::OkCancelCustom( - "Update".to_string(), - "Cancel".to_string(), - )) - .blocking_show(); + #[cfg(not(feature = "portable"))] + { + let do_update = app + .dialog() + .message(format!( + "Update to {} is available!{}", + update.version, + update + .body + .clone() + .map(|body| format!("\n\nRelease Notes: {}", body)) + .unwrap_or("".into()) + )) + .title("Update Available") + .kind(MessageDialogKind::Info) + .buttons(MessageDialogButtons::OkCancelCustom( + "Update".to_string(), + "Cancel".to_string(), + )) + .blocking_show(); - if do_update { - let _ = update.download_and_install(|_, _| {}, || {}).await; + if do_update { + let _ = update.download_and_install(|_, _| {}, || {}).await; - tauri::process::restart(&app.env()); + tauri::process::restart(&app.env()); + } + } + #[cfg(feature = "portable")] + { + let download_url = "https://hoppscotch.com/download"; + let message = format!( + "An update (version {}) is available for the Hoppscotch Agent.\n\nPlease download the latest portable version from our website.", + update.version + ); + + dialog::info(&message); + + if dialog::confirm( + "Open Download Page", + "Would you like to open the download page in your browser?", + MessageType::Info, + ) { + if let None = util::open_link(download_url) { + dialog::error(&format!( + "Failed to open download page. Please visit {}", + download_url + )); + } + } } } } diff --git a/packages/hoppscotch-agent/src-tauri/src/util.rs b/packages/hoppscotch-agent/src-tauri/src/util.rs index 3d46fcb93..e383f0b93 100644 --- a/packages/hoppscotch-agent/src-tauri/src/util.rs +++ b/packages/hoppscotch-agent/src-tauri/src/util.rs @@ -1,40 +1,86 @@ +use std::process::{Command, Stdio}; + use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, KeyInit}; -use axum::{body::Body, response::{IntoResponse, Response}}; +use axum::{ + body::Body, + response::{IntoResponse, Response}, +}; use rand::rngs::OsRng; use serde::Serialize; -#[derive(Debug)] -pub struct EncryptedJson { - pub key_b16: String, - pub data: T -} +pub fn open_link(link: &str) -> Option<()> { + let null = Stdio::null(); -impl IntoResponse for EncryptedJson where T: Serialize { - fn into_response(self) -> Response { - let serialized_response = serde_json::to_vec(&self.data) - .expect("Failed serializing response to vec for encryption"); + #[cfg(target_os = "windows")] + { + Command::new("rundll32") + .args(["url.dll,FileProtocolHandler", link]) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } - let key: [u8; 32] = base16::decode(&self.key_b16) - .unwrap() - [0..32] - .try_into() - .unwrap(); + #[cfg(target_os = "macos")] + { + Command::new("open") + .arg(link) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } - let cipher = Aes256Gcm::new(&key.into()); + #[cfg(target_os = "linux")] + { + Command::new("xdg-open") + .arg(link) + .stdout(null) + .spawn() + .ok() + .map(|_| ()) + } - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - - let nonce_b16 = base16::encode_lower(&nonce); - - let encrypted_response = cipher.encrypt(&nonce, serialized_response.as_slice()) - .expect("Failed encrypting response"); - - let mut response = Response::new(Body::from(encrypted_response)); - let response_headers = response.headers_mut(); - - response_headers.insert("Content-Type", "application/octet-stream".parse().unwrap()); - response_headers.insert("X-Hopp-Nonce", nonce_b16.parse().unwrap()); - - response + #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))] + { + None + } +} + +#[derive(Debug)] +pub struct EncryptedJson { + pub key_b16: String, + pub data: T, +} + +impl IntoResponse for EncryptedJson +where + T: Serialize, +{ + fn into_response(self) -> Response { + let serialized_response = serde_json::to_vec(&self.data) + .expect("Failed serializing response to vec for encryption"); + + let key: [u8; 32] = base16::decode(&self.key_b16).unwrap()[0..32] + .try_into() + .unwrap(); + + let cipher = Aes256Gcm::new(&key.into()); + + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + + let nonce_b16 = base16::encode_lower(&nonce); + + let encrypted_response = cipher + .encrypt(&nonce, serialized_response.as_slice()) + .expect("Failed encrypting response"); + + let mut response = Response::new(Body::from(encrypted_response)); + let response_headers = response.headers_mut(); + + response_headers.insert("Content-Type", "application/octet-stream".parse().unwrap()); + response_headers.insert("X-Hopp-Nonce", nonce_b16.parse().unwrap()); + + response } } diff --git a/packages/hoppscotch-agent/src-tauri/src/webview/error.rs b/packages/hoppscotch-agent/src-tauri/src/webview/error.rs new file mode 100644 index 000000000..7bfeff524 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/webview/error.rs @@ -0,0 +1,15 @@ +use std::io; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum WebViewError { + #[error("Failed to open URL: {0}")] + UrlOpen(#[from] io::Error), + #[error("Failed to download WebView2 installer: {0}")] + Download(String), + #[error("WebView2 installation failed: {0}")] + Installation(String), + #[error("Failed during request: {0}")] + Request(#[from] tauri_plugin_http::reqwest::Error), +} diff --git a/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs b/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs new file mode 100644 index 000000000..f66279b44 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/src/webview/mod.rs @@ -0,0 +1,212 @@ +/// The WebView2 Runtime is a critical dependency for Tauri applications on Windows. +/// We need to check for its presence, see [Source: GitHub Issue #59 - Portable windows build](https://github.com/tauri-apps/tauri-action/issues/59#issuecomment-827142638) +/// +/// > "Tauri requires an installer if you define app resources, external binaries or running on environments that do not have Webview2 runtime installed. So I don't think it's a good idea to have a "portable" option since a Tauri binary itself isn't 100% portable." +/// +/// The approach for checking WebView2 installation is based on Microsoft's official documentation, which states: +/// +/// > ###### Detect if a WebView2 Runtime is already installed +/// > +/// > To verify that a WebView2 Runtime is installed, use one of the following approaches: +/// > +/// > * Approach 1: Inspect the `pv (REG_SZ)` regkey for the WebView2 Runtime at both of the following registry locations. The `HKEY_LOCAL_MACHINE` regkey is used for _per-machine_ install. The `HKEY_CURRENT_USER` regkey is used for _per-user_ install. +/// > +/// > For WebView2 applications, at least one of these regkeys must be present and defined with a version greater than 0.0.0.0. If neither regkey exists, or if only one of these regkeys exists but its value is `null`, an empty string, or 0.0.0.0, this means that the WebView2 Runtime isn't installed on the client. Inspect these regkeys to detect whether the WebView2 Runtime is installed, and to get the version of the WebView2 Runtime. Find `pv (REG_SZ)` at the following two locations. +/// > +/// > The two registry locations to inspect on 64-bit Windows: +/// > +/// > ``` +/// > HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > +/// > HKEY_CURRENT_USER\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > ``` +/// > +/// > The two registry locations to inspect on 32-bit Windows: +/// > +/// > ``` +/// > HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > +/// > HKEY_CURRENT_USER\Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} +/// > ``` +/// > +/// > * Approach 2: Run [GetAvailableCoreWebView2BrowserVersionString](/microsoft-edge/webview2/reference/win32/webview2-idl#getavailablecorewebview2browserversionstring) and evaluate whether the `versionInfo` is `nullptr`. `nullptr` indicates that the WebView2 Runtime isn't installed. This API returns version information for the WebView2 Runtime or for any installed preview channels of Microsoft Edge (Beta, Dev, or Canary). +/// +/// See: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution?tabs=dotnetcsharp#detect-if-a-webview2-runtime-is-already-installed +/// +/// Our implementation uses Approach 1, checking both the 32-bit (WOW6432Node) and 64-bit registry locations +/// to make sure we have critical dependencis compatibility with different system architectures. +pub mod error; + +use std::{io, ops::Not}; + +use native_dialog::MessageType; + +use crate::{dialog, util}; +use error::WebViewError; + +#[cfg(windows)] +use { + std::io::Cursor, + std::process::Command, + tauri_plugin_http::reqwest, + tempfile::TempDir, + winreg::{ + enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}, + RegKey, + }, +}; + +const TAURI_WEBVIEW_REF: &str = "https://v2.tauri.app/references/webview-versions/"; +const WINDOWS_WEBVIEW_REF: &str = + "https://developer.microsoft.com/microsoft-edge/webview2/#download-section"; + +fn is_available() -> bool { + #[cfg(windows)] + { + const KEY_WOW64: &str = r"SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + const KEY: &str = + r"SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let hkcu = RegKey::predef(HKEY_CURRENT_USER); + + [ + hklm.open_subkey(KEY_WOW64), + hkcu.open_subkey(KEY_WOW64), + hklm.open_subkey(KEY), + hkcu.open_subkey(KEY), + ] + .into_iter() + .any(|result| result.is_ok()) + } + + #[cfg(not(windows))] + { + true + } +} + +fn open_install_website() -> Result<(), WebViewError> { + let url = if cfg!(windows) { + WINDOWS_WEBVIEW_REF + } else { + TAURI_WEBVIEW_REF + }; + + util::open_link(url).map(|_| ()).ok_or_else(|| { + WebViewError::UrlOpen(io::Error::new( + io::ErrorKind::Other, + "Failed to open browser to WebView download section", + )) + }) +} + +#[cfg(windows)] +async fn install() -> Result<(), WebViewError> { + const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; + const DEFAULT_FILENAME: &str = "MicrosoftEdgeWebview2Setup.exe"; + + let client = reqwest::Client::builder() + .user_agent("Hoppscotch Agent") + .gzip(true) + .build()?; + + let response = client.get(WEBVIEW2_BOOTSTRAPPER_URL).send().await?; + + if !response.status().is_success() { + return Err(WebViewError::Download(format!( + "Failed to download WebView2 bootstrapper. Status: {}", + response.status() + ))); + } + + let filename = + get_filename_from_response(&response).unwrap_or_else(|| DEFAULT_FILENAME.to_owned()); + + let tmp_dir = TempDir::with_prefix("WebView-setup-")?; + let installer_path = tmp_dir.path().join(filename); + + let content = response.bytes().await?; + { + let mut file = std::fs::File::create(&installer_path)?; + io::copy(&mut Cursor::new(content), &mut file)?; + } + + let status = Command::new(&installer_path).args(["/install"]).status()?; + + if !status.success() { + return Err(WebViewError::Installation(format!( + "Installer exited with code `{}`.", + status.code().unwrap_or(-1) + ))); + } + + Ok(()) +} + +#[cfg(windows)] +fn get_filename_from_response(response: &reqwest::Response) -> Option { + response + .headers() + .get("content-disposition") + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.split("filename=").last()) + .map(|name| name.trim().replace('\"', "")) + .or_else(|| { + response + .url() + .path_segments() + .and_then(|segments| segments.last()) + .map(|name| name.to_string()) + }) + .filter(|name| !name.is_empty()) +} + +#[cfg(not(windows))] +async fn install() -> Result<(), WebViewError> { + Err(WebViewError::Installation( + "Unable to auto-install WebView. Please refer to https://v2.tauri.app/references/webview-versions/".to_string(), + )) +} + +pub fn init_webview() { + if is_available() { + return; + } + + if dialog::confirm( + "WebView Error", + "WebView is required for this application to work.\n\n\ + Do you want to install it?", + MessageType::Error, + ) + .not() + { + log::warn!("Declined to setup WebView."); + + std::process::exit(1); + } + + if let Err(e) = tauri::async_runtime::block_on(install()) { + dialog::error(&format!( + "Failed to install WebView: {}\n\n\ + Please install it manually from webpage that should open when you click 'Ok'.\n\n\ + If that doesn't work, please visit Microsoft Edge Webview2 download section.", + e + )); + + if let Err(e) = open_install_website() { + log::warn!("Failed to launch WebView website:\n{}", e); + } + + std::process::exit(1); + } + + if is_available().not() { + dialog::panic( + "Unable to setup WebView:\n\n\ + Please install it manually and relaunch the application.\n\ + https://developer.microsoft.com/microsoft-edge/webview2/#download-section", + ); + } +} diff --git a/packages/hoppscotch-agent/src-tauri/tauri.conf.json b/packages/hoppscotch-agent/src-tauri/tauri.conf.json index ff8a7d33c..4a4d8268b 100644 --- a/packages/hoppscotch-agent/src-tauri/tauri.conf.json +++ b/packages/hoppscotch-agent/src-tauri/tauri.conf.json @@ -28,7 +28,7 @@ "csp": null } }, - "bundle": { + "bundle": { "active": true, "targets": "all", "icon": [ @@ -40,10 +40,9 @@ ], "createUpdaterArtifacts": true }, - "plugins" : { + "plugins": { "updater": { "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDRBQzgxQjc3MzJCMjZENEMKUldSTWJiSXlkeHZJU3EvQW1abFVlREVhRDNlM0ZhOVJYaHN4M0FpbXZhcUFzSVdVbG84RWhPa1AK", - "endpoints": [ "https://releases.hoppscotch.com/hoppscotch-agent.json" ] diff --git a/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json b/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json new file mode 100644 index 000000000..7cd912f34 --- /dev/null +++ b/packages/hoppscotch-agent/src-tauri/tauri.portable.conf.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.tauri.app/config/2.0.0-rc", + "productName": "Hoppscotch Agent Portable", + "version": "0.1.1", + "identifier": "io.hoppscotch.agent", + "build": { + "beforeDevCommand": "pnpm dev", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "pnpm build", + "frontendDist": "../dist" + }, + "app": { + "windows": [ + { + "title": "Hoppscotch Agent Portable", + "width": 600, + "height": 400, + "center": true, + "resizable": false, + "maximizable": false, + "minimizable": false, + "focus": true, + "alwaysOnTop": true, + "create": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": false, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "createUpdaterArtifacts": false + }, + "plugins": { + "updater": { + "active": false + } + } +}