Merge remote-tracking branch 'origin/v2' into feat/scanner

pull/536/head
Lucas Nogueira 2 years ago
commit 666d185afc
No known key found for this signature in database
GPG Key ID: FFEA6C72E73482F1

@ -41,7 +41,7 @@
"app", "app",
"log-plugin", "log-plugin",
"cli", "cli",
"clipboard", "clipboard-manager",
"dialog", "dialog",
"fs", "fs",
"global-shortcut", "global-shortcut",
@ -52,7 +52,8 @@
"shell", "shell",
"updater", "updater",
"window" "window"
] ],
"postversion": "pnpm install --no-frozen-lockfile"
}, },
"api-example-js": { "api-example-js": {
"path": "./examples/api", "path": "./examples/api",
@ -62,7 +63,7 @@
"app-js", "app-js",
"log-js", "log-js",
"cli-js", "cli-js",
"clipboard-js", "clipboard-manager-js",
"dialog-js", "dialog-js",
"fs-js", "fs-js",
"global-shortcut-js", "global-shortcut-js",

@ -0,0 +1,5 @@
---
"dialog": "patch"
---
On non-Linux system, use `AsyncMessageDialog` instead of `MessageDialog`. [(tauri#7182)](https://github.com/tauri-apps/tauri/issues/7182)

@ -0,0 +1,30 @@
---
"app": patch
"authenticator": patch
"autostart": patch
"cli": patch
"clipboard-manager": patch
"dialog": patch
"fs": patch
"global-shortcut": patch
"http": patch
"localhost": patch
"log-plugin": patch
"notification": patch
"os": patch
"persisted-scope": patch
"positioner": patch
"process": patch
"shell": patch
"single-instance": patch
"sql": patch
"store": patch
"stronghold": patch
"updater": patch
"upload": patch
"websocket": patch
"window": patch
"window-state": patch
---
Fixes docs.rs build by enabling the `tauri/dox` feature flag.

@ -0,0 +1,5 @@
---
"fs-js": patch
---
Fix `writeBinaryFile` crashing with `command 'write_binary_file' not found`

@ -0,0 +1,5 @@
---
"http-js": minor
---
Multipart requests are now handled in JavaScript by the `Request` JavaScript class so you just need to use a `FormData` body and not set the content-type header to `multipart/form-data`. `application/x-www-form-urlencoded` requests must be done manually.

@ -0,0 +1,6 @@
---
"http": minor
"http-js": minor
---
The http plugin has been rewritten from scratch and now only exposes a `fetch` function in Javascript and Re-exports `reqwest` crate in Rust. The new `fetch` method tries to be as close and compliant to the `fetch` Web API as possible.

@ -0,0 +1,5 @@
---
"http": patch
---
Improve response performance by using the new IPC streaming data.

@ -0,0 +1,5 @@
---
"os-js": "patch"
---
Fix `macss -> macos` typo in `OsType` type.

@ -0,0 +1,5 @@
---
"persisted-scope": patch
---
Split up fs and asset scopes. **This will reset the asset protocol scope once!**

@ -0,0 +1,5 @@
---
"persisted-scope": patch
---
Fix usage of directory patterns by removing glob asterisks at the end before allowing/forbidding them. This was causing them to be escaped, and so undesirable paths were allowed/forbidden while polluting the `.persisted-scope` file.

@ -0,0 +1,5 @@
---
"positioner": patch
---
Change `system-tray` feature flag to `tray-icon`.

@ -1,9 +1,36 @@
{ {
"tag": "alpha", "tag": "alpha",
"changes": [ "changes": [
".changes/dialog-async-message-dialog.md",
".changes/disable-window-controls-api-options.md",
".changes/disable-window-controls-api.md",
".changes/fix-docs-build.md",
".changes/fs-wiret-binary-file.md",
".changes/http-multipart-refactor.md",
".changes/http-plugin-refactor.md",
".changes/http-response.md",
".changes/notification-init-script.md", ".changes/notification-init-script.md",
".changes/notification-revert-sound.md",
".changes/notification-sound.md", ".changes/notification-sound.md",
".changes/os-OsType.md",
".changes/os-plugin-refactor.md",
".changes/persisted-scope-asset.md",
".changes/persisted-scope-glob.md",
".changes/positioner-tray-flag.md",
".changes/shell-command-apis.md",
".changes/shell-detached.md",
".changes/stronghold-arg-name.md",
".changes/stronghold-constructor.md", ".changes/stronghold-constructor.md",
".changes/v2-alpha.md" ".changes/tauri-alpha.11.md",
".changes/updater-nsis-admin.md",
".changes/updater-nsis.md",
".changes/updater-plugin-refactor.md",
".changes/v2-alpha.md",
".changes/window-incognito.md",
".changes/window-is-focused.md",
".changes/window-plugin-refactor.md",
".changes/window-set-effects.md",
".changes/window-state-decorated.md",
".changes/window-state-promise.md"
] ]
} }

@ -0,0 +1,5 @@
---
"shell": "patch"
---
Added `Command::arg`, `Command::env` and changed `Command::new` input type.

@ -0,0 +1,5 @@
---
"shell": patch
---
Ensure the launched process is detached so it can out-live your tauri app and does not shutdown with it.

@ -0,0 +1,5 @@
---
"stronghold-js": patch
---
Change the argument name of the `Stronghold.remove` from `location` to `recordPath` to match the Stronghold command argument

@ -0,0 +1,53 @@
---
"app": patch
"app-js": patch
"authenticator": patch
"authenticator-js": patch
"autostart": patch
"autostart-js": patch
"cli": patch
"cli-js": patch
"clipboard-manager": patch
"clipboard-manager-js": patch
"dialog": patch
"dialog-js": patch
"fs": patch
"fs-js": patch
"global-shortcut": patch
"global-shortcut-js": patch
"http": patch
"http-js": patch
"localhost": patch
"log-plugin": patch
"log-js": patch
"notification": patch
"notification-js": patch
"os": patch
"os-js": patch
"persisted-scope": patch
"positioner": patch
"positioner-js": patch
"process": patch
"process-js": patch
"shell": patch
"shell-js": patch
"single-instance": patch
"sql": patch
"sql-js": patch
"store": patch
"store-js": patch
"stronghold": patch
"stronghold-js": patch
"updater": patch
"updater-js": patch
"upload": patch
"upload-js": patch
"websocket": patch
"websocket-js": patch
"window": patch
"window-js": patch
"window-state": patch
"window-state-js": patch
---
Update to alpha.11.

@ -0,0 +1,6 @@
---
"updater": minor
"updater-js": minor
---
The updater plugin is recieving a few changes to improve consistency and ergonomics of the Rust and JS APIs

@ -0,0 +1,10 @@
---
"window": "patch"
"window-js": "patch"
---
The window plugin is recieving a few changes to improve consistency and add new features:
- Removed `appWindow` variable from JS module, use `getCurrent` or `Window.getCurrent`.
- Removed `WindowManager`, `WebviewWindow` and `WebviewHandle` types and merged them into one `Window` type that matches the name of the rust window type.
- Added `Window.getCurrent` and `Window.getAll` which is a convenient method for `getCurrent` and `getAll` functions.

@ -0,0 +1,54 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: integration tests
on:
push:
branches:
- v1
- v2
paths:
- ".github/workflows/integration-tests.yml"
- "plugins/updater/src/**"
pull_request:
branches:
- v1
- v2
paths:
- ".github/workflows/integration-tests.yml"
- "plugins/updater/src/**"
jobs:
run-integration-tests:
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: install stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: install Linux dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.1 libayatana-appindicator3-dev libfuse2
- uses: Swatinem/rust-cache@v2
- name: install Tauri CLI
run: cargo install tauri-cli --git https://github.com/tauri-apps/tauri --branch dev
- name: run integration tests
run: cargo test --test '*' -- --ignored

@ -193,6 +193,11 @@ jobs:
working-directory: examples/api working-directory: examples/api
run: mkdir dist run: mkdir dist
- name: Downgrade crates with MSRV conflict
# The --precise flag can only be used once per invocation.
run: |
cargo update -p time@0.3.24 --precise 0.3.23
- name: test ${{ matrix.package }} - name: test ${{ matrix.package }}
if: matrix.package != 'tauri-plugin-sql' if: matrix.package != 'tauri-plugin-sql'
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

@ -116,6 +116,6 @@ if (files.length > 0) {
console.log(missing.join("\n")); console.log(missing.join("\n"));
process.exit(1); process.exit(1);
} }
} },
); );
} }

@ -52,7 +52,7 @@ https.get(url, options, (response) => {
} }
} else if (kind === "npm") { } else if (kind === "npm") {
const versions = Object.keys(data.versions || {}).filter((v) => const versions = Object.keys(data.versions || {}).filter((v) =>
v.startsWith(target) v.startsWith(target),
); );
console.log(versions[versions.length - 1] || "0.0.0"); console.log(versions[versions.length - 1] || "0.0.0");
} }

2212
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -5,8 +5,8 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
log = "0.4" log = "0.4"
tauri = "2.0.0-alpha.10" tauri = "2.0.0-alpha.11"
tauri-build = "2.0.0-alpha.6" tauri-build = "2.0.0-alpha.8"
serde_json = "1" serde_json = "1"
thiserror = "1" thiserror = "1"

@ -1,5 +1,29 @@
# Changelog # Changelog
## \[2.0.0-alpha.2]
### Dependencies
- Upgraded to `os-js@2.0.0-alpha.2`
## \[2.0.0-alpha.1]
### Dependencies
- Upgraded to `window-js@2.0.0-alpha.1`
- Upgraded to `fs-js@2.0.0-alpha.1`
- Upgraded to `http-js@2.0.0-alpha.1`
- Upgraded to `os-js@2.0.0-alpha.1`
- Upgraded to `app-js@2.0.0-alpha.1`
- Upgraded to `cli-js@2.0.0-alpha.1`
- Upgraded to `dialog-js@2.0.0-alpha.1`
- Upgraded to `global-shortcut-js@2.0.0-alpha.1`
- Upgraded to `log-js@2.0.0-alpha.1`
- Upgraded to `notification-js@2.0.0-alpha.1`
- Upgraded to `process-js@2.0.0-alpha.1`
- Upgraded to `shell-js@2.0.0-alpha.1`
- Upgraded to `updater-js@2.0.0-alpha.1`
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
### Dependencies ### Dependencies

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en" theme="dark"> <html lang="en" theme="dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

@ -1,7 +1,7 @@
{ {
"name": "svelte-app", "name": "svelte-app",
"private": true, "private": true,
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --clearScreen false", "dev": "vite --clearScreen false",
@ -9,28 +9,28 @@
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5", "@tauri-apps/api": "2.0.0-alpha.6",
"@tauri-apps/plugin-app": "2.0.0-alpha.0", "@tauri-apps/plugin-app": "2.0.0-alpha.1",
"@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0", "@tauri-apps/plugin-barcode-scanner": "2.0.0-alpha.0",
"@tauri-apps/plugin-cli": "2.0.0-alpha.0", "@tauri-apps/plugin-cli": "2.0.0-alpha.1",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.0", "@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.1",
"@tauri-apps/plugin-dialog": "2.0.0-alpha.0", "@tauri-apps/plugin-dialog": "2.0.0-alpha.1",
"@tauri-apps/plugin-fs": "2.0.0-alpha.0", "@tauri-apps/plugin-fs": "2.0.0-alpha.1",
"@tauri-apps/plugin-global-shortcut": "2.0.0-alpha.0", "@tauri-apps/plugin-global-shortcut": "2.0.0-alpha.1",
"@tauri-apps/plugin-http": "2.0.0-alpha.0", "@tauri-apps/plugin-http": "2.0.0-alpha.1",
"@tauri-apps/plugin-notification": "2.0.0-alpha.0", "@tauri-apps/plugin-notification": "2.0.0-alpha.1",
"@tauri-apps/plugin-os": "2.0.0-alpha.0", "@tauri-apps/plugin-os": "2.0.0-alpha.2",
"@tauri-apps/plugin-process": "2.0.0-alpha.0", "@tauri-apps/plugin-process": "2.0.0-alpha.1",
"@tauri-apps/plugin-shell": "2.0.0-alpha.0", "@tauri-apps/plugin-shell": "2.0.0-alpha.1",
"@tauri-apps/plugin-updater": "2.0.0-alpha.0", "@tauri-apps/plugin-updater": "2.0.0-alpha.1",
"@tauri-apps/plugin-window": "2.0.0-alpha.0", "@tauri-apps/plugin-window": "2.0.0-alpha.1",
"@zerodevx/svelte-json-view": "1.0.5" "@zerodevx/svelte-json-view": "1.0.5"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/codicon": "^1.1.26", "@iconify-json/codicon": "^1.1.26",
"@iconify-json/ph": "^1.1.5", "@iconify-json/ph": "^1.1.5",
"@sveltejs/vite-plugin-svelte": "^2.4.1", "@sveltejs/vite-plugin-svelte": "^2.4.1",
"@tauri-apps/cli": "2.0.0-alpha.10", "@tauri-apps/cli": "2.0.0-alpha.11",
"@unocss/extractor-svelte": "^0.53.1", "@unocss/extractor-svelte": "^0.53.1",
"internal-ip": "^8.0.0", "internal-ip": "^8.0.0",
"svelte": "^3.59.1", "svelte": "^3.59.1",

@ -1,5 +1,29 @@
# Changelog # Changelog
## \[2.0.0-alpha.3]
### Dependencies
- Upgraded to `http@2.0.0-alpha.2`
## \[2.0.0-alpha.2]
### Dependencies
- Upgraded to `dialog@2.0.0-alpha.1`
- Upgraded to `window@2.0.0-alpha.1`
- Upgraded to `app@2.0.0-alpha.1`
- Upgraded to `cli@2.0.0-alpha.1`
- Upgraded to `fs@2.0.0-alpha.1`
- Upgraded to `global-shortcut@2.0.0-alpha.1`
- Upgraded to `http@2.0.0-alpha.1`
- Upgraded to `log-plugin@2.0.0-alpha.1`
- Upgraded to `notification@2.0.0-alpha.2`
- Upgraded to `os@2.0.0-alpha.1`
- Upgraded to `process@2.0.0-alpha.1`
- Upgraded to `shell@2.0.0-alpha.1`
- Upgraded to `updater@2.0.0-alpha.1`
## \[2.0.0-alpha.1] ## \[2.0.0-alpha.1]
### Dependencies ### Dependencies

@ -1,10 +1,10 @@
[package] [package]
name = "api" name = "api"
publish = false publish = false
version = "2.0.0-alpha.1" version = "2.0.0-alpha.3"
description = "An example Tauri Application showcasing the api" description = "An example Tauri Application showcasing the api"
edition = "2021" edition = "2021"
rust-version = "1.65" rust-version = { workspace = true }
license = "Apache-2.0 OR MIT" license = "Apache-2.0 OR MIT"
[lib] [lib]
@ -18,17 +18,17 @@ serde_json = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
tiny_http = "0.11" tiny_http = "0.11"
log = { workspace = true } log = { workspace = true }
tauri-plugin-app = { path = "../../../plugins/app", version = "2.0.0-alpha.0" } tauri-plugin-app = { path = "../../../plugins/app", version = "2.0.0-alpha.1" }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0-alpha.0" } tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0-alpha.1" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.0" } tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.1" }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.0-alpha.0" } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.0-alpha.0" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0-alpha.0" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0-alpha.1" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-alpha.0" } tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-alpha.2" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.0-alpha.0", features = [ "windows7-compat" ] } tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.0-alpha.2", features = [ "windows7-compat" ] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.0-alpha.0" } tauri-plugin-os = { path = "../../../plugins/os", version = "2.0.0-alpha.1" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.0-alpha.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.0.0-alpha.1" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0-alpha.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.0.0-alpha.1" }
tauri-plugin-window = { path = "../../../plugins/window", version = "2.0.0-alpha.0", features = [ "devtools", "icon-ico", "icon-png" ] } tauri-plugin-window = { path = "../../../plugins/window", version = "2.0.0-alpha.1", features = [ "devtools", "icon-ico", "icon-png" ] }
[dependencies.tauri] [dependencies.tauri]
workspace = true workspace = true
@ -37,14 +37,14 @@ tauri-plugin-window = { path = "../../../plugins/window", version = "2.0.0-alpha
"icon-png", "icon-png",
"isolation", "isolation",
"macos-private-api", "macos-private-api",
"system-tray", "tray-icon",
"protocol-asset" "protocol-asset"
] ]
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.0.0-alpha.0" } tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.0.0-alpha.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.0-alpha.0" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.0-alpha.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-alpha.0" } tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-alpha.1" }
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] [target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.0.0-alpha.0" } tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.0.0-alpha.0" }

@ -44,7 +44,7 @@ pub fn run() {
.setup(move |app| { .setup(move |app| {
#[cfg(desktop)] #[cfg(desktop)]
{ {
tray::create_tray(app)?; tray::create_tray(app.handle())?;
app.handle().plugin(tauri_plugin_cli::init())?; app.handle().plugin(tauri_plugin_cli::init())?;
app.handle() app.handle()
.plugin(tauri_plugin_global_shortcut::Builder::new().build())?; .plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
@ -128,7 +128,7 @@ pub fn run() {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
builder = builder.menu(tauri::Menu::os_default("Tauri API Validation")); builder = builder.menu(tauri::menu::Menu::default);
} }
#[allow(unused_mut)] #[allow(unused_mut)]

@ -4,140 +4,113 @@
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use tauri::{ use tauri::{
CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl, menu::{Menu, MenuItem},
tray::{ClickType, TrayIconBuilder},
Manager, Runtime, WindowBuilder, WindowUrl,
}; };
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_shell::ShellExt;
pub fn create_tray(app: &tauri::App) -> tauri::Result<()> {
let mut tray_menu1 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"));
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None);
let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None);
let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None);
let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None);
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None);
tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title")); let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None);
} let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None);
let remove_tray_i = MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None);
tray_menu1 = tray_menu1 let menu1 = Menu::with_items(
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu")) app,
.add_item(CustomMenuItem::new("about", "About")) &[
.add_item(CustomMenuItem::new("exit_app", "Quit")) &toggle_i,
.add_item(CustomMenuItem::new("destroy", "Destroy")); &new_window_i,
&icon_i_1,
&icon_i_2,
#[cfg(target_os = "macos")]
&set_title_i,
&switch_i,
&quit_i,
&remove_tray_i,
],
)?;
let menu2 = Menu::with_items(
app,
&[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i],
)?;
let tray_menu2 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("about", "About"))
.add_item(CustomMenuItem::new("exit_app", "Quit"))
.add_item(CustomMenuItem::new("destroy", "Destroy"));
let is_menu1 = AtomicBool::new(true); let is_menu1 = AtomicBool::new(true);
let handle = app.handle(); let _ = TrayIconBuilder::with_id("tray-1")
let tray_id = "my-tray".to_string(); .tooltip("Tauri")
SystemTray::new() .icon(app.default_window_icon().unwrap().clone())
.with_id(&tray_id) .menu(&menu1)
.with_menu(tray_menu1.clone()) .menu_on_left_click(false)
.with_tooltip("Tauri") .on_menu_event(move |app, event| match event.id.as_ref() {
.on_event(move |event| { "quit" => {
let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap(); app.exit(0);
match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
let window = handle.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
SystemTrayEvent::MenuItemClick { id, .. } => {
let item_handle = tray_handle.get_item(&id);
match id.as_str() {
"exit_app" => {
// exit the app
handle.exit(0);
} }
"destroy" => { "remove-tray" => {
tray_handle.destroy().unwrap(); app.remove_tray_by_id("tray-1");
} }
"toggle" => { "toggle" => {
let window = handle.get_window("main").unwrap(); if let Some(window) = app.get_window("main") {
let new_title = if window.is_visible().unwrap() { let new_title = if window.is_visible().unwrap_or_default() {
window.hide().unwrap(); let _ = window.hide();
"Show" "Show"
} else { } else {
window.show().unwrap(); let _ = window.show();
let _ = window.set_focus();
"Hide" "Hide"
}; };
item_handle.set_title(new_title).unwrap(); toggle_i.set_text(new_title).unwrap();
} }
"new" => { }
WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into())) "new-window" => {
let _ = WindowBuilder::new(app, "new", WindowUrl::App("index.html".into()))
.title("Tauri") .title("Tauri")
.build() .build();
.unwrap();
} }
"set_title" => {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
tray_handle.set_title("Tauri").unwrap(); "set-title" => {
if let Some(tray) = app.tray_by_id("tray-1") {
let _ = tray.set_title(Some("Tauri"));
} }
"icon_1" => {
#[cfg(target_os = "macos")]
tray_handle.set_icon_as_template(true).unwrap();
tray_handle
.set_icon(tauri::Icon::Raw(
include_bytes!("../icons/tray_icon_with_transparency.png")
.to_vec(),
))
.unwrap();
} }
"icon_2" => { i @ "icon-1" | i @ "icon-2" => {
#[cfg(target_os = "macos")] if let Some(tray) = app.tray_by_id("tray-1") {
tray_handle.set_icon_as_template(true).unwrap(); let _ = tray.set_icon(Some(tauri::Icon::Raw(if i == "icon-1" {
include_bytes!("../icons/icon.ico").to_vec()
tray_handle } else {
.set_icon(tauri::Icon::Raw( include_bytes!("../icons/tray_icon_with_transparency.png").to_vec()
include_bytes!("../icons/icon.ico").to_vec(), })));
)) }
.unwrap();
} }
"switch_menu" => { "switch-menu" => {
let flag = is_menu1.load(Ordering::Relaxed); let flag = is_menu1.load(Ordering::Relaxed);
let (menu, tooltip) = if flag { let (menu, tooltip) = if flag {
(tray_menu2.clone(), "Menu 2") (menu2.clone(), "Menu 2")
} else { } else {
(tray_menu1.clone(), "Tauri") (menu1.clone(), "Tauri")
}; };
tray_handle.set_menu(menu).unwrap(); if let Some(tray) = app.tray_by_id("tray-1") {
tray_handle.set_tooltip(tooltip).unwrap(); let _ = tray.set_menu(Some(menu));
is_menu1.store(!flag, Ordering::Relaxed); let _ = tray.set_tooltip(Some(tooltip));
} }
"about" => { is_menu1.store(!flag, Ordering::Relaxed);
let window = handle.get_window("main").unwrap();
window
.dialog()
.message("Tauri demo app")
.title("About app")
.parent(&window)
.ok_button_label("Homepage")
.cancel_button_label("Cancel")
.show(move |ok| {
if ok {
window.shell().open("https://tauri.app/", None).unwrap();
}
});
} }
_ => {} _ => {}
})
.on_tray_event(|tray, event| {
if event.click_type == ClickType::Left {
let app = tray.app_handle();
if let Some(window) = app.get_window("main") {
let _ = window.show();
let _ = window.set_focus();
} }
} }
_ => {}
}
}) })
.build(app) .build(app);
.map(|_| ())
Ok(())
} }

@ -124,6 +124,7 @@
"security": { "security": {
"csp": { "csp": {
"default-src": "'self' customprotocol: asset:", "default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: https://ipc.localhost",
"font-src": ["https://fonts.gstatic.com"], "font-src": ["https://fonts.gstatic.com"],
"img-src": "'self' asset: https://asset.localhost blob: data:", "img-src": "'self' asset: https://asset.localhost blob: data:",
"style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com" "style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
@ -136,11 +137,6 @@
"deny": ["$APPDATA/db/*.stronghold"] "deny": ["$APPDATA/db/*.stronghold"]
} }
} }
},
"systemTray": {
"iconPath": "icons/tray_icon_with_transparency.png",
"iconAsTemplate": true,
"menuOnLeftClick": false
} }
} }
} }

@ -1,7 +1,7 @@
<script> <script>
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { open } from "@tauri-apps/plugin-shell"; import { open } from "@tauri-apps/plugin-shell";
import { appWindow, getCurrent } from "@tauri-apps/plugin-window"; import { getCurrent } from "@tauri-apps/plugin-window";
import * as os from "@tauri-apps/plugin-os"; import * as os from "@tauri-apps/plugin-os";
import Welcome from "./views/Welcome.svelte"; import Welcome from "./views/Welcome.svelte";
@ -23,6 +23,8 @@
import { onMount } from "svelte"; import { onMount } from "svelte";
import { ask } from "@tauri-apps/plugin-dialog"; import { ask } from "@tauri-apps/plugin-dialog";
const appWindow = getCurrent();
if (appWindow.label !== "main") { if (appWindow.label !== "main") {
appWindow.onCloseRequested(async (event) => { appWindow.onCloseRequested(async (event) => {
const confirmed = await confirm("Are you sure?"); const confirmed = await confirm("Are you sure?");
@ -127,20 +129,20 @@
// Window controls // Window controls
let isWindowMaximized; let isWindowMaximized;
onMount(async () => { onMount(async () => {
const window = getCurrent(); isWindowMaximized = await appWindow.isMaximized();
isWindowMaximized = await window.isMaximized(); appWindow.onResized(async () => {
window.onResized(async () => { isWindowMaximized = await appWindow.isMaximized();
isWindowMaximized = await window.isMaximized();
}); });
}); });
function minimize() { function minimize() {
getCurrent().minimize(); appWindow.minimize();
} }
async function toggleMaximize() { async function toggleMaximize() {
const window = getCurrent(); (await appWindow.isMaximized())
(await window.isMaximized()) ? window.unmaximize() : window.maximize(); ? appWindow.unmaximize()
: appWindow.maximize();
} }
let confirmed_close = false; let confirmed_close = false;
@ -153,7 +155,7 @@
} }
); );
if (confirmed_close) { if (confirmed_close) {
getCurrent().close(); appWindow.close();
} }
} }
} }

@ -1,8 +1,10 @@
<script> <script>
import { appWindow } from "@tauri-apps/plugin-window"; import { getCurrent } from "@tauri-apps/plugin-window";
import { invoke } from "@tauri-apps/api/tauri"; import { invoke } from "@tauri-apps/api/tauri";
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
const appWindow = getCurrent();
export let onMessage; export let onMessage;
let unlisten; let unlisten;

@ -1,5 +1,5 @@
<script> <script>
import { getClient, Body, ResponseType } from "@tauri-apps/plugin-http"; import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
import { JsonView } from "@zerodevx/svelte-json-view"; import { JsonView } from "@zerodevx/svelte-json-view";
let httpMethod = "GET"; let httpMethod = "GET";
@ -8,53 +8,62 @@
export let onMessage; export let onMessage;
async function makeHttpRequest() { async function makeHttpRequest() {
const client = await getClient().catch((e) => {
onMessage(e);
throw e;
});
let method = httpMethod || "GET"; let method = httpMethod || "GET";
const options = { const options = {
url: "http://localhost:3003",
method: method || "GET", method: method || "GET",
headers: {},
}; };
let bodyType;
if (method !== "GET") {
options.body = httpBody;
if ( if (
(httpBody.startsWith("{") && httpBody.endsWith("}")) || (httpBody.startsWith("{") && httpBody.endsWith("}")) ||
(httpBody.startsWith("[") && httpBody.endsWith("]")) (httpBody.startsWith("[") && httpBody.endsWith("]"))
) { ) {
options.body = Body.json(JSON.parse(httpBody)); options.headers["Content-Type"] = "application/json";
bodyType = "json";
} else if (httpBody !== "") { } else if (httpBody !== "") {
options.body = Body.text(httpBody); bodyType = "text";
} }
}
const response = await tauriFetch("http://localhost:3003", options);
const body =
bodyType === "json" ? await response.json() : await response.text();
client.request(options).then(onMessage).catch(onMessage); onMessage({
url: response.url,
status: response.status,
ok: response.ok,
headers: Object.fromEntries(response.headers.entries()),
body,
});
} }
/// http form /// http form
let foo = "baz"; let foo = "baz";
let bar = "qux"; let bar = "qux";
let result = null; let result = null;
let multipart = true;
async function doPost() { async function doPost() {
const client = await getClient().catch((e) => { const form = new FormData();
onMessage(e); form.append("foo", foo);
throw e; form.append("bar", bar);
}); const response = await tauriFetch("http://localhost:3003", {
result = await client.request({
url: "http://localhost:3003",
method: "POST", method: "POST",
body: Body.form({ body: form,
foo,
bar,
}),
headers: multipart
? { "Content-Type": "multipart/form-data" }
: undefined,
responseType: ResponseType.Text,
}); });
result = {
url: response.url,
status: response.status,
ok: response.ok,
headers: Object.fromEntries(response.headers.entries()),
body: await response.text(),
};
} }
</script> </script>
@ -87,11 +96,6 @@
<input class="input" bind:value={bar} /> <input class="input" bind:value={bar} />
</div> </div>
<br /> <br />
<label>
<input type="checkbox" bind:checked={multipart} />
Multipart
</label>
<br />
<br /> <br />
<button class="btn" type="button" on:click={doPost}> Post it</button> <button class="btn" type="button" on:click={doPost}> Post it</button>
<br /> <br />

@ -1,17 +1,19 @@
<script> <script>
import { import {
appWindow, getCurrent,
WebviewWindow,
LogicalSize, LogicalSize,
UserAttentionType, UserAttentionType,
PhysicalSize, PhysicalSize,
PhysicalPosition, PhysicalPosition,
Effect, Effect,
EffectState, EffectState,
Window
} from "@tauri-apps/plugin-window"; } from "@tauri-apps/plugin-window";
import { open as openDialog } from "@tauri-apps/plugin-dialog"; import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { open } from "@tauri-apps/plugin-shell"; import { open } from "@tauri-apps/plugin-shell";
const appWindow = getCurrent();
let selectedWindow = appWindow.label; let selectedWindow = appWindow.label;
const windowMap = { const windowMap = {
[appWindow.label]: appWindow, [appWindow.label]: appWindow,
@ -146,7 +148,7 @@
function createWindow() { function createWindow() {
if (!newWindowLabel) return; if (!newWindowLabel) return;
const webview = new WebviewWindow(newWindowLabel); const webview = new Window(newWindowLabel);
windowMap[newWindowLabel] = webview; windowMap[newWindowLabel] = webview;
webview.once("tauri://error", function () { webview.once("tauri://error", function () {
onMessage("Error creating new webview"); onMessage("Error creating new webview");

@ -6,28 +6,32 @@
"scripts": { "scripts": {
"build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build", "build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
"lint": "eslint .", "lint": "eslint .",
"format": "prettier --write .", "format": "prettier --write \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\" --ignore-path .prettierignore",
"format-check": "prettier --check ." "format-check": "prettier --check \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\" --ignore-path .prettierignore"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.1.0", "@rollup/plugin-node-resolve": "15.1.0",
"@rollup/plugin-terser": "^0.4.3", "@rollup/plugin-terser": "0.4.3",
"@rollup/plugin-typescript": "^11.1.1", "@rollup/plugin-typescript": "11.1.2",
"@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/eslint-plugin": "6.1.0",
"@typescript-eslint/parser": "^5.59.11", "@typescript-eslint/parser": "6.1.0",
"covector": "^0.9.0", "covector": "^0.9.0",
"eslint": "^8.43.0", "eslint": "8.45.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "8.8.0",
"eslint-config-standard-with-typescript": "^35.0.0", "eslint-config-standard-with-typescript": "36.1.0",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "^16.0.0", "eslint-plugin-n": "16.0.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-security": "^1.7.1", "eslint-plugin-security": "1.7.1",
"prettier": "^2.8.8", "prettier": "3.0.0",
"rollup": "^3.25.1", "rollup": "3.26.3",
"typescript": "^5.1.3" "typescript": "5.1.6"
},
"resolutions": {
"semver": ">=7.5.2",
"optionator": ">=0.9.3"
}, },
"engines": { "engines": {
"pnpm": ">=7.33.0" "pnpm": ">=7.33.1"
} }
} }

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,10 +1,13 @@
[package] [package]
name = "tauri-plugin-app" name = "tauri-plugin-app"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "APIs to read application metadata and change app visibility on macOS." description = "APIs to read application metadata and change app visibility on macOS."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
tauri = { workspace = true } tauri = { workspace = true }

@ -1,4 +1,4 @@
![plugin-app](banner.png) ![plugin-app](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/app/banner.png)
This plugin provides APIs to read application metadata and macOS app visibility functions. This plugin provides APIs to read application metadata and macOS app visibility functions.

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-app", "name": "@tauri-apps/plugin-app",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.5.0" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,12 +1,15 @@
[package] [package]
name = "tauri-plugin-authenticator" name = "tauri-plugin-authenticator"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Use hardware security-keys in your Tauri App." description = "Use hardware security-keys in your Tauri App."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

@ -1,4 +1,4 @@
![plugin-authenticator](banner.png) ![plugin-authenticator](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/authenticator/banner.png)
Use hardware security-keys in your Tauri App. Use hardware security-keys in your Tauri App.
@ -93,7 +93,7 @@ const r2 = await auth.verifyRegistration(
challenge, challenge,
app, app,
registerResult.registerData, registerResult.registerData,
registerResult.clientData registerResult.clientData,
); );
const j2 = JSON.parse(r2); const j2 = JSON.parse(r2);
@ -108,7 +108,7 @@ const counter = await auth.verifySignature(
signData.signData, signData.signData,
clientData, clientData,
keyHandle, keyHandle,
pubkey pubkey,
); );
if (counter && counter > 0) { if (counter && counter > 0) {

@ -25,7 +25,7 @@ export class Authenticator {
challenge: string, challenge: string,
application: string, application: string,
registerData: string, registerData: string,
clientData: string clientData: string,
): Promise<string> { ): Promise<string> {
return await window.__TAURI_INVOKE__( return await window.__TAURI_INVOKE__(
"plugin:authenticator|verify_registration", "plugin:authenticator|verify_registration",
@ -34,14 +34,14 @@ export class Authenticator {
application, application,
registerData, registerData,
clientData, clientData,
} },
); );
} }
async sign( async sign(
challenge: string, challenge: string,
application: string, application: string,
keyHandle: string keyHandle: string,
): Promise<string> { ): Promise<string> {
return await window.__TAURI_INVOKE__("plugin:authenticator|sign", { return await window.__TAURI_INVOKE__("plugin:authenticator|sign", {
timeout: 10000, timeout: 10000,
@ -57,7 +57,7 @@ export class Authenticator {
signData: string, signData: string,
clientData: string, clientData: string,
keyHandle: string, keyHandle: string,
pubkey: string pubkey: string,
): Promise<number> { ): Promise<number> {
return await window.__TAURI_INVOKE__( return await window.__TAURI_INVOKE__(
"plugin:authenticator|verify_signature", "plugin:authenticator|verify_signature",
@ -68,7 +68,7 @@ export class Authenticator {
clientData, clientData,
keyHandle, keyHandle,
pubkey, pubkey,
} },
); );
} }
} }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-authenticator", "name": "@tauri-apps/plugin-authenticator",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"description": "Use hardware security-keys in your Tauri App.", "description": "Use hardware security-keys in your Tauri App.",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
@ -25,9 +25,9 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.5.0" "tslib": "2.6.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,12 +1,15 @@
[package] [package]
name = "tauri-plugin-autostart" name = "tauri-plugin-autostart"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Automatically launch your application at startup." description = "Automatically launch your application at startup."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

@ -1,4 +1,4 @@
![plugin-autostart](banner.png) ![plugin-autostart](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/autostart/banner.png)
Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux. Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux.

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-autostart", "name": "@tauri-apps/plugin-autostart",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -24,9 +24,9 @@
"LICENSE" "LICENSE"
], ],
"devDependencies": { "devDependencies": {
"tslib": "^2.5.0" "tslib": "2.6.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,11 +1,14 @@
[package] [package]
name = "tauri-plugin-cli" name = "tauri-plugin-cli"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Parse arguments from your Tauri application's command line interface." description = "Parse arguments from your Tauri application's command line interface."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

@ -1,4 +1,4 @@
![plugin-cli](banner.png) ![plugin-cli](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/cli/banner.png)
Parse arguments from your Command Line Interface. Parse arguments from your Command Line Interface.

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-cli", "name": "@tauri-apps/plugin-cli",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.4.1" "tslib": "^2.4.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,12 +1,15 @@
[package] [package]
name = "tauri-plugin-clipboard-manager" name = "tauri-plugin-clipboard-manager"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Read and write to the system clipboard." description = "Read and write to the system clipboard."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
links = "tauri-plugin-clipboard-manager" links = "tauri-plugin-clipboard-manager"
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[build-dependencies] [build-dependencies]
tauri-build = { workspace = true } tauri-build = { workspace = true }

@ -1,4 +1,4 @@
![plugin-clipboard-manager](banner.png) ![plugin-clipboard-manager](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/clipboard-manager/banner.png)
Read and write to the system clipboard. Read and write to the system clipboard.

@ -36,7 +36,7 @@ type ClipResponse = Clip<"PlainText", string>;
*/ */
async function writeText( async function writeText(
text: string, text: string,
opts?: { label?: string } opts?: { label?: string },
): Promise<void> { ): Promise<void> {
return window.__TAURI_INVOKE__("plugin:clipboard|write", { return window.__TAURI_INVOKE__("plugin:clipboard|write", {
data: { data: {
@ -60,7 +60,7 @@ async function writeText(
*/ */
async function readText(): Promise<string> { async function readText(): Promise<string> {
const kind: ClipResponse = await window.__TAURI_INVOKE__( const kind: ClipResponse = await window.__TAURI_INVOKE__(
"plugin:clipboard|read" "plugin:clipboard|read",
); );
return kind.options; return kind.options;
} }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-clipboard-manager", "name": "@tauri-apps/plugin-clipboard-manager",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.4.1" "tslib": "^2.4.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1,5 +1,19 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
d6e80b)([#545](https://github.com/tauri-apps/plugins-workspace/pull/545)) Fixes docs.rs build by enabling the `tauri/dox` feature flag.
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
### Dependencies
- Upgraded to `fs@2.0.0-alpha.1`
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,19 +1,22 @@
[package] [package]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application." description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
links = "tauri-plugin-dialog" links = "tauri-plugin-dialog"
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
log = { workspace = true } log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.0" } tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.1" }
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
glib = "0.16" glib = "0.16"

@ -1,4 +1,4 @@
![plugin-dialog](banner.png) ![plugin-dialog](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/dialog/banner.png)
Native system dialogs for opening and saving files along with message dialogs. Native system dialogs for opening and saving files along with message dialogs.

@ -103,16 +103,16 @@ interface ConfirmDialogOptions {
} }
async function open( async function open(
options?: OpenDialogOptions & { multiple?: false; directory?: false } options?: OpenDialogOptions & { multiple?: false; directory?: false },
): Promise<null | FileResponse>; ): Promise<null | FileResponse>;
async function open( async function open(
options?: OpenDialogOptions & { multiple?: true; directory?: false } options?: OpenDialogOptions & { multiple?: true; directory?: false },
): Promise<null | FileResponse[]>; ): Promise<null | FileResponse[]>;
async function open( async function open(
options?: OpenDialogOptions & { multiple?: false; directory?: true } options?: OpenDialogOptions & { multiple?: false; directory?: true },
): Promise<null | string>; ): Promise<null | string>;
async function open( async function open(
options?: OpenDialogOptions & { multiple?: true; directory?: true } options?: OpenDialogOptions & { multiple?: true; directory?: true },
): Promise<null | string[]>; ): Promise<null | string[]>;
/** /**
* Open a file/directory selection dialog. * Open a file/directory selection dialog.
@ -125,7 +125,7 @@ async function open(
* You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
* @example * @example
* ```typescript * ```typescript
* import { open } from '@tauri-apps/api/dialog'; * import { open } from '@tauri-apps/plugin-dialog';
* // Open a selection dialog for image files * // Open a selection dialog for image files
* const selected = await open({ * const selected = await open({
* multiple: true, * multiple: true,
@ -145,7 +145,7 @@ async function open(
* *
* @example * @example
* ```typescript * ```typescript
* import { open } from '@tauri-apps/api/dialog'; * import { open } from '@tauri-apps/plugin-dialog';
* import { appDir } from '@tauri-apps/api/path'; * import { appDir } from '@tauri-apps/api/path';
* // Open a selection dialog for directories * // Open a selection dialog for directories
* const selected = await open({ * const selected = await open({
@ -167,7 +167,7 @@ async function open(
* @since 2.0.0 * @since 2.0.0
*/ */
async function open( async function open(
options: OpenDialogOptions = {} options: OpenDialogOptions = {},
): Promise<null | string | string[] | FileResponse | FileResponse[]> { ): Promise<null | string | string[] | FileResponse | FileResponse[]> {
if (typeof options === "object") { if (typeof options === "object") {
Object.freeze(options); Object.freeze(options);
@ -187,7 +187,7 @@ async function open(
* You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope). * You can save it to the filesystem using [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
* @example * @example
* ```typescript * ```typescript
* import { save } from '@tauri-apps/api/dialog'; * import { save } from '@tauri-apps/plugin-dialog';
* const filePath = await save({ * const filePath = await save({
* filters: [{ * filters: [{
* name: 'Image', * name: 'Image',
@ -212,7 +212,7 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
* Shows a message dialog with an `Ok` button. * Shows a message dialog with an `Ok` button.
* @example * @example
* ```typescript * ```typescript
* import { message } from '@tauri-apps/api/dialog'; * import { message } from '@tauri-apps/plugin-dialog';
* await message('Tauri is awesome', 'Tauri'); * await message('Tauri is awesome', 'Tauri');
* await message('File not found', { title: 'Tauri', type: 'error' }); * await message('File not found', { title: 'Tauri', type: 'error' });
* ``` * ```
@ -227,7 +227,7 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
*/ */
async function message( async function message(
message: string, message: string,
options?: string | MessageDialogOptions options?: string | MessageDialogOptions,
): Promise<void> { ): Promise<void> {
const opts = typeof options === "string" ? { title: options } : options; const opts = typeof options === "string" ? { title: options } : options;
return window.__TAURI_INVOKE__("plugin:dialog|message", { return window.__TAURI_INVOKE__("plugin:dialog|message", {
@ -242,7 +242,7 @@ async function message(
* Shows a question dialog with `Yes` and `No` buttons. * Shows a question dialog with `Yes` and `No` buttons.
* @example * @example
* ```typescript * ```typescript
* import { ask } from '@tauri-apps/api/dialog'; * import { ask } from '@tauri-apps/plugin-dialog';
* const yes = await ask('Are you sure?', 'Tauri'); * const yes = await ask('Are you sure?', 'Tauri');
* const yes2 = await ask('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' }); * const yes2 = await ask('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' });
* ``` * ```
@ -256,7 +256,7 @@ async function message(
*/ */
async function ask( async function ask(
message: string, message: string,
options?: string | ConfirmDialogOptions options?: string | ConfirmDialogOptions,
): Promise<boolean> { ): Promise<boolean> {
const opts = typeof options === "string" ? { title: options } : options; const opts = typeof options === "string" ? { title: options } : options;
return window.__TAURI_INVOKE__("plugin:dialog|ask", { return window.__TAURI_INVOKE__("plugin:dialog|ask", {
@ -272,7 +272,7 @@ async function ask(
* Shows a question dialog with `Ok` and `Cancel` buttons. * Shows a question dialog with `Ok` and `Cancel` buttons.
* @example * @example
* ```typescript * ```typescript
* import { confirm } from '@tauri-apps/api/dialog'; * import { confirm } from '@tauri-apps/plugin-dialog';
* const confirmed = await confirm('Are you sure?', 'Tauri'); * const confirmed = await confirm('Are you sure?', 'Tauri');
* const confirmed2 = await confirm('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' }); * const confirmed2 = await confirm('This action cannot be reverted. Are you sure?', { title: 'Tauri', type: 'warning' });
* ``` * ```
@ -286,7 +286,7 @@ async function ask(
*/ */
async function confirm( async function confirm(
message: string, message: string,
options?: string | ConfirmDialogOptions options?: string | ConfirmDialogOptions,
): Promise<boolean> { ): Promise<boolean> {
const opts = typeof options === "string" ? { title: options } : options; const opts = typeof options === "string" ? { title: options } : options;
return window.__TAURI_INVOKE__("plugin:dialog|confirm", { return window.__TAURI_INVOKE__("plugin:dialog|confirm", {

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-dialog", "name": "@tauri-apps/plugin-dialog",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.4.1" "tslib": "^2.4.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -23,6 +23,11 @@ type FileDialog = rfd::FileDialog;
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
type FileDialog = rfd::AsyncFileDialog; type FileDialog = rfd::AsyncFileDialog;
#[cfg(target_os = "linux")]
type MessageDialog = rfd::MessageDialog;
#[cfg(not(target_os = "linux"))]
type MessageDialog = rfd::AsyncMessageDialog;
pub fn init<R: Runtime, C: DeserializeOwned>( pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>, app: &AppHandle<R>,
_api: PluginApi<R, C>, _api: PluginApi<R, C>,
@ -50,7 +55,7 @@ impl<R: Runtime> Dialog<R> {
macro_rules! run_dialog { macro_rules! run_dialog {
($e:expr, $h: ident) => {{ ($e:expr, $h: ident) => {{
std::thread::spawn(move || { std::thread::spawn(move || {
let response = $e; let response = tauri::async_runtime::block_on($e);
$h(response); $h(response);
}); });
}}; }};
@ -136,9 +141,9 @@ impl<R: Runtime> From<FileDialogBuilder<R>> for FileDialog {
} }
} }
impl<R: Runtime> From<MessageDialogBuilder<R>> for rfd::MessageDialog { impl<R: Runtime> From<MessageDialogBuilder<R>> for MessageDialog {
fn from(d: MessageDialogBuilder<R>) -> Self { fn from(d: MessageDialogBuilder<R>) -> Self {
let mut dialog = rfd::MessageDialog::new() let mut dialog = MessageDialog::new()
.set_title(&d.title) .set_title(&d.title)
.set_description(&d.message) .set_description(&d.message)
.set_level(d.kind.into()); .set_level(d.kind.into());
@ -215,5 +220,5 @@ pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
dialog: MessageDialogBuilder<R>, dialog: MessageDialogBuilder<R>,
f: F, f: F,
) { ) {
run_dialog!(rfd::MessageDialog::from(dialog).show(), f); run_dialog!(MessageDialog::from(dialog).show(), f);
} }

@ -209,7 +209,9 @@ impl<R: Runtime> MessageDialogBuilder<R> {
show_message_dialog(self, f) show_message_dialog(self, f)
} }
//// Shows a message dialog. /// Shows a message dialog.
/// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context.
pub fn blocking_show(self) -> bool { pub fn blocking_show(self) -> bool {
blocking_fn!(self, show) blocking_fn!(self, show)
} }

@ -1,5 +1,10 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`0bba693`](https://github.com/tauri-apps/plugins-workspace/commit/0bba6932c09da5267a9dbf75ba52252e39458420)([#454](https://github.com/tauri-apps/plugins-workspace/pull/454)) Fix `writeBinaryFile` crashing with `command 'write_binary_file' not found`
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,12 +1,15 @@
[package] [package]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Access the file system." description = "Access the file system."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
edition = { workspace = true } edition = { workspace = true }
rust-version = { workspace = true } rust-version = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }

@ -1,4 +1,4 @@
![plugin-fs](banner.png) ![plugin-fs](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/fs/banner.png)
Access the file system. Access the file system.

@ -229,7 +229,7 @@ interface FileEntry {
*/ */
async function readTextFile( async function readTextFile(
filePath: string, filePath: string,
options: FsOptions = {} options: FsOptions = {},
): Promise<string> { ): Promise<string> {
return await window.__TAURI_INVOKE__("plugin:fs|read_text_file", { return await window.__TAURI_INVOKE__("plugin:fs|read_text_file", {
path: filePath, path: filePath,
@ -250,7 +250,7 @@ async function readTextFile(
*/ */
async function readBinaryFile( async function readBinaryFile(
filePath: string, filePath: string,
options: FsOptions = {} options: FsOptions = {},
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const arr = await window.__TAURI_INVOKE__<number[]>("plugin:fs|read_file", { const arr = await window.__TAURI_INVOKE__<number[]>("plugin:fs|read_file", {
path: filePath, path: filePath,
@ -274,7 +274,7 @@ async function readBinaryFile(
async function writeTextFile( async function writeTextFile(
path: string, path: string,
contents: string, contents: string,
options?: FsOptions options?: FsOptions,
): Promise<void>; ): Promise<void>;
/** /**
@ -291,7 +291,7 @@ async function writeTextFile(
*/ */
async function writeTextFile( async function writeTextFile(
file: FsTextFileOption, file: FsTextFileOption,
options?: FsOptions options?: FsOptions,
): Promise<void>; ): Promise<void>;
/** /**
@ -304,7 +304,7 @@ async function writeTextFile(
async function writeTextFile( async function writeTextFile(
path: string | FsTextFileOption, path: string | FsTextFileOption,
contents?: string | FsOptions, contents?: string | FsOptions,
options?: FsOptions options?: FsOptions,
): Promise<void> { ): Promise<void> {
if (typeof options === "object") { if (typeof options === "object") {
Object.freeze(options); Object.freeze(options);
@ -352,7 +352,7 @@ async function writeTextFile(
async function writeBinaryFile( async function writeBinaryFile(
path: string, path: string,
contents: BinaryFileContents, contents: BinaryFileContents,
options?: FsOptions options?: FsOptions,
): Promise<void>; ): Promise<void>;
/** /**
@ -372,7 +372,7 @@ async function writeBinaryFile(
*/ */
async function writeBinaryFile( async function writeBinaryFile(
file: FsBinaryFileOption, file: FsBinaryFileOption,
options?: FsOptions options?: FsOptions,
): Promise<void>; ): Promise<void>;
/** /**
@ -385,7 +385,7 @@ async function writeBinaryFile(
async function writeBinaryFile( async function writeBinaryFile(
path: string | FsBinaryFileOption, path: string | FsBinaryFileOption,
contents?: BinaryFileContents | FsOptions, contents?: BinaryFileContents | FsOptions,
options?: FsOptions options?: FsOptions,
): Promise<void> { ): Promise<void> {
if (typeof options === "object") { if (typeof options === "object") {
Object.freeze(options); Object.freeze(options);
@ -410,12 +410,12 @@ async function writeBinaryFile(
file.contents = contents ?? []; file.contents = contents ?? [];
} }
return await window.__TAURI_INVOKE__("plugin:fs|write_binary_file", { return await window.__TAURI_INVOKE__("plugin:fs|write_file", {
path: file.path, path: file.path,
contents: Array.from( contents: Array.from(
file.contents instanceof ArrayBuffer file.contents instanceof ArrayBuffer
? new Uint8Array(file.contents) ? new Uint8Array(file.contents)
: file.contents : file.contents,
), ),
options: fileOptions, options: fileOptions,
}); });
@ -443,7 +443,7 @@ async function writeBinaryFile(
*/ */
async function readDir( async function readDir(
dir: string, dir: string,
options: FsDirOptions = {} options: FsDirOptions = {},
): Promise<FileEntry[]> { ): Promise<FileEntry[]> {
return await window.__TAURI_INVOKE__("plugin:fs|read_dir", { return await window.__TAURI_INVOKE__("plugin:fs|read_dir", {
path: dir, path: dir,
@ -468,7 +468,7 @@ async function readDir(
*/ */
async function createDir( async function createDir(
dir: string, dir: string,
options: FsDirOptions = {} options: FsDirOptions = {},
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:fs|create_dir", { return await window.__TAURI_INVOKE__("plugin:fs|create_dir", {
path: dir, path: dir,
@ -492,7 +492,7 @@ async function createDir(
*/ */
async function removeDir( async function removeDir(
dir: string, dir: string,
options: FsDirOptions = {} options: FsDirOptions = {},
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:fs|remove_dir", { return await window.__TAURI_INVOKE__("plugin:fs|remove_dir", {
path: dir, path: dir,
@ -516,7 +516,7 @@ async function removeDir(
async function copyFile( async function copyFile(
source: string, source: string,
destination: string, destination: string,
options: FsOptions = {} options: FsOptions = {},
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:fs|copy_file", { return await window.__TAURI_INVOKE__("plugin:fs|copy_file", {
source, source,
@ -540,7 +540,7 @@ async function copyFile(
*/ */
async function removeFile( async function removeFile(
file: string, file: string,
options: FsOptions = {} options: FsOptions = {},
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:fs|remove_file", { return await window.__TAURI_INVOKE__("plugin:fs|remove_file", {
path: file, path: file,
@ -564,7 +564,7 @@ async function removeFile(
async function renameFile( async function renameFile(
oldPath: string, oldPath: string,
newPath: string, newPath: string,
options: FsOptions = {} options: FsOptions = {},
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:fs|rename_file", { return await window.__TAURI_INVOKE__("plugin:fs|rename_file", {
oldPath, oldPath,

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-fs", "name": "@tauri-apps/plugin-fs",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"description": "Access the file system.", "description": "Access the file system.",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
@ -28,6 +28,6 @@
"tslib": "^2.4.1" "tslib": "^2.4.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

File diff suppressed because one or more lines are too long

@ -5,7 +5,7 @@
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
use serde::Deserialize; use serde::Deserialize;
use tauri::{api::ipc::Channel, command, Runtime, State}; use tauri::{command, ipc::Channel, State};
use crate::Result; use crate::Result;
@ -30,7 +30,7 @@ enum WatcherKind {
Watcher(RecommendedWatcher), Watcher(RecommendedWatcher),
} }
fn watch_raw<R: Runtime>(on_event: Channel<R>, rx: Receiver<notify::Result<Event>>) { fn watch_raw(on_event: Channel, rx: Receiver<notify::Result<Event>>) {
spawn(move || { spawn(move || {
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
if let Ok(event) = event { if let Ok(event) = event {
@ -41,7 +41,7 @@ fn watch_raw<R: Runtime>(on_event: Channel<R>, rx: Receiver<notify::Result<Event
}); });
} }
fn watch_debounced<R: Runtime>(on_event: Channel<R>, rx: Receiver<DebounceEventResult>) { fn watch_debounced(on_event: Channel, rx: Receiver<DebounceEventResult>) {
spawn(move || { spawn(move || {
while let Ok(event) = rx.recv() { while let Ok(event) = rx.recv() {
if let Ok(event) = event { if let Ok(event) = event {
@ -60,12 +60,12 @@ pub struct WatchOptions {
} }
#[command] #[command]
pub async fn watch<R: Runtime>( pub async fn watch(
watchers: State<'_, WatcherCollection>, watchers: State<'_, WatcherCollection>,
id: Id, id: Id,
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
options: WatchOptions, options: WatchOptions,
on_event: Channel<R>, on_event: Channel,
) -> Result<()> { ) -> Result<()> {
let mode = if options.recursive { let mode = if options.recursive {
RecursiveMode::Recursive RecursiveMode::Recursive

@ -1,5 +1,14 @@
# Changelog # Changelog
## \[2.0.0-alpha.1]
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!
te to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,11 +1,14 @@
[package] [package]
name = "tauri-plugin-global-shortcut" name = "tauri-plugin-global-shortcut"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.1"
description = "Register global hotkeys listeners on your Tauri application." description = "Register global hotkeys listeners on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }

@ -1,4 +1,4 @@
![plugin-global-shortcut](banner.png) ![plugin-global-shortcut](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/global-shortcut/banner.png)
Register global shortcuts. Register global shortcuts.

@ -8,14 +8,7 @@
* @module * @module
*/ */
declare global { import { invoke, Channel } from "@tauri-apps/api/tauri";
interface Window {
__TAURI_INVOKE__: <T>(cmd: string, args?: unknown) => Promise<T>;
__TAURI__: {
transformCallback: <T>(cb: (payload: T) => void) => number;
};
}
}
export type ShortcutHandler = (shortcut: string) => void; export type ShortcutHandler = (shortcut: string) => void;
@ -36,11 +29,14 @@ export type ShortcutHandler = (shortcut: string) => void;
*/ */
async function register( async function register(
shortcut: string, shortcut: string,
handler: ShortcutHandler handler: ShortcutHandler,
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:globalShortcut|register", { const h = new Channel<string>();
h.onmessage = handler;
return await invoke("plugin:globalShortcut|register", {
shortcut, shortcut,
handler: window.__TAURI__.transformCallback(handler), handler: h,
}); });
} }
@ -61,11 +57,14 @@ async function register(
*/ */
async function registerAll( async function registerAll(
shortcuts: string[], shortcuts: string[],
handler: ShortcutHandler handler: ShortcutHandler,
): Promise<void> { ): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:globalShortcut|register_all", { const h = new Channel<string>();
h.onmessage = handler;
return await invoke("plugin:globalShortcut|register_all", {
shortcuts, shortcuts,
handler: window.__TAURI__.transformCallback(handler), handler: h,
}); });
} }
@ -85,7 +84,7 @@ async function registerAll(
* @since 2.0.0 * @since 2.0.0
*/ */
async function isRegistered(shortcut: string): Promise<boolean> { async function isRegistered(shortcut: string): Promise<boolean> {
return await window.__TAURI_INVOKE__("plugin:globalShortcut|is_registered", { return await invoke("plugin:globalShortcut|is_registered", {
shortcut, shortcut,
}); });
} }
@ -103,7 +102,7 @@ async function isRegistered(shortcut: string): Promise<boolean> {
* @since 2.0.0 * @since 2.0.0
*/ */
async function unregister(shortcut: string): Promise<void> { async function unregister(shortcut: string): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:globalShortcut|unregister", { return await invoke("plugin:globalShortcut|unregister", {
shortcut, shortcut,
}); });
} }
@ -119,7 +118,7 @@ async function unregister(shortcut: string): Promise<void> {
* @since 2.0.0 * @since 2.0.0
*/ */
async function unregisterAll(): Promise<void> { async function unregisterAll(): Promise<void> {
return await window.__TAURI_INVOKE__("plugin:globalShortcut|unregister_all"); return await invoke("plugin:globalShortcut|unregister_all");
} }
export { register, registerAll, isRegistered, unregister, unregisterAll }; export { register, registerAll, isRegistered, unregister, unregisterAll };

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-global-shortcut", "name": "@tauri-apps/plugin-global-shortcut",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.4.1" "tslib": "^2.4.1"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_GLOBALSHORTCUT__=function(_){"use strict";return _.isRegistered=async function(_){return await window.__TAURI_INVOKE__("plugin:globalShortcut|is_registered",{shortcut:_})},_.register=async function(_,t){return await window.__TAURI_INVOKE__("plugin:globalShortcut|register",{shortcut:_,handler:window.__TAURI__.transformCallback(t)})},_.registerAll=async function(_,t){return await window.__TAURI_INVOKE__("plugin:globalShortcut|register_all",{shortcuts:_,handler:window.__TAURI__.transformCallback(t)})},_.unregister=async function(_){return await window.__TAURI_INVOKE__("plugin:globalShortcut|unregister",{shortcut:_})},_.unregisterAll=async function(){return await window.__TAURI_INVOKE__("plugin:globalShortcut|unregister_all")},_}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_GLOBALSHORTCUT__})} if("__TAURI__"in window){var __TAURI_GLOBALSHORTCUT__=function(e){"use strict";var t=Object.defineProperty,n=(e,t,n)=>{if(!t.has(e))throw TypeError("Cannot "+n)},r=(e,t,r)=>(n(e,t,"read from private field"),r?r.call(e):t.get(e)),i=(e,t,r,i)=>(n(e,t,"write to private field"),i?i.call(e,r):t.set(e,r),r);function a(e,t=!1){let n=window.crypto.getRandomValues(new Uint32Array(1))[0],r=`_${n}`;return Object.defineProperty(window,r,{value:n=>(t&&Reflect.deleteProperty(window,r),e?.(n)),writable:!1,configurable:!0}),n}((e,n)=>{for(var r in n)t(e,r,{get:n[r],enumerable:!0})})({},{Channel:()=>s,PluginListener:()=>l,addPluginListener:()=>c,convertFileSrc:()=>_,invoke:()=>u,transformCallback:()=>a});var o,s=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,((e,t,n)=>{if(t.has(e))throw TypeError("Cannot add the same private member more than once");t instanceof WeakSet?t.add(e):t.set(e,n)})(this,o,(()=>{})),this.id=a((e=>{r(this,o).call(this,e)}))}set onmessage(e){i(this,o,e)}get onmessage(){return r(this,o)}toJSON(){return`__CHANNEL__:${this.id}`}};o=new WeakMap;var l=class{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return u(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function c(e,t,n){let r=new s;return r.onmessage=n,u(`plugin:${e}|register_listener`,{event:t,handler:r}).then((()=>new l(e,t,r.id)))}async function u(e,t={},n){return new Promise(((r,i)=>{let o=a((e=>{r(e),Reflect.deleteProperty(window,`_${s}`)}),!0),s=a((e=>{i(e),Reflect.deleteProperty(window,`_${o}`)}),!0);window.__TAURI_IPC__({cmd:e,callback:o,error:s,payload:t,options:n})}))}function _(e,t="asset"){return window.__TAURI__.convertFileSrc(e,t)}return e.isRegistered=async function(e){return await u("plugin:globalShortcut|is_registered",{shortcut:e})},e.register=async function(e,t){const n=new s;return n.onmessage=t,await u("plugin:globalShortcut|register",{shortcut:e,handler:n})},e.registerAll=async function(e,t){const n=new s;return n.onmessage=t,await u("plugin:globalShortcut|register_all",{shortcuts:e,handler:n})},e.unregister=async function(e){return await u("plugin:globalShortcut|unregister",{shortcut:e})},e.unregisterAll=async function(){return await u("plugin:globalShortcut|unregister_all")},e}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_GLOBALSHORTCUT__})}

@ -23,7 +23,7 @@ use std::{
pub use global_hotkey::hotkey::{Code, HotKey as Shortcut, Modifiers}; pub use global_hotkey::hotkey::{Code, HotKey as Shortcut, Modifiers};
use global_hotkey::{GlobalHotKeyEvent, GlobalHotKeyManager}; use global_hotkey::{GlobalHotKeyEvent, GlobalHotKeyManager};
use tauri::{ use tauri::{
api::ipc::CallbackFn, ipc::Channel,
plugin::{Builder as PluginBuilder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
AppHandle, Manager, Runtime, State, Window, AppHandle, Manager, Runtime, State, Window,
}; };
@ -35,21 +35,15 @@ type Result<T> = std::result::Result<T, Error>;
type HotKeyId = u32; type HotKeyId = u32;
type HandlerFn = Box<dyn Fn(&Shortcut) + Send + Sync + 'static>; type HandlerFn = Box<dyn Fn(&Shortcut) + Send + Sync + 'static>;
enum ShortcutSource<R: Runtime> { enum ShortcutSource {
Ipc { Ipc(Channel),
window: Window<R>,
handler: CallbackFn,
},
Rust, Rust,
} }
impl<R: Runtime> Clone for ShortcutSource<R> { impl Clone for ShortcutSource {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {
Self::Ipc { window, handler } => Self::Ipc { Self::Ipc(channel) => Self::Ipc(channel.clone()),
window: window.clone(),
handler: *handler,
},
Self::Rust => Self::Rust, Self::Rust => Self::Rust,
} }
} }
@ -70,8 +64,8 @@ impl TryFrom<&str> for ShortcutWrapper {
} }
} }
struct RegisteredShortcut<R: Runtime> { struct RegisteredShortcut {
source: ShortcutSource<R>, source: ShortcutSource,
shortcut: (Shortcut, Option<String>), shortcut: (Shortcut, Option<String>),
} }
@ -79,14 +73,14 @@ pub struct GlobalShortcut<R: Runtime> {
#[allow(dead_code)] #[allow(dead_code)]
app: AppHandle<R>, app: AppHandle<R>,
manager: std::result::Result<GlobalHotKeyManager, global_hotkey::Error>, manager: std::result::Result<GlobalHotKeyManager, global_hotkey::Error>,
shortcuts: Arc<Mutex<HashMap<HotKeyId, RegisteredShortcut<R>>>>, shortcuts: Arc<Mutex<HashMap<HotKeyId, RegisteredShortcut>>>,
} }
impl<R: Runtime> GlobalShortcut<R> { impl<R: Runtime> GlobalShortcut<R> {
fn register_internal( fn register_internal(
&self, &self,
shortcut: (Shortcut, Option<String>), shortcut: (Shortcut, Option<String>),
source: ShortcutSource<R>, source: ShortcutSource,
) -> Result<()> { ) -> Result<()> {
let id = shortcut.0.id(); let id = shortcut.0.id();
acquire_manager(&self.manager)?.register(shortcut.0)?; acquire_manager(&self.manager)?.register(shortcut.0)?;
@ -100,7 +94,7 @@ impl<R: Runtime> GlobalShortcut<R> {
fn register_all_internal<S: IntoIterator<Item = (Shortcut, Option<String>)>>( fn register_all_internal<S: IntoIterator<Item = (Shortcut, Option<String>)>>(
&self, &self,
shortcuts: S, shortcuts: S,
source: ShortcutSource<R>, source: ShortcutSource,
) -> Result<()> { ) -> Result<()> {
let hotkeys = shortcuts let hotkeys = shortcuts
.into_iter() .into_iter()
@ -218,29 +212,29 @@ where
#[tauri::command] #[tauri::command]
fn register<R: Runtime>( fn register<R: Runtime>(
window: Window<R>, _window: Window<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: State<'_, GlobalShortcut<R>>,
shortcut: String, shortcut: String,
handler: CallbackFn, handler: Channel,
) -> Result<()> { ) -> Result<()> {
global_shortcut.register_internal( global_shortcut.register_internal(
(parse_shortcut(&shortcut)?, Some(shortcut)), (parse_shortcut(&shortcut)?, Some(shortcut)),
ShortcutSource::Ipc { window, handler }, ShortcutSource::Ipc(handler),
) )
} }
#[tauri::command] #[tauri::command]
fn register_all<R: Runtime>( fn register_all<R: Runtime>(
window: Window<R>, _window: Window<R>,
global_shortcut: State<'_, GlobalShortcut<R>>, global_shortcut: State<'_, GlobalShortcut<R>>,
shortcuts: Vec<String>, shortcuts: Vec<String>,
handler: CallbackFn, handler: Channel,
) -> Result<()> { ) -> Result<()> {
let mut hotkeys = Vec::new(); let mut hotkeys = Vec::new();
for shortcut in shortcuts { for shortcut in shortcuts {
hotkeys.push((parse_shortcut(&shortcut)?, Some(shortcut))); hotkeys.push((parse_shortcut(&shortcut)?, Some(shortcut)));
} }
global_shortcut.register_all_internal(hotkeys, ShortcutSource::Ipc { window, handler }) global_shortcut.register_all_internal(hotkeys, ShortcutSource::Ipc(handler))
} }
#[tauri::command] #[tauri::command]
@ -303,19 +297,14 @@ impl Builder {
]) ])
.setup(move |app, _api| { .setup(move |app, _api| {
let shortcuts = let shortcuts =
Arc::new(Mutex::new(HashMap::<HotKeyId, RegisteredShortcut<R>>::new())); Arc::new(Mutex::new(HashMap::<HotKeyId, RegisteredShortcut>::new()));
let shortcuts_ = shortcuts.clone(); let shortcuts_ = shortcuts.clone();
GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| { GlobalHotKeyEvent::set_event_handler(Some(move |e: GlobalHotKeyEvent| {
if let Some(shortcut) = shortcuts_.lock().unwrap().get(&e.id) { if let Some(shortcut) = shortcuts_.lock().unwrap().get(&e.id) {
match &shortcut.source { match &shortcut.source {
ShortcutSource::Ipc { window, handler } => { ShortcutSource::Ipc(channel) => {
let callback_string = tauri::api::ipc::format_callback( let _ = channel.send(&shortcut.shortcut.1);
*handler,
&shortcut.shortcut.1,
)
.expect("unable to serialize shortcut string to json");
let _ = window.eval(callback_string.as_str());
} }
ShortcutSource::Rust => { ShortcutSource::Rust => {
if let Some(handler) = &handler { if let Some(handler) = &handler {

@ -1,5 +1,15 @@
# Changelog # Changelog
## \[2.0.0-alpha.2]
- [`aec17a9`](https://github.com/tauri-apps/plugins-workspace/commit/aec17a90fc365774c70c4876b94a899416120e26)([#558](https://github.com/tauri-apps/plugins-workspace/pull/558)) Improve response performance by using the new IPC streaming data.
## \[2.0.0-alpha.1]
- [`7d9df72`](https://github.com/tauri-apps/plugins-workspace/commit/7d9df7297a221a64d9de945ffc2cd8313d3104dc)([#428](https://github.com/tauri-apps/plugins-workspace/pull/428)) Multipart requests are now handled in JavaScript by the `Request` JavaScript class so you just need to use a `FormData` body and not set the content-type header to `multipart/form-data`. `application/x-www-form-urlencoded` requests must be done manually.
- [`7d9df72`](https://github.com/tauri-apps/plugins-workspace/commit/7d9df7297a221a64d9de945ffc2cd8313d3104dc)([#428](https://github.com/tauri-apps/plugins-workspace/pull/428)) The http plugin has been rewritten from scratch and now only exposes a `fetch` function in Javascript and Re-exports `reqwest` crate in Rust. The new `fetch` method tries to be as close and compliant to the `fetch` Web API as possible.
- [`d74fc0a`](https://github.com/tauri-apps/plugins-workspace/commit/d74fc0a097996e90a37be8f57d50b7d1f6ca616f)([#555](https://github.com/tauri-apps/plugins-workspace/pull/555)) Update to alpha.11.
## \[2.0.0-alpha.0] ## \[2.0.0-alpha.0]
- [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release! - [`717ae67`](https://github.com/tauri-apps/plugins-workspace/commit/717ae670978feb4492fac1f295998b93f2b9347f)([#371](https://github.com/tauri-apps/plugins-workspace/pull/371)) First v2 alpha release!

@ -1,26 +1,43 @@
[package] [package]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.0.0-alpha.0" version = "2.0.0-alpha.2"
description = "Access an HTTP client written in Rust." description = "Access an HTTP client written in Rust."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
[package.metadata.docs.rs]
features = [ "tauri/dox" ]
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.0" } tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.1" }
glob = "0.3" glob = "0.3"
rand = "0.8"
bytes = { version = "1", features = [ "serde" ] }
serde_repr = "0.1"
http = "0.2" http = "0.2"
reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] } reqwest = { version = "0.11", default-features = false }
url = "2.4"
data-url = "0.3"
[features] [features]
multipart = [ "reqwest/multipart" ] multipart = [ "reqwest/multipart" ]
json = [ "reqwest/json" ]
stream = [ "reqwest/stream" ]
native-tls = [ "reqwest/native-tls" ] native-tls = [ "reqwest/native-tls" ]
native-tls-vendored = [ "reqwest/native-tls-vendored" ] native-tls-vendored = [ "reqwest/native-tls-vendored" ]
rustls-tls = [ "reqwest/rustls-tls" ] rustls-tls = [ "reqwest/rustls-tls" ]
default-tls = [ "reqwest/default-tls" ]
native-tls-alpn = [ "reqwest/native-tls-alpn" ]
rustls-tls-manual-roots = [ "reqwest/rustls-tls-manual-roots" ]
rustls-tls-webpki-roots = [ "reqwest/rustls-tls-webpki-roots" ]
rustls-tls-native-roots = [ "reqwest/rustls-tls-native-roots" ]
blocking = [ "reqwest/blocking" ]
cookies = [ "reqwest/cookies" ]
gzip = [ "reqwest/gzip" ]
brotli = [ "reqwest/brotli" ]
deflate = [ "reqwest/deflate" ]
trust-dns = [ "reqwest/trust-dns" ]
socks = [ "reqwest/socks" ]
http3 = [ "reqwest/http3" ]

@ -1,4 +1,4 @@
![plugin-http](banner.png) ![plugin-http](https://github.com/tauri-apps/plugins-workspace/raw/v2/plugins/http/banner.png)
Access the HTTP client written in Rust. Access the HTTP client written in Rust.

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
/** /**
* Access the HTTP client written in Rust. * Make HTTP requests with the Rust backend.
* *
* ## Security * ## Security
* *
@ -31,518 +31,94 @@ declare global {
} }
/** /**
* Options to configure the Rust client used to make fetch requests
*
* @since 2.0.0 * @since 2.0.0
*/ */
interface Duration { export interface ClientOptions {
secs: number;
nanos: number;
}
/**
* @since 2.0.0
*/
interface ClientOptions {
/** /**
* Defines the maximum number of redirects the client should follow. * Defines the maximum number of redirects the client should follow.
* If set to 0, no redirects will be followed. * If set to 0, no redirects will be followed.
*/ */
maxRedirections?: number; maxRedirections?: number;
connectTimeout?: number | Duration; /** Timeout in milliseconds */
} connectTimeout?: number;
/**
* @since 2.0.0
*/
enum ResponseType {
JSON = 1,
Text = 2,
Binary = 3,
} }
/** /**
* @since 2.0.0 * Fetch a resource from the network. It returns a `Promise` that resolves to the
*/ * `Response` to that `Request`, whether it is successful or not.
interface FilePart<T> {
file: string | T;
mime?: string;
fileName?: string;
}
type Part = string | Uint8Array | FilePart<Uint8Array>;
/**
* The body object to be used on POST and PUT requests.
*
* @since 2.0.0
*/
class Body {
type: string;
payload: unknown;
/** @ignore */
private constructor(type: string, payload: unknown) {
this.type = type;
this.payload = payload;
}
/**
* Creates a new form data body. The form data is an object where each key is the entry name,
* and the value is either a string or a file object.
*
* By default it sets the `application/x-www-form-urlencoded` Content-Type header,
* but you can set it to `multipart/form-data` if the Cargo feature `multipart` is enabled.
* *
* Note that a file path must be allowed in the `fs` scope.
* @example * @example
* ```typescript * ```typescript
* import { Body } from "@tauri-apps/plugin-http" * const response = await fetch("http://my.json.host/data.json");
* const body = Body.form({ * console.log(response.status); // e.g. 200
* key: 'value', * console.log(response.statusText); // e.g. "OK"
* image: { * const jsonData = await response.json();
* file: '/path/to/file', // either a path or an array buffer of the file contents
* mime: 'image/jpeg', // optional
* fileName: 'image.jpg' // optional
* }
* });
*
* // alternatively, use a FormData:
* const form = new FormData();
* form.append('key', 'value');
* form.append('image', file, 'image.png');
* const formBody = Body.form(form);
* ``` * ```
* *
* @param data The body data.
*
* @returns The body object ready to be used on the POST and PUT requests.
*
* @since 2.0.0
*/
static form(data: Record<string, Part> | FormData): Body {
const form: Record<string, string | number[] | FilePart<number[]>> = {};
const append = (
key: string,
v: string | Uint8Array | FilePart<Uint8Array> | File
): void => {
if (v !== null) {
let r;
if (typeof v === "string") {
r = v;
} else if (v instanceof Uint8Array || Array.isArray(v)) {
r = Array.from(v);
} else if (v instanceof File) {
r = { file: v.name, mime: v.type, fileName: v.name };
} else if (typeof v.file === "string") {
r = { file: v.file, mime: v.mime, fileName: v.fileName };
} else {
r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName };
}
form[String(key)] = r;
}
};
if (data instanceof FormData) {
for (const [key, value] of data) {
append(key, value);
}
} else {
for (const [key, value] of Object.entries(data)) {
append(key, value);
}
}
return new Body("Form", form);
}
/**
* Creates a new JSON body.
* @example
* ```typescript
* import { Body } from "@tauri-apps/plugin-http"
* Body.json({
* registered: true,
* name: 'tauri'
* });
* ```
*
* @param data The body JSON object.
*
* @returns The body object ready to be used on the POST and PUT requests.
*
* @since 2.0.0
*/
static json<K extends string | number | symbol, V>(data: Record<K, V>): Body {
return new Body("Json", data);
}
/**
* Creates a new UTF-8 string body.
* @example
* ```typescript
* import { Body } from "@tauri-apps/plugin-http"
* Body.text('The body content as a string');
* ```
*
* @param value The body string.
*
* @returns The body object ready to be used on the POST and PUT requests.
*
* @since 2.0.0
*/
static text(value: string): Body {
return new Body("Text", value);
}
/**
* Creates a new byte array body.
* @example
* ```typescript
* import { Body } from "@tauri-apps/plugin-http"
* Body.bytes(new Uint8Array([1, 2, 3]));
* ```
*
* @param bytes The body byte array.
*
* @returns The body object ready to be used on the POST and PUT requests.
*
* @since 2.0.0
*/
static bytes(
bytes: Iterable<number> | ArrayLike<number> | ArrayBuffer
): Body {
// stringifying Uint8Array doesn't return an array of numbers, so we create one here
return new Body(
"Bytes",
Array.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes)
);
}
}
/** The request HTTP verb. */
type HttpVerb =
| "GET"
| "POST"
| "PUT"
| "DELETE"
| "PATCH"
| "HEAD"
| "OPTIONS"
| "CONNECT"
| "TRACE";
/**
* Options object sent to the backend.
*
* @since 2.0.0
*/
interface HttpOptions {
method: HttpVerb;
url: string;
headers?: Record<string, unknown>;
query?: Record<string, unknown>;
body?: Body;
timeout?: number | Duration;
responseType?: ResponseType;
}
/** Request options. */
type RequestOptions = Omit<HttpOptions, "method" | "url">;
/** Options for the `fetch` API. */
type FetchOptions = Omit<HttpOptions, "url">;
/** @ignore */
interface IResponse<T> {
url: string;
status: number;
headers: Record<string, string>;
rawHeaders: Record<string, string[]>;
data: T;
}
/**
* Response object.
*
* @since 2.0.0
* */
class Response<T> {
/** The request URL. */
url: string;
/** The response status code. */
status: number;
/** A boolean indicating whether the response was successful (status in the range 200299) or not. */
ok: boolean;
/** The response headers. */
headers: Record<string, string>;
/** The response raw headers. */
rawHeaders: Record<string, string[]>;
/** The response data. */
data: T;
/** @ignore */
constructor(response: IResponse<T>) {
this.url = response.url;
this.status = response.status;
this.ok = this.status >= 200 && this.status < 300;
this.headers = response.headers;
this.rawHeaders = response.rawHeaders;
this.data = response.data;
}
}
/**
* @since 2.0.0 * @since 2.0.0
*/ */
class Client { export async function fetch(
id: number; input: URL | Request | string,
/** @ignore */ init?: RequestInit & ClientOptions,
constructor(id: number) { ): Promise<Response> {
this.id = id; const maxRedirections = init?.maxRedirections;
} const connectTimeout = init?.maxRedirections;
/** // Remove these fields before creating the request
* Drops the client instance. if (init) {
* @example delete init.maxRedirections;
* ```typescript delete init.connectTimeout;
* import { getClient } from '@tauri-apps/plugin-http'; }
* const client = await getClient();
* await client.drop(); const req = new Request(input, init);
* ``` const buffer = await req.arrayBuffer();
*/ const reqData = buffer.byteLength ? Array.from(new Uint8Array(buffer)) : null;
async drop(): Promise<void> {
return window.__TAURI_INVOKE__("plugin:http|drop_client", { const rid = await window.__TAURI_INVOKE__<number>("plugin:http|fetch", {
client: this.id, cmd: "fetch",
method: req.method,
url: req.url,
headers: Array.from(req.headers.entries()),
data: reqData,
maxRedirections,
connectTimeout,
}); });
}
/** req.signal.addEventListener("abort", () => {
* Makes an HTTP request. window.__TAURI_INVOKE__("plugin:http|fetch_cancel", {
* @example rid,
* ```typescript
* import { getClient } from '@tauri-apps/plugin-http';
* const client = await getClient();
* const response = await client.request({
* method: 'GET',
* url: 'http://localhost:3003/users',
* });
* ```
*/
async request<T>(options: HttpOptions): Promise<Response<T>> {
const jsonResponse =
!options.responseType || options.responseType === ResponseType.JSON;
if (jsonResponse) {
options.responseType = ResponseType.Text;
}
return window
.__TAURI_INVOKE__<IResponse<T>>("plugin:http|request", {
clientId: this.id,
options,
})
.then((res) => {
const response = new Response(res);
if (jsonResponse) {
/* eslint-disable */
try {
response.data = JSON.parse(response.data as string);
} catch (e) {
if (response.ok && (response.data as unknown as string) === "") {
response.data = {} as T;
} else if (response.ok) {
throw Error(
`Failed to parse response \`${response.data}\` as JSON: ${e};
try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`
);
}
}
/* eslint-enable */
return response;
}
return response;
}); });
}
/**
* Makes a GET request.
* @example
* ```typescript
* import { getClient, ResponseType } from '@tauri-apps/plugin-http';
* const client = await getClient();
* const response = await client.get('http://localhost:3003/users', {
* timeout: 30,
* // the expected response type
* responseType: ResponseType.JSON
* });
* ```
*/
async get<T>(url: string, options?: RequestOptions): Promise<Response<T>> {
return this.request({
method: "GET",
url,
...options,
}); });
}
/** interface FetchSendResponse {
* Makes a POST request. status: number;
* @example statusText: string;
* ```typescript headers: [[string, string]];
* import { getClient, Body, ResponseType } from '@tauri-apps/plugin-http'; url: string;
* const client = await getClient();
* const response = await client.post('http://localhost:3003/users', {
* body: Body.json({
* name: 'tauri',
* password: 'awesome'
* }),
* // in this case the server returns a simple string
* responseType: ResponseType.Text,
* });
* ```
*/
async post<T>(
url: string,
body?: Body,
options?: RequestOptions
): Promise<Response<T>> {
return this.request({
method: "POST",
url,
body,
...options,
});
} }
/** const { status, statusText, url, headers } =
* Makes a PUT request. await window.__TAURI_INVOKE__<FetchSendResponse>("plugin:http|fetch_send", {
* @example rid,
* ```typescript
* import { getClient, Body } from '@tauri-apps/plugin-http';
* const client = await getClient();
* const response = await client.put('http://localhost:3003/users/1', {
* body: Body.form({
* file: {
* file: '/home/tauri/avatar.png',
* mime: 'image/png',
* fileName: 'avatar.png'
* }
* })
* });
* ```
*/
async put<T>(
url: string,
body?: Body,
options?: RequestOptions
): Promise<Response<T>> {
return this.request({
method: "PUT",
url,
body,
...options,
}); });
}
/** const body = await window.__TAURI_INVOKE__<number[]>(
* Makes a PATCH request. "plugin:http|fetch_read_body",
* @example {
* ```typescript rid,
* import { getClient, Body } from '@tauri-apps/plugin-http'; },
* const client = await getClient(); );
* const response = await client.patch('http://localhost:3003/users/1', {
* body: Body.json({ email: 'contact@tauri.app' })
* });
* ```
*/
async patch<T>(url: string, options?: RequestOptions): Promise<Response<T>> {
return this.request({
method: "PATCH",
url,
...options,
});
}
/** const res = new Response(Uint8Array.from(body), {
* Makes a DELETE request. headers,
* @example status,
* ```typescript statusText,
* import { getClient } from '@tauri-apps/plugin-http';
* const client = await getClient();
* const response = await client.delete('http://localhost:3003/users/1');
* ```
*/
async delete<T>(url: string, options?: RequestOptions): Promise<Response<T>> {
return this.request({
method: "DELETE",
url,
...options,
}); });
}
}
/** // url is read only but seems like we can do this
* Creates a new client using the specified options. Object.defineProperty(res, "url", { value: url });
* @example
* ```typescript
* import { getClient } from '@tauri-apps/plugin-http';
* const client = await getClient();
* ```
*
* @param options Client configuration.
*
* @returns A promise resolving to the client instance.
*
* @since 2.0.0
*/
async function getClient(options?: ClientOptions): Promise<Client> {
return window
.__TAURI_INVOKE__<number>("plugin:http|create_client", {
options,
})
.then((id) => new Client(id));
}
/** @internal */ return res;
let defaultClient: Client | null = null;
/**
* Perform an HTTP request using the default client.
* @example
* ```typescript
* import { fetch } from '@tauri-apps/plugin-http';
* const response = await fetch('http://localhost:3003/users/2', {
* method: 'GET',
* timeout: 30,
* });
* ```
*/
async function fetch<T>(
url: string,
options?: FetchOptions
): Promise<Response<T>> {
if (defaultClient === null) {
defaultClient = await getClient();
}
return defaultClient.request({
url,
method: options?.method ?? "GET",
...options,
});
} }
export type {
Duration,
ClientOptions,
Part,
HttpVerb,
HttpOptions,
RequestOptions,
FetchOptions,
};
export {
getClient,
fetch,
Body,
Client,
Response,
ResponseType,
type FilePart,
};

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-http", "name": "@tauri-apps/plugin-http",
"version": "2.0.0-alpha.0", "version": "2.0.0-alpha.1",
"license": "MIT or APACHE-2.0", "license": "MIT or APACHE-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@ -27,6 +27,6 @@
"tslib": "^2.5.0" "tslib": "^2.5.0"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.0.0-alpha.5" "@tauri-apps/api": "2.0.0-alpha.6"
} }
} }

@ -5,7 +5,7 @@ import { createConfig } from "../../shared/rollup.config.mjs";
export default createConfig({ export default createConfig({
input: "guest-js/index.ts", input: "guest-js/index.ts",
pkg: JSON.parse( pkg: JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8") readFileSync(new URL("./package.json", import.meta.url), "utf8"),
), ),
external: [/^@tauri-apps\/api/], external: [/^@tauri-apps\/api/],
}); });

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_HTTP__=function(e){"use strict";var t;e.ResponseType=void 0,(t=e.ResponseType||(e.ResponseType={}))[t.JSON=1]="JSON",t[t.Text=2]="Text",t[t.Binary=3]="Binary";class r{constructor(e,t){this.type=e,this.payload=t}static form(e){const t={},s=(e,r)=>{if(null!==r){let s;s="string"==typeof r?r:r instanceof Uint8Array||Array.isArray(r)?Array.from(r):r instanceof File?{file:r.name,mime:r.type,fileName:r.name}:"string"==typeof r.file?{file:r.file,mime:r.mime,fileName:r.fileName}:{file:Array.from(r.file),mime:r.mime,fileName:r.fileName},t[String(e)]=s}};if(e instanceof FormData)for(const[t,r]of e)s(t,r);else for(const[t,r]of Object.entries(e))s(t,r);return new r("Form",t)}static json(e){return new r("Json",e)}static text(e){return new r("Text",e)}static bytes(e){return new r("Bytes",Array.from(e instanceof ArrayBuffer?new Uint8Array(e):e))}}class s{constructor(e){this.url=e.url,this.status=e.status,this.ok=this.status>=200&&this.status<300,this.headers=e.headers,this.rawHeaders=e.rawHeaders,this.data=e.data}}class n{constructor(e){this.id=e}async drop(){return window.__TAURI_INVOKE__("plugin:http|drop_client",{client:this.id})}async request(t){const r=!t.responseType||t.responseType===e.ResponseType.JSON;return r&&(t.responseType=e.ResponseType.Text),window.__TAURI_INVOKE__("plugin:http|request",{clientId:this.id,options:t}).then((e=>{const t=new s(e);if(r){try{t.data=JSON.parse(t.data)}catch(e){if(t.ok&&""===t.data)t.data={};else if(t.ok)throw Error(`Failed to parse response \`${t.data}\` as JSON: ${e};\n try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`)}return t}return t}))}async get(e,t){return this.request({method:"GET",url:e,...t})}async post(e,t,r){return this.request({method:"POST",url:e,body:t,...r})}async put(e,t,r){return this.request({method:"PUT",url:e,body:t,...r})}async patch(e,t){return this.request({method:"PATCH",url:e,...t})}async delete(e,t){return this.request({method:"DELETE",url:e,...t})}}async function i(e){return window.__TAURI_INVOKE__("plugin:http|create_client",{options:e}).then((e=>new n(e)))}let o=null;return e.Body=r,e.Client=n,e.Response=s,e.fetch=async function(e,t){var r;return null===o&&(o=await i()),o.request({url:e,method:null!==(r=null==t?void 0:t.method)&&void 0!==r?r:"GET",...t})},e.getClient=i,e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})} if("__TAURI__"in window){var __TAURI_HTTP__=function(e){"use strict";return e.fetch=async function(e,t){const n=null==t?void 0:t.maxRedirections,r=null==t?void 0:t.maxRedirections;t&&(delete t.maxRedirections,delete t.connectTimeout);const _=new Request(e,t),i=await _.arrayBuffer(),a=i.byteLength?Array.from(new Uint8Array(i)):null,d=await window.__TAURI_INVOKE__("plugin:http|fetch",{cmd:"fetch",method:_.method,url:_.url,headers:Array.from(_.headers.entries()),data:a,maxRedirections:n,connectTimeout:r});_.signal.addEventListener("abort",(()=>{window.__TAURI_INVOKE__("plugin:http|fetch_cancel",{rid:d})}));const{status:o,statusText:s,url:c,headers:u}=await window.__TAURI_INVOKE__("plugin:http|fetch_send",{rid:d}),l=await window.__TAURI_INVOKE__("plugin:http|fetch_read_body",{rid:d}),w=new Response(Uint8Array.from(l),{headers:u,status:o,statusText:s});return Object.defineProperty(w,"url",{value:c}),w},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_HTTP__})}

@ -0,0 +1,177 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{collections::HashMap, time::Duration};
use http::{header, HeaderName, HeaderValue, Method, StatusCode};
use reqwest::redirect::Policy;
use serde::Serialize;
use tauri::{command, AppHandle, Runtime};
use crate::{Error, FetchRequest, HttpExt, RequestId};
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FetchResponse {
status: u16,
status_text: String,
headers: Vec<(String, String)>,
url: String,
}
#[command]
pub async fn fetch<R: Runtime>(
app: AppHandle<R>,
method: String,
url: url::Url,
headers: Vec<(String, String)>,
data: Option<Vec<u8>>,
connect_timeout: Option<u64>,
max_redirections: Option<usize>,
) -> crate::Result<RequestId> {
let scheme = url.scheme();
let method = Method::from_bytes(method.as_bytes())?;
let headers: HashMap<String, String> = HashMap::from_iter(headers);
match scheme {
"http" | "https" => {
if app.http().scope.is_allowed(&url) {
let mut builder = reqwest::ClientBuilder::new();
if let Some(timeout) = connect_timeout {
builder = builder.connect_timeout(Duration::from_millis(timeout));
}
if let Some(max_redirections) = max_redirections {
builder = builder.redirect(if max_redirections == 0 {
Policy::none()
} else {
Policy::limited(max_redirections)
});
}
let mut request = builder.build()?.request(method.clone(), url);
for (key, value) in &headers {
let name = HeaderName::from_bytes(key.as_bytes())?;
let v = HeaderValue::from_bytes(value.as_bytes())?;
if !matches!(name, header::HOST | header::CONTENT_LENGTH) {
request = request.header(name, v);
}
}
// POST and PUT requests should always have a 0 length content-length,
// if there is no body. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
if data.is_none() && matches!(method, Method::POST | Method::PUT) {
request = request.header(header::CONTENT_LENGTH, HeaderValue::from(0));
}
if headers.contains_key(header::RANGE.as_str()) {
// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch step 18
// If httpRequests header list contains `Range`, then append (`Accept-Encoding`, `identity`)
request = request.header(
header::ACCEPT_ENCODING,
HeaderValue::from_static("identity"),
);
}
if !headers.contains_key(header::USER_AGENT.as_str()) {
request = request.header(header::USER_AGENT, HeaderValue::from_static("tauri"));
}
if let Some(data) = data {
request = request.body(data);
}
let http_state = app.http();
let rid = http_state.next_id();
let fut = async move { Ok(request.send().await.map_err(Into::into)) };
let mut request_table = http_state.requests.lock().await;
request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
Ok(rid)
} else {
Err(Error::UrlNotAllowed(url))
}
}
"data" => {
let data_url =
data_url::DataUrl::process(url.as_str()).map_err(|_| Error::DataUrlError)?;
let (body, _) = data_url
.decode_to_vec()
.map_err(|_| Error::DataUrlDecodeError)?;
let response = http::Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, data_url.mime_type().to_string())
.body(reqwest::Body::from(body))?;
let http_state = app.http();
let rid = http_state.next_id();
let fut = async move { Ok(Ok(reqwest::Response::from(response))) };
let mut request_table = http_state.requests.lock().await;
request_table.insert(rid, FetchRequest::new(Box::pin(fut)));
Ok(rid)
}
_ => Err(Error::SchemeNotSupport(scheme.to_string())),
}
}
#[command]
pub async fn fetch_cancel<R: Runtime>(app: AppHandle<R>, rid: RequestId) -> crate::Result<()> {
let mut request_table = app.http().requests.lock().await;
let req = request_table
.get_mut(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
*req = FetchRequest::new(Box::pin(async { Err(Error::RequestCanceled) }));
Ok(())
}
#[command]
pub async fn fetch_send<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
) -> crate::Result<FetchResponse> {
let mut request_table = app.http().requests.lock().await;
let req = request_table
.remove(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
let res = match req.0.lock().await.as_mut().await {
Ok(Ok(res)) => res,
Ok(Err(e)) | Err(e) => return Err(e),
};
let status = res.status();
let url = res.url().to_string();
let mut headers = Vec::new();
for (key, val) in res.headers().iter() {
headers.push((
key.as_str().into(),
String::from_utf8(val.as_bytes().to_vec())?,
));
}
app.http().responses.lock().await.insert(rid, res);
Ok(FetchResponse {
status: status.as_u16(),
status_text: status.canonical_reason().unwrap_or_default().to_string(),
headers,
url,
})
}
#[command]
pub(crate) async fn fetch_read_body<R: Runtime>(
app: AppHandle<R>,
rid: RequestId,
) -> crate::Result<tauri::ipc::Response> {
let mut response_table = app.http().responses.lock().await;
let res = response_table
.remove(&rid)
.ok_or(Error::InvalidRequestId(rid))?;
Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec()))
}

@ -1,341 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{collections::HashMap, path::PathBuf, time::Duration};
use reqwest::{header, Method, Url};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
#[derive(Deserialize)]
#[serde(untagged)]
enum SerdeDuration {
Seconds(u64),
Duration(Duration),
}
fn deserialize_duration<'de, D: Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Option<Duration>, D::Error> {
if let Some(duration) = Option::<SerdeDuration>::deserialize(deserializer)? {
Ok(Some(match duration {
SerdeDuration::Seconds(s) => Duration::from_secs(s),
SerdeDuration::Duration(d) => d,
}))
} else {
Ok(None)
}
}
/// The builder of [`Client`].
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientBuilder {
/// Max number of redirections to follow.
pub max_redirections: Option<usize>,
/// Connect timeout for the request.
#[serde(deserialize_with = "deserialize_duration", default)]
pub connect_timeout: Option<Duration>,
}
impl ClientBuilder {
/// Builds the Client.
pub fn build(self) -> crate::Result<Client> {
let mut client_builder = reqwest::Client::builder();
if let Some(max_redirections) = self.max_redirections {
client_builder = client_builder.redirect(if max_redirections == 0 {
reqwest::redirect::Policy::none()
} else {
reqwest::redirect::Policy::limited(max_redirections)
});
}
if let Some(connect_timeout) = self.connect_timeout {
client_builder = client_builder.connect_timeout(connect_timeout);
}
let client = client_builder.build()?;
Ok(Client(client))
}
}
/// The HTTP client based on [`reqwest`].
#[derive(Debug, Clone)]
pub struct Client(reqwest::Client);
impl Client {
/// Executes an HTTP request
///
/// # Examples
pub async fn send(&self, mut request: HttpRequestBuilder) -> crate::Result<Response> {
let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
let mut request_builder = self.0.request(method, request.url.as_str());
if let Some(query) = request.query {
request_builder = request_builder.query(&query);
}
if let Some(timeout) = request.timeout {
request_builder = request_builder.timeout(timeout);
}
if let Some(body) = request.body {
request_builder = match body {
Body::Bytes(data) => request_builder.body(bytes::Bytes::from(data)),
Body::Text(text) => request_builder.body(bytes::Bytes::from(text)),
Body::Json(json) => request_builder.json(&json),
Body::Form(form_body) => {
#[allow(unused_variables)]
fn send_form(
request_builder: reqwest::RequestBuilder,
headers: &mut Option<HeaderMap>,
form_body: FormBody,
) -> crate::Result<reqwest::RequestBuilder> {
#[cfg(feature = "multipart")]
if matches!(
headers
.as_ref()
.and_then(|h| h.0.get("content-type"))
.map(|v| v.as_bytes()),
Some(b"multipart/form-data")
) {
// the Content-Type header will be set by reqwest in the `.multipart` call
headers.as_mut().map(|h| h.0.remove("content-type"));
let mut multipart = reqwest::multipart::Form::new();
for (name, part) in form_body.0 {
let part = match part {
FormPart::File {
file,
mime,
file_name,
} => {
let bytes: Vec<u8> = file.try_into()?;
let mut part = reqwest::multipart::Part::bytes(bytes);
if let Some(mime) = mime {
part = part.mime_str(&mime)?;
}
if let Some(file_name) = file_name {
part = part.file_name(file_name);
}
part
}
FormPart::Text(value) => reqwest::multipart::Part::text(value),
};
multipart = multipart.part(name, part);
}
return Ok(request_builder.multipart(multipart));
}
let mut form = Vec::new();
for (name, part) in form_body.0 {
match part {
FormPart::File { file, .. } => {
let bytes: Vec<u8> = file.try_into()?;
form.push((name, serde_json::to_string(&bytes)?))
}
FormPart::Text(value) => form.push((name, value)),
}
}
Ok(request_builder.form(&form))
}
send_form(request_builder, &mut request.headers, form_body)?
}
};
}
if let Some(headers) = request.headers {
request_builder = request_builder.headers(headers.0);
}
let http_request = request_builder.build()?;
let response = self.0.execute(http_request).await?;
Ok(Response(
request.response_type.unwrap_or(ResponseType::Json),
response,
))
}
}
#[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
#[repr(u16)]
#[non_exhaustive]
/// The HTTP response type.
pub enum ResponseType {
/// Read the response as JSON
Json = 1,
/// Read the response as text
Text,
/// Read the response as binary
Binary,
}
#[derive(Debug)]
pub struct Response(ResponseType, reqwest::Response);
impl Response {
/// Reads the response.
///
/// Note that the body is serialized to a [`Value`].
pub async fn read(self) -> crate::Result<ResponseData> {
let url = self.1.url().clone();
let mut headers = HashMap::new();
let mut raw_headers = HashMap::new();
for (name, value) in self.1.headers() {
headers.insert(
name.as_str().to_string(),
String::from_utf8(value.as_bytes().to_vec())?,
);
raw_headers.insert(
name.as_str().to_string(),
self.1
.headers()
.get_all(name)
.into_iter()
.map(|v| String::from_utf8(v.as_bytes().to_vec()).map_err(Into::into))
.collect::<crate::Result<Vec<String>>>()?,
);
}
let status = self.1.status().as_u16();
let data = match self.0 {
ResponseType::Json => self.1.json().await?,
ResponseType::Text => Value::String(self.1.text().await?),
ResponseType::Binary => serde_json::to_value(&self.1.bytes().await?)?,
};
Ok(ResponseData {
url,
status,
headers,
raw_headers,
data,
})
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ResponseData {
/// Response URL. Useful if it followed redirects.
pub url: Url,
/// Response status code.
pub status: u16,
/// Response headers.
pub headers: HashMap<String, String>,
/// Response raw headers.
pub raw_headers: HashMap<String, Vec<String>>,
/// Response data.
pub data: Value,
}
/// A file path or contents.
#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum FilePart {
/// File path.
Path(PathBuf),
/// File contents.
Contents(Vec<u8>),
}
impl TryFrom<FilePart> for Vec<u8> {
type Error = crate::Error;
fn try_from(file: FilePart) -> crate::Result<Self> {
let bytes = match file {
FilePart::Path(path) => std::fs::read(path)?,
FilePart::Contents(bytes) => bytes,
};
Ok(bytes)
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum FormPart {
/// A string value.
Text(String),
/// A file value.
#[serde(rename_all = "camelCase")]
File {
/// File path or content.
file: FilePart,
/// Mime type of this part.
/// Only used when the `Content-Type` header is set to `multipart/form-data`.
mime: Option<String>,
/// File name.
/// Only used when the `Content-Type` header is set to `multipart/form-data`.
file_name: Option<String>,
},
}
#[derive(Debug, Deserialize)]
pub struct FormBody(pub(crate) HashMap<String, FormPart>);
#[derive(Debug, Deserialize)]
#[serde(tag = "type", content = "payload")]
#[non_exhaustive]
pub enum Body {
Form(FormBody),
Json(Value),
Text(String),
Bytes(Vec<u8>),
}
#[derive(Debug, Default)]
pub struct HeaderMap(header::HeaderMap);
impl<'de> Deserialize<'de> for HeaderMap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let map = HashMap::<String, String>::deserialize(deserializer)?;
let mut headers = header::HeaderMap::default();
for (key, value) in map {
if let (Ok(key), Ok(value)) = (
header::HeaderName::from_bytes(key.as_bytes()),
header::HeaderValue::from_str(&value),
) {
headers.insert(key, value);
} else {
return Err(serde::de::Error::custom(format!(
"invalid header `{key}` `{value}`"
)));
}
}
Ok(Self(headers))
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HttpRequestBuilder {
/// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
pub method: String,
/// The request URL
pub url: Url,
/// The request query params
pub query: Option<HashMap<String, String>>,
/// The request headers
pub headers: Option<HeaderMap>,
/// The request body
pub body: Option<Body>,
/// Timeout for the whole request
#[serde(deserialize_with = "deserialize_duration", default)]
pub timeout: Option<Duration>,
/// The response type (defaults to Json)
pub response_type: Option<ResponseType>,
}

@ -1,78 +0,0 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use tauri::{path::SafePathBuf, AppHandle, Runtime, State};
use tauri_plugin_fs::FsExt;
use crate::{ClientId, Http};
mod client;
use client::{Body, ClientBuilder, FilePart, FormPart, HttpRequestBuilder, ResponseData};
pub use client::Client;
#[tauri::command]
pub async fn create_client<R: Runtime>(
_app: AppHandle<R>,
http: State<'_, Http<R>>,
options: Option<ClientBuilder>,
) -> super::Result<ClientId> {
let client = options.unwrap_or_default().build()?;
let mut store = http.clients.lock().unwrap();
let id = rand::random::<ClientId>();
store.insert(id, client);
Ok(id)
}
#[tauri::command]
pub async fn drop_client<R: Runtime>(
_app: AppHandle<R>,
http: State<'_, Http<R>>,
client: ClientId,
) -> super::Result<()> {
let mut store = http.clients.lock().unwrap();
store.remove(&client);
Ok(())
}
#[tauri::command]
pub async fn request<R: Runtime>(
app: AppHandle<R>,
http: State<'_, Http<R>>,
client_id: ClientId,
options: Box<HttpRequestBuilder>,
) -> super::Result<ResponseData> {
if http.scope.is_allowed(&options.url) {
let client = http
.clients
.lock()
.unwrap()
.get(&client_id)
.ok_or_else(|| crate::Error::HttpClientNotInitialized)?
.clone();
let options = *options;
if let Some(Body::Form(form)) = &options.body {
for value in form.0.values() {
if let FormPart::File {
file: FilePart::Path(path),
..
} = value
{
if SafePathBuf::new(path.clone()).is_err()
|| !app
.try_fs_scope()
.map(|s| s.is_allowed(path))
.unwrap_or_default()
{
return Err(crate::Error::PathNotAllowed(path.clone()));
}
}
}
}
let response = client.send(options).await?;
Ok(response.read().await?)
} else {
Err(crate::Error::UrlNotAllowed(options.url))
}
}

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use reqwest::Url;
use serde::Deserialize; use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -15,9 +14,9 @@ pub struct Config {
/// The scoped URL is matched against the request URL using a glob pattern. /// The scoped URL is matched against the request URL using a glob pattern.
/// ///
/// Examples: /// Examples:
/// - "https://**": allows all HTTPS urls /// - "https://*" or "https://**" : allows all HTTPS urls
/// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path /// - "https://*.github.com/tauri-apps/tauri": allows any subdomain of "github.com" with the "tauri-apps/api" path
/// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/" /// - "https://myapi.service.com/users/*": allows access to any URLs that begins with "https://myapi.service.com/users/"
#[allow(rustdoc::bare_urls)] #[allow(rustdoc::bare_urls)]
#[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize)]
pub struct HttpAllowlistScope(pub Vec<Url>); pub struct HttpAllowlistScope(pub Vec<String>);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save