Merge branch 'v2' into v2

pull/2454/head
Charles Schaefer 1 month ago committed by GitHub
commit dd85c60e9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,8 @@
---
"updater": minor
"updater-js": minor
---
Add allowDowngrades parameter to check command
Added a new optional `allowDowngrades` parameter to the JavaScript check command that allows the updater to consider versions that are lower than the current version as valid updates. When enabled, the version comparator will accept any version that is different from the current version, effectively allowing downgrades.

@ -0,0 +1,6 @@
---
autostart: minor
autostart-js: minor
---
Added a new builder method app_name() to allow customizing the application name in the autostart entry.

@ -46,7 +46,7 @@ jobs:
node-version: 'lts/*' node-version: 'lts/*'
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- name: audit - name: audit
run: pnpm audit run: pnpm audit

@ -167,7 +167,7 @@ jobs:
node-version: 'lts/*' node-version: 'lts/*'
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- name: build api - name: build api

@ -39,7 +39,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- name: cargo login - name: cargo login
@ -75,7 +75,7 @@ jobs:
- name: Create Pull Request With Versions Bumped - name: Create Pull Request With Versions Bumped
id: cpr id: cpr
uses: tauri-apps/create-pull-request@v3 uses: peter-evans/create-pull-request@dd2324fc52d5d43c699a5636bcf19fceaa70c284 # 7.0.7
if: steps.covector.outputs.commandRan == 'version' if: steps.covector.outputs.commandRan == 'version'
with: with:
title: 'Publish New Versions (${{ github.ref_name }})' title: 'Publish New Versions (${{ github.ref_name }})'

@ -38,7 +38,7 @@ jobs:
node-version: 'lts/*' node-version: 'lts/*'
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- run: pnpm format:check - run: pnpm format:check

@ -49,7 +49,7 @@ jobs:
node-version: 'lts/*' node-version: 'lts/*'
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- name: eslint - name: eslint
run: pnpm lint run: pnpm lint

@ -38,7 +38,7 @@ jobs:
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v4
with: with:
version: 9.x.x version: 10.x.x
run_install: true run_install: true
- name: Build packages - name: Build packages

2
.gitignore vendored

@ -14,6 +14,8 @@ target/
# .vscode workspace settings file # .vscode workspace settings file
.vscode/settings.json .vscode/settings.json
.vscode/launch.json
.vscode/tasks.json
# npm, yarn and bun lock files # npm, yarn and bun lock files
package-lock.json package-lock.json

2489
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,5 +1,71 @@
# Changelog # Changelog
## \[2.0.22]
### Dependencies
- Upgraded to `fs-js@2.3.0`
- Upgraded to `global-shortcut-js@2.2.1`
- Upgraded to `http-js@2.4.4`
- Upgraded to `opener-js@2.2.7`
- Upgraded to `dialog-js@2.2.2`
## \[2.0.21]
### Dependencies
- Upgraded to `log-js@2.4.0`
- Upgraded to `biometric-js@2.2.1`
- Upgraded to `updater-js@2.7.1`
## \[2.0.20]
### Dependencies
- Upgraded to `http-js@2.4.3`
- Upgraded to `shell-js@2.2.1`
- Upgraded to `fs-js@2.2.1`
- Upgraded to `process-js@2.2.1`
- Upgraded to `updater-js@2.7.0`
- Upgraded to `dialog-js@2.2.1`
## \[2.0.19]
### Dependencies
- Upgraded to `http-js@2.4.2`
- Upgraded to `updater-js@2.6.1`
## \[2.0.18]
### Dependencies
- Upgraded to `http-js@2.4.1`
## \[2.0.17]
### Dependencies
- Upgraded to `log-js@2.3.1`
## \[2.0.16]
### Dependencies
- Upgraded to `clipboard-manager-js@2.2.2`
- Upgraded to `notification-js@2.2.2`
- Upgraded to `os-js@2.2.1`
- Upgraded to `http-js@2.4.0`
- Upgraded to `log-js@2.3.0`
- Upgraded to `updater-js@2.6.0`
## \[2.0.15]
### Dependencies
- Upgraded to `log-js@2.2.3`
- Upgraded to `opener-js@2.2.6`
## \[2.0.14] ## \[2.0.14]
### Dependencies ### Dependencies

@ -1,7 +1,7 @@
{ {
"name": "api", "name": "api",
"private": true, "private": true,
"version": "2.0.14", "version": "2.0.22",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --clearScreen false", "dev": "vite --clearScreen false",
@ -10,35 +10,35 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.2.0", "@tauri-apps/api": "2.5.0",
"@tauri-apps/plugin-barcode-scanner": "^2.2.0", "@tauri-apps/plugin-barcode-scanner": "^2.2.0",
"@tauri-apps/plugin-biometric": "^2.2.0", "@tauri-apps/plugin-biometric": "^2.2.1",
"@tauri-apps/plugin-cli": "^2.2.0", "@tauri-apps/plugin-cli": "^2.2.0",
"@tauri-apps/plugin-clipboard-manager": "^2.2.1", "@tauri-apps/plugin-clipboard-manager": "^2.2.2",
"@tauri-apps/plugin-dialog": "^2.2.0", "@tauri-apps/plugin-dialog": "^2.2.2",
"@tauri-apps/plugin-fs": "^2.2.0", "@tauri-apps/plugin-fs": "^2.3.0",
"@tauri-apps/plugin-geolocation": "^2.2.0", "@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.2.0", "@tauri-apps/plugin-global-shortcut": "^2.2.1",
"@tauri-apps/plugin-opener": "^2.2.5",
"@tauri-apps/plugin-haptics": "^2.2.0", "@tauri-apps/plugin-haptics": "^2.2.0",
"@tauri-apps/plugin-http": "^2.3.0", "@tauri-apps/plugin-http": "^2.4.4",
"@tauri-apps/plugin-nfc": "^2.2.0", "@tauri-apps/plugin-nfc": "^2.2.0",
"@tauri-apps/plugin-notification": "^2.2.1", "@tauri-apps/plugin-notification": "^2.2.2",
"@tauri-apps/plugin-os": "^2.2.0", "@tauri-apps/plugin-opener": "^2.2.7",
"@tauri-apps/plugin-process": "^2.2.0", "@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-shell": "^2.2.0", "@tauri-apps/plugin-process": "^2.2.1",
"@tauri-apps/plugin-shell": "^2.2.1",
"@tauri-apps/plugin-store": "^2.2.0", "@tauri-apps/plugin-store": "^2.2.0",
"@tauri-apps/plugin-updater": "^2.5.1", "@tauri-apps/plugin-updater": "^2.7.1",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
}, },
"devDependencies": { "devDependencies": {
"@iconify-json/codicon": "^1.2.6", "@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.1", "@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^5.0.1", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tauri-apps/cli": "2.2.7", "@tauri-apps/cli": "2.5.0",
"@unocss/extractor-svelte": "^66.0.0", "@unocss/extractor-svelte": "^66.0.0",
"svelte": "^5.10.0", "svelte": "^5.20.4",
"unocss": "^66.0.0", "unocss": "^66.0.0",
"vite": "^6.0.3" "vite": "^6.2.6"
} }
} }

@ -1,5 +1,73 @@
# Changelog # Changelog
## \[2.0.26]
### Dependencies
- Upgraded to `fs@2.3.0`
- Upgraded to `global-shortcut@2.2.1`
- Upgraded to `http@2.4.4`
- Upgraded to `opener@2.2.7`
- Upgraded to `dialog@2.2.2`
## \[2.0.25]
### Dependencies
- Upgraded to `log@2.4.0`
- Upgraded to `biometric@2.2.1`
- Upgraded to `updater@2.7.1`
## \[2.0.24]
### Dependencies
- Upgraded to `http@2.4.3`
- Upgraded to `shell@2.2.1`
- Upgraded to `fs@2.2.1`
- Upgraded to `process@2.2.1`
- Upgraded to `updater@2.7.0`
- Upgraded to `dialog@2.2.1`
## \[2.0.23]
### Dependencies
- Upgraded to `http@2.4.2`
- Upgraded to `updater@2.6.1`
## \[2.0.22]
### Dependencies
- Upgraded to `http@2.4.1`
## \[2.0.21]
### Dependencies
- Upgraded to `log@2.3.1`
## \[2.0.20]
### Dependencies
- Upgraded to `clipboard-manager@2.2.2`
- Upgraded to `geolocation@2.2.4`
- Upgraded to `haptics@2.2.4`
- Upgraded to `notification@2.2.2`
- Upgraded to `os@2.2.1`
- Upgraded to `http@2.4.0`
- Upgraded to `log@2.3.0`
- Upgraded to `updater@2.6.0`
## \[2.0.19]
### Dependencies
- Upgraded to `log@2.2.3`
- Upgraded to `opener@2.2.6`
## \[2.0.18] ## \[2.0.18]
### Dependencies ### Dependencies

@ -1,7 +1,7 @@
[package] [package]
name = "api" name = "api"
publish = false publish = false
version = "2.0.18" version = "2.0.26"
description = "An example Tauri Application showcasing the api" description = "An example Tauri Application showcasing the api"
edition = "2021" edition = "2021"
rust-version = { workspace = true } rust-version = { workspace = true }
@ -18,23 +18,25 @@ tauri-build = { workspace = true, features = ["codegen", "isolation"] }
serde_json = { workspace = true } serde_json = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
tiny_http = "0.12" tiny_http = "0.12"
time = "0.3"
log = { workspace = true } log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.2.2" } tauri-plugin-log = { path = "../../../plugins/log", version = "2.4.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.2.0", features = [ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.3.0", features = [
"watch", "watch",
] } ] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.2.1" } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.2.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.2.0" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.2.2" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart", "multipart",
], version = "2.3.0" } "cookies",
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.2.1", features = [ ], version = "2.4.4" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.2.2", features = [
"windows7-compat", "windows7-compat",
] } ] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.2.0" } tauri-plugin-os = { path = "../../../plugins/os", version = "2.2.1" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.2.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.2.1" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.2.5" } tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.2.7" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.2.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.2.1" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.2.0" } tauri-plugin-store = { path = "../../../plugins/store", version = "2.2.0" }
[dependencies.tauri] [dependencies.tauri]
@ -52,16 +54,16 @@ features = [
[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.2.0" } tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.2.0" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.2.0" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.2.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.5.1" } tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.7.1" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" } tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
[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.2.0" } tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.2.0" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.2.0" } tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.2.0" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.2.0" } tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.2.1" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.2.3" } tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.2.4" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.2.3" } tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.2.4" }
[features] [features]
prod = ["tauri/custom-protocol"] prod = ["tauri/custom-protocol"]

@ -53,7 +53,7 @@
} }
] ]
}, },
"shell:allow-open", "shell:default",
"shell:allow-kill", "shell:allow-kill",
"shell:allow-stdin-write", "shell:allow-stdin-write",
"process:allow-exit", "process:allow-exit",
@ -69,6 +69,7 @@
"fs:allow-mkdir", "fs:allow-mkdir",
"fs:allow-remove", "fs:allow-remove",
"fs:allow-write-text-file", "fs:allow-write-text-file",
"fs:read-meta",
"fs:scope-download-recursive", "fs:scope-download-recursive",
"fs:scope-resource-recursive", "fs:scope-resource-recursive",
{ {

@ -102,9 +102,28 @@ pub fn run() {
if let Ok(mut request) = server.recv() { if let Ok(mut request) = server.recv() {
let mut body = Vec::new(); let mut body = Vec::new();
let _ = request.as_reader().read_to_end(&mut body); let _ = request.as_reader().read_to_end(&mut body);
let mut headers = request.headers().to_vec();
if !headers.iter().any(|header| header.field == tiny_http::HeaderField::from_bytes(b"Cookie").unwrap()) {
let expires = time::OffsetDateTime::now_utc() + time::Duration::days(1);
// RFC 1123 format
let format = time::macros::format_description!(
"[weekday repr:short], [day] [month repr:short] [year] [hour]:[minute]:[second] GMT"
);
let expires_str = expires.format(format).unwrap();
headers.push(
tiny_http::Header::from_bytes(
&b"Set-Cookie"[..],
format!("session-token=test-value; Secure; Path=/; Expires={expires_str}")
.as_bytes(),
)
.unwrap(),
);
}
let response = tiny_http::Response::new( let response = tiny_http::Response::new(
tiny_http::StatusCode(200), tiny_http::StatusCode(200),
request.headers().to_vec(), headers,
std::io::Cursor::new(body), std::io::Cursor::new(body),
request.body_length(), request.body_length(),
None, None,

@ -2,16 +2,18 @@
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { convertFileSrc } from "@tauri-apps/api/core"; import { convertFileSrc } from "@tauri-apps/api/core";
import { arrayBufferToBase64 } from "../lib/utils"; import { arrayBufferToBase64 } from "../lib/utils";
import { onDestroy } from "svelte";
export let onMessage; export let onMessage;
export let insecureRenderHtml; export let insecureRenderHtml;
let path = ""; let path = "";
let img; let img;
/** @type {fs.FileHandle} */
let file; let file;
let renameTo; let renameTo;
let watchPath = ""; let watchPath = "";
let watchDebounceDelay = 0; let watchDebounceDelay = "0";
let watchRecursive = false; let watchRecursive = false;
let unwatchFn; let unwatchFn;
let unwatchPath = ""; let unwatchPath = "";
@ -118,7 +120,7 @@
.getElementById("file-save") .getElementById("file-save")
.addEventListener("click", function () { .addEventListener("click", function () {
fs.writeTextFile(path, fileInput.value, { fs.writeTextFile(path, fileInput.value, {
dir: getDir(), baseDir: getDir(),
}).catch(onMessage); }).catch(onMessage);
}); });
}); });
@ -170,6 +172,15 @@
unwatchFn = undefined; unwatchFn = undefined;
unwatchPath = undefined; unwatchPath = undefined;
} }
onDestroy(() => {
if (file) {
file.close();
}
if (unwatchFn) {
unwatchFn();
}
})
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col">

@ -12,10 +12,14 @@
isChecking = true isChecking = true
try { try {
const update = await check() const update = await check()
if (update) {
onMessage(`Should update: ${update.available}`) onMessage(`Should update: ${update.available}`)
onMessage(update) onMessage(update)
newUpdate = update newUpdate = update
} else {
onMessage('No update available')
}
} catch (e) { } catch (e) {
onMessage(e) onMessage(e)
} finally { } finally {

@ -11,25 +11,29 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev" "example:api:dev": "pnpm run --filter \"api\" tauri dev"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.21.0", "@eslint/js": "9.28.0",
"@rollup/plugin-node-resolve": "16.0.0", "@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4", "@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "11.1.6", "@rollup/plugin-typescript": "12.1.2",
"covector": "^0.12.3", "covector": "^0.12.4",
"eslint": "9.21.0", "eslint": "9.28.0",
"eslint-config-prettier": "10.0.1", "eslint-config-prettier": "10.1.5",
"eslint-plugin-security": "3.0.1", "eslint-plugin-security": "3.0.1",
"prettier": "3.5.2", "prettier": "3.5.3",
"rollup": "4.34.8", "rollup": "4.41.1",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.7.3", "typescript": "5.8.3",
"typescript-eslint": "8.25.0" "typescript-eslint": "8.33.1"
}, },
"resolutions": { "pnpm": {
"semver": ">=7.5.2", "overrides": {
"optionator": ">=0.9.3" "esbuild@<0.25.0": ">=0.25.0"
},
"onlyBuiltDependencies": [
"esbuild"
]
}, },
"engines": { "engines": {
"pnpm": "^9.0.0" "pnpm": "^10.0.0"
} }
} }

@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.3.0]
- [`8ecb418a`](https://github.com/tauri-apps/plugins-workspace/commit/8ecb418a1a35d7f234dc5d833746ac2d8e062aec) ([#2569](https://github.com/tauri-apps/plugins-workspace/pull/2569)) Add a `Builder` for more flexible settings
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-autostart" name = "tauri-plugin-autostart"
version = "2.2.0" version = "2.3.0"
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 }

@ -57,11 +57,12 @@ First you need to register the core plugin with Tauri:
`src-tauri/src/lib.rs` `src-tauri/src/lib.rs`
```rust ```rust
use tauri_plugin_autostart::MacosLauncher;
fn main() { fn main() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_autostart::init(MacosLauncher::LaunchAgent, Some(vec!["--flag1", "--flag2"]) /* arbitrary number of args to pass to your app */)) .plugin(tauri_plugin_autostart::Builder::new()
.args(["--flag1", "--flag2"])
.app_name("My Custom Name")
.build())
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-autostart", "name": "@tauri-apps/plugin-autostart",
"version": "2.2.0", "version": "2.3.0",
"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"

@ -11,6 +11,8 @@ disable the automatic start on boot.
#### This default permission set includes the following:
- `allow-enable` - `allow-enable`
- `allow-disable` - `allow-disable`
- `allow-is-enabled` - `allow-is-enabled`

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,37 +297,44 @@
{ {
"description": "Enables the disable command without any pre-configured scope.", "description": "Enables the disable command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-disable" "const": "allow-disable",
"markdownDescription": "Enables the disable command without any pre-configured scope."
}, },
{ {
"description": "Denies the disable command without any pre-configured scope.", "description": "Denies the disable command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-disable" "const": "deny-disable",
"markdownDescription": "Denies the disable command without any pre-configured scope."
}, },
{ {
"description": "Enables the enable command without any pre-configured scope.", "description": "Enables the enable command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-enable" "const": "allow-enable",
"markdownDescription": "Enables the enable command without any pre-configured scope."
}, },
{ {
"description": "Denies the enable command without any pre-configured scope.", "description": "Denies the enable command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-enable" "const": "deny-enable",
"markdownDescription": "Denies the enable command without any pre-configured scope."
}, },
{ {
"description": "Enables the is_enabled command without any pre-configured scope.", "description": "Enables the is_enabled command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-is-enabled" "const": "allow-is-enabled",
"markdownDescription": "Enables the is_enabled command without any pre-configured scope."
}, },
{ {
"description": "Denies the is_enabled command without any pre-configured scope.", "description": "Denies the is_enabled command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-is-enabled" "const": "deny-is-enabled",
"markdownDescription": "Denies the is_enabled command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n", "description": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "This permission set configures if your\napplication can enable or disable auto\nstarting the application on boot.\n\n#### Granted Permissions\n\nIt allows all to check, enable and\ndisable the automatic start on boot.\n\n\n#### This default permission set includes:\n\n- `allow-enable`\n- `allow-disable`\n- `allow-is-enabled`"
} }
] ]
} }

@ -14,7 +14,7 @@ use auto_launch::{AutoLaunch, AutoLaunchBuilder};
use serde::{ser::Serializer, Serialize}; use serde::{ser::Serializer, Serialize};
use tauri::{ use tauri::{
command, command,
plugin::{Builder, TauriPlugin}, plugin::{Builder as PluginBuilder, TauriPlugin},
Manager, Runtime, State, Manager, Runtime, State,
}; };
@ -22,8 +22,9 @@ use std::env::current_exe;
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Default, Copy, Clone)]
pub enum MacosLauncher { pub enum MacosLauncher {
#[default]
LaunchAgent, LaunchAgent,
AppleScript, AppleScript,
} }
@ -71,10 +72,12 @@ impl AutoLaunchManager {
} }
pub trait ManagerExt<R: Runtime> { pub trait ManagerExt<R: Runtime> {
/// TODO: Rename these to `autostart` or `auto_start` in v3
fn autolaunch(&self) -> State<'_, AutoLaunchManager>; fn autolaunch(&self) -> State<'_, AutoLaunchManager>;
} }
impl<R: Runtime, T: Manager<R>> ManagerExt<R> for T { impl<R: Runtime, T: Manager<R>> ManagerExt<R> for T {
/// TODO: Rename these to `autostart` or `auto_start` in v3
fn autolaunch(&self) -> State<'_, AutoLaunchManager> { fn autolaunch(&self) -> State<'_, AutoLaunchManager> {
self.state::<AutoLaunchManager>() self.state::<AutoLaunchManager>()
} }
@ -95,29 +98,102 @@ async fn is_enabled(manager: State<'_, AutoLaunchManager>) -> Result<bool> {
manager.is_enabled() manager.is_enabled()
} }
/// Initializes the plugin. #[derive(Default)]
/// pub struct Builder {
/// `args` - are passed to your app on startup. #[cfg(target_os = "macos")]
pub fn init<R: Runtime>(
macos_launcher: MacosLauncher, macos_launcher: MacosLauncher,
args: Option<Vec<&'static str>>, args: Vec<String>,
) -> TauriPlugin<R> { app_name: Option<String>,
Builder::new("autostart") }
impl Builder {
/// Create a new auto start builder with default settings
pub fn new() -> Self {
Self::default()
}
/// Adds an argument to pass to your app on startup.
///
/// ## Examples
///
/// ```no_run
/// Builder::new()
/// .arg("--from-autostart")
/// .arg("--hey")
/// .build();
/// ```
pub fn arg<S: Into<String>>(mut self, arg: S) -> Self {
self.args.push(arg.into());
self
}
/// Adds multiple arguments to pass to your app on startup.
///
/// ## Examples
///
/// ```no_run
/// Builder::new()
/// .args(["--from-autostart", "--hey"])
/// .build();
/// ```
pub fn args<I, S>(mut self, args: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
for arg in args {
self = self.arg(arg);
}
self
}
/// Sets whether to use launch agent or apple script to be used to enable auto start,
/// the builder's default is [`MacosLauncher::LaunchAgent`]
#[cfg(target_os = "macos")]
pub fn macos_launcher(mut self, macos_launcher: MacosLauncher) -> Self {
self.macos_launcher = macos_launcher;
self
}
/// Sets the app name to be used for the auto start entry.
///
/// ## Examples
///
/// ```no_run
/// Builder::new()
/// .app_name("My Custom Name"))
/// .build();
/// ```
pub fn app_name<S: Into<String>>(mut self, app_name: S) -> Self {
self.app_name = Some(app_name.into());
self
}
pub fn build<R: Runtime>(self) -> TauriPlugin<R> {
PluginBuilder::new("autostart")
.invoke_handler(tauri::generate_handler![enable, disable, is_enabled]) .invoke_handler(tauri::generate_handler![enable, disable, is_enabled])
.setup(move |app, _api| { .setup(move |app, _api| {
let mut builder = AutoLaunchBuilder::new(); let mut builder = AutoLaunchBuilder::new();
builder.set_app_name(&app.package_info().name);
if let Some(args) = args { let app_name = self
builder.set_args(&args); .app_name
} .as_ref()
builder.set_use_launch_agent(matches!(macos_launcher, MacosLauncher::LaunchAgent)); .unwrap_or_else(|| &app.package_info().name);
builder.set_app_name(app_name);
builder.set_args(&self.args);
let current_exe = current_exe()?; let current_exe = current_exe()?;
#[cfg(windows)] #[cfg(windows)]
builder.set_app_path(&current_exe.display().to_string()); builder.set_app_path(&current_exe.display().to_string());
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
builder.set_use_launch_agent(matches!(
self.macos_launcher,
MacosLauncher::LaunchAgent
));
// on macOS, current_exe gives path to /Applications/Example.app/MacOS/Example // on macOS, current_exe gives path to /Applications/Example.app/MacOS/Example
// but this results in seeing a Unix Executable in macOS login items // but this results in seeing a Unix Executable in macOS login items
// It must be: /Applications/Example.app // It must be: /Applications/Example.app
@ -125,14 +201,16 @@ pub fn init<R: Runtime>(
// exe path to not break it. // exe path to not break it.
let exe_path = current_exe.canonicalize()?.display().to_string(); let exe_path = current_exe.canonicalize()?.display().to_string();
let parts: Vec<&str> = exe_path.split(".app/").collect(); let parts: Vec<&str> = exe_path.split(".app/").collect();
let app_path = let app_path = if parts.len() == 2
if parts.len() == 2 && matches!(macos_launcher, MacosLauncher::AppleScript) { && matches!(self.macos_launcher, MacosLauncher::AppleScript)
{
format!("{}.app", parts.first().unwrap()) format!("{}.app", parts.first().unwrap())
} else { } else {
exe_path exe_path
}; };
builder.set_app_path(&app_path); builder.set_app_path(&app_path);
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if let Some(appimage) = app if let Some(appimage) = app
.env() .env()
@ -151,3 +229,22 @@ pub fn init<R: Runtime>(
}) })
.build() .build()
} }
}
/// Initializes the plugin.
///
/// `args` - are passed to your app on startup.
pub fn init<R: Runtime>(
#[allow(unused)] macos_launcher: MacosLauncher,
args: Option<Vec<&'static str>>,
) -> TauriPlugin<R> {
let mut builder = Builder::new();
if let Some(args) = args {
builder = builder.args(args)
}
#[cfg(target_os = "macos")]
{
builder = builder.macos_launcher(macos_launcher);
}
builder.build()
}

@ -9,6 +9,8 @@ It allows all barcode related features.
#### This default permission set includes the following:
- `allow-cancel` - `allow-cancel`
- `allow-check-permissions` - `allow-check-permissions`
- `allow-open-app-settings` - `allow-open-app-settings`

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,67 +297,80 @@
{ {
"description": "Enables the cancel command without any pre-configured scope.", "description": "Enables the cancel command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-cancel" "const": "allow-cancel",
"markdownDescription": "Enables the cancel command without any pre-configured scope."
}, },
{ {
"description": "Denies the cancel command without any pre-configured scope.", "description": "Denies the cancel command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-cancel" "const": "deny-cancel",
"markdownDescription": "Denies the cancel command without any pre-configured scope."
}, },
{ {
"description": "Enables the check_permissions command without any pre-configured scope.", "description": "Enables the check_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-check-permissions" "const": "allow-check-permissions",
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
}, },
{ {
"description": "Denies the check_permissions command without any pre-configured scope.", "description": "Denies the check_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-check-permissions" "const": "deny-check-permissions",
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
}, },
{ {
"description": "Enables the open_app_settings command without any pre-configured scope.", "description": "Enables the open_app_settings command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-open-app-settings" "const": "allow-open-app-settings",
"markdownDescription": "Enables the open_app_settings command without any pre-configured scope."
}, },
{ {
"description": "Denies the open_app_settings command without any pre-configured scope.", "description": "Denies the open_app_settings command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-open-app-settings" "const": "deny-open-app-settings",
"markdownDescription": "Denies the open_app_settings command without any pre-configured scope."
}, },
{ {
"description": "Enables the request_permissions command without any pre-configured scope.", "description": "Enables the request_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-request-permissions" "const": "allow-request-permissions",
"markdownDescription": "Enables the request_permissions command without any pre-configured scope."
}, },
{ {
"description": "Denies the request_permissions command without any pre-configured scope.", "description": "Denies the request_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-request-permissions" "const": "deny-request-permissions",
"markdownDescription": "Denies the request_permissions command without any pre-configured scope."
}, },
{ {
"description": "Enables the scan command without any pre-configured scope.", "description": "Enables the scan command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-scan" "const": "allow-scan",
"markdownDescription": "Enables the scan command without any pre-configured scope."
}, },
{ {
"description": "Denies the scan command without any pre-configured scope.", "description": "Denies the scan command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-scan" "const": "deny-scan",
"markdownDescription": "Denies the scan command without any pre-configured scope."
}, },
{ {
"description": "Enables the vibrate command without any pre-configured scope.", "description": "Enables the vibrate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-vibrate" "const": "allow-vibrate",
"markdownDescription": "Enables the vibrate command without any pre-configured scope."
}, },
{ {
"description": "Denies the vibrate command without any pre-configured scope.", "description": "Denies the vibrate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-vibrate" "const": "deny-vibrate",
"markdownDescription": "Denies the vibrate command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures which\nbarcode scanning features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all barcode related features.\n\n", "description": "This permission set configures which\nbarcode scanning features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all barcode related features.\n\n\n#### This default permission set includes:\n\n- `allow-cancel`\n- `allow-check-permissions`\n- `allow-open-app-settings`\n- `allow-request-permissions`\n- `allow-scan`\n- `allow-vibrate`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "This permission set configures which\nbarcode scanning features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all barcode related features.\n\n\n#### This default permission set includes:\n\n- `allow-cancel`\n- `allow-check-permissions`\n- `allow-open-app-settings`\n- `allow-request-permissions`\n- `allow-scan`\n- `allow-vibrate`"
} }
] ]
} }

@ -1,5 +1,11 @@
# Changelog # Changelog
## \[2.2.1]
### bug
- [`10f9e66e`](https://github.com/tauri-apps/plugins-workspace/commit/10f9e66e32141dd35f4bf884fbf9102691187e92) ([#2633](https://github.com/tauri-apps/plugins-workspace/pull/2633) by [@pjf-dev](https://github.com/tauri-apps/plugins-workspace/../../pjf-dev)) Fix biometric plugin ignoring fallback logic when biometry status is unavailable or not enrolled on iOS.
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-biometric" name = "tauri-plugin-biometric"
version = "2.2.0" version = "2.2.1"
description = "Prompt the user for biometric authentication on Android and iOS." description = "Prompt the user for biometric authentication on Android and iOS."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }

@ -98,7 +98,12 @@ class BiometricPlugin: Plugin {
} }
@objc func authenticate(_ invoke: Invoke) throws { @objc func authenticate(_ invoke: Invoke) throws {
guard self.status.available else { let args = try invoke.parseArgs(AuthOptions.self)
let allowDeviceCredential = args.allowDeviceCredential ?? false
guard self.status.available || allowDeviceCredential else {
// Biometry unavailable, fallback disabled
invoke.reject( invoke.reject(
self.status.errorReason ?? "", self.status.errorReason ?? "",
code: self.status.errorCode ?? "" code: self.status.errorCode ?? ""
@ -106,15 +111,11 @@ class BiometricPlugin: Plugin {
return return
} }
let args = try invoke.parseArgs(AuthOptions.self)
let context = LAContext() let context = LAContext()
context.localizedFallbackTitle = args.fallbackTitle context.localizedFallbackTitle = args.fallbackTitle
context.localizedCancelTitle = args.cancelTitle context.localizedCancelTitle = args.cancelTitle
context.touchIDAuthenticationAllowableReuseDuration = 0 context.touchIDAuthenticationAllowableReuseDuration = 0
let allowDeviceCredential = args.allowDeviceCredential ?? false
// force system default fallback title if an empty string is provided (the OS hides the fallback button in this case) // force system default fallback title if an empty string is provided (the OS hides the fallback button in this case)
if allowDeviceCredential, if allowDeviceCredential,
let fallbackTitle = context.localizedFallbackTitle, let fallbackTitle = context.localizedFallbackTitle,

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-biometric", "name": "@tauri-apps/plugin-biometric",
"version": "2.2.0", "version": "2.2.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"

@ -9,6 +9,8 @@ It allows acccess to all biometric commands.
#### This default permission set includes the following:
- `allow-authenticate` - `allow-authenticate`
- `allow-status` - `allow-status`

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,12 +297,14 @@
{ {
"description": "Enables the authenticate command without any pre-configured scope.", "description": "Enables the authenticate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-authenticate" "const": "allow-authenticate",
"markdownDescription": "Enables the authenticate command without any pre-configured scope."
}, },
{ {
"description": "Denies the authenticate command without any pre-configured scope.", "description": "Denies the authenticate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-authenticate" "const": "deny-authenticate",
"markdownDescription": "Denies the authenticate command without any pre-configured scope."
}, },
{ {
"description": "Enables the biometric_cipher command without any pre-configured scope.", "description": "Enables the biometric_cipher command without any pre-configured scope.",
@ -317,17 +319,20 @@
{ {
"description": "Enables the status command without any pre-configured scope.", "description": "Enables the status command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-status" "const": "allow-status",
"markdownDescription": "Enables the status command without any pre-configured scope."
}, },
{ {
"description": "Denies the status command without any pre-configured scope.", "description": "Denies the status command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-status" "const": "deny-status",
"markdownDescription": "Denies the status command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures which\nbiometric features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all biometric commands.\n\n", "description": "This permission set configures which\nbiometric features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all biometric commands.\n\n\n#### This default permission set includes:\n\n- `allow-authenticate`\n- `allow-status`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "This permission set configures which\nbiometric features are by default exposed.\n\n#### Granted Permissions\n\nIt allows acccess to all biometric commands.\n\n\n#### This default permission set includes:\n\n- `allow-authenticate`\n- `allow-status`"
} }
] ]
} }

@ -2,6 +2,8 @@
Allows reading the CLI matches Allows reading the CLI matches
#### This default permission set includes the following:
- `allow-cli-matches` - `allow-cli-matches`
## Permission Table ## Permission Table

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,17 +297,20 @@
{ {
"description": "Enables the cli_matches command without any pre-configured scope.", "description": "Enables the cli_matches command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-cli-matches" "const": "allow-cli-matches",
"markdownDescription": "Enables the cli_matches command without any pre-configured scope."
}, },
{ {
"description": "Denies the cli_matches command without any pre-configured scope.", "description": "Denies the cli_matches command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-cli-matches" "const": "deny-cli-matches",
"markdownDescription": "Denies the cli_matches command without any pre-configured scope."
}, },
{ {
"description": "Allows reading the CLI matches", "description": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "Allows reading the CLI matches\n#### This default permission set includes:\n\n- `allow-cli-matches`"
} }
] ]
} }

@ -1,5 +1,11 @@
# Changelog # Changelog
## \[2.2.2]
### bug
- [`d37bbdef`](https://github.com/tauri-apps/plugins-workspace/commit/d37bbdef8dc70e61e59f9fe0bb8b2a48999d0aa1) ([#2507](https://github.com/tauri-apps/plugins-workspace/pull/2507) by [@SquitchYT](https://github.com/tauri-apps/plugins-workspace/../../SquitchYT)) Fix clipboard-manager Wayland support.
## \[2.2.1] ## \[2.2.1]
- [`ce11079f`](https://github.com/tauri-apps/plugins-workspace/commit/ce11079f19852fbefdecf0e4c7d947af3624fee0) ([#2280](https://github.com/tauri-apps/plugins-workspace/pull/2280) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Explicitly drop `arboard::Clipboard` on exit. Add recommendation to not use read methods on the mainthread. - [`ce11079f`](https://github.com/tauri-apps/plugins-workspace/commit/ce11079f19852fbefdecf0e4c7d947af3624fee0) ([#2280](https://github.com/tauri-apps/plugins-workspace/pull/2280) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Explicitly drop `arboard::Clipboard` on exit. Add recommendation to not use read methods on the mainthread.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-clipboard-manager" name = "tauri-plugin-clipboard-manager"
version = "2.2.1" version = "2.2.2"
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 }
@ -36,4 +36,4 @@ thiserror = { workspace = true }
tauri = { workspace = true, features = ["wry"] } tauri = { workspace = true, features = ["wry"] }
[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]
arboard = "3" arboard = { version = "3", features = ["wayland-data-control"] }

@ -91,7 +91,7 @@ async function writeImage(
* import { readImage } from '@tauri-apps/plugin-clipboard-manager'; * import { readImage } from '@tauri-apps/plugin-clipboard-manager';
* *
* const clipboardImage = await readImage(); * const clipboardImage = await readImage();
* const blob = new Blob([await clipboardImage.rbga()], { type: 'image' }) * const blob = new Blob([await clipboardImage.rgba()], { type: 'image' })
* const url = URL.createObjectURL(blob) * const url = URL.createObjectURL(blob)
* ``` * ```
* @since 2.0.0 * @since 2.0.0

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-clipboard-manager", "name": "@tauri-apps/plugin-clipboard-manager",
"version": "2.2.1", "version": "2.2.2",
"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"

@ -7,6 +7,8 @@ application specific if read and/or write access is needed.
Clipboard interaction needs to be explicitly enabled. Clipboard interaction needs to be explicitly enabled.
#### This default permission set includes the following:
## Permission Table ## Permission Table

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,67 +297,80 @@
{ {
"description": "Enables the clear command without any pre-configured scope.", "description": "Enables the clear command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-clear" "const": "allow-clear",
"markdownDescription": "Enables the clear command without any pre-configured scope."
}, },
{ {
"description": "Denies the clear command without any pre-configured scope.", "description": "Denies the clear command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-clear" "const": "deny-clear",
"markdownDescription": "Denies the clear command without any pre-configured scope."
}, },
{ {
"description": "Enables the read_image command without any pre-configured scope.", "description": "Enables the read_image command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-read-image" "const": "allow-read-image",
"markdownDescription": "Enables the read_image command without any pre-configured scope."
}, },
{ {
"description": "Denies the read_image command without any pre-configured scope.", "description": "Denies the read_image command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-read-image" "const": "deny-read-image",
"markdownDescription": "Denies the read_image command without any pre-configured scope."
}, },
{ {
"description": "Enables the read_text command without any pre-configured scope.", "description": "Enables the read_text command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-read-text" "const": "allow-read-text",
"markdownDescription": "Enables the read_text command without any pre-configured scope."
}, },
{ {
"description": "Denies the read_text command without any pre-configured scope.", "description": "Denies the read_text command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-read-text" "const": "deny-read-text",
"markdownDescription": "Denies the read_text command without any pre-configured scope."
}, },
{ {
"description": "Enables the write_html command without any pre-configured scope.", "description": "Enables the write_html command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-write-html" "const": "allow-write-html",
"markdownDescription": "Enables the write_html command without any pre-configured scope."
}, },
{ {
"description": "Denies the write_html command without any pre-configured scope.", "description": "Denies the write_html command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-write-html" "const": "deny-write-html",
"markdownDescription": "Denies the write_html command without any pre-configured scope."
}, },
{ {
"description": "Enables the write_image command without any pre-configured scope.", "description": "Enables the write_image command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-write-image" "const": "allow-write-image",
"markdownDescription": "Enables the write_image command without any pre-configured scope."
}, },
{ {
"description": "Denies the write_image command without any pre-configured scope.", "description": "Denies the write_image command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-write-image" "const": "deny-write-image",
"markdownDescription": "Denies the write_image command without any pre-configured scope."
}, },
{ {
"description": "Enables the write_text command without any pre-configured scope.", "description": "Enables the write_text command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-write-text" "const": "allow-write-text",
"markdownDescription": "Enables the write_text command without any pre-configured scope."
}, },
{ {
"description": "Denies the write_text command without any pre-configured scope.", "description": "Denies the write_text command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-write-text" "const": "deny-write-text",
"markdownDescription": "Denies the write_text command without any pre-configured scope."
}, },
{ {
"description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n", "description": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "No features are enabled by default, as we believe\nthe clipboard can be inherently dangerous and it is \napplication specific if read and/or write access is needed.\n\nClipboard interaction needs to be explicitly enabled.\n"
} }
] ]
} }

@ -1,5 +1,17 @@
# Changelog # Changelog
## \[2.3.0]
- [`4d10acee`](https://github.com/tauri-apps/plugins-workspace/commit/4d10acee61bad8045705508121424ed5f2d381f6) ([#993](https://github.com/tauri-apps/plugins-workspace/pull/993) by [@m00nwtchr](https://github.com/tauri-apps/plugins-workspace/../../m00nwtchr)) Exposed Android's `path`, `pathPattern` and `pathSuffix` configurations.
- [`4d10acee`](https://github.com/tauri-apps/plugins-workspace/commit/4d10acee61bad8045705508121424ed5f2d381f6) ([#993](https://github.com/tauri-apps/plugins-workspace/pull/993) by [@m00nwtchr](https://github.com/tauri-apps/plugins-workspace/../../m00nwtchr)) Added a `scheme` configuration to set a scheme other than http/https. This is only supported on Android and will still default to http,https if not set.
## \[2.2.1]
### bug
- [`38deef43`](https://github.com/tauri-apps/plugins-workspace/commit/38deef43dca9d5a09a38ed2da45b0f86c6afa1c5) ([#2483](https://github.com/tauri-apps/plugins-workspace/pull/2483)) Fix `is_registered` not being able to pickup deep link registered in `HKEY_LOCAL_MACHINE` on Windows
- [`38deef43`](https://github.com/tauri-apps/plugins-workspace/commit/38deef43dca9d5a09a38ed2da45b0f86c6afa1c5) ([#2483](https://github.com/tauri-apps/plugins-workspace/pull/2483)) Fix `unregister` not being able to remove deep link registered in `HKEY_LOCAL_MACHINE` on Windows
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.2.0" version = "2.3.0"
description = "Set your Tauri application as the default handler for an URL" description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }

@ -9,25 +9,50 @@ use config::{AssociatedDomain, Config};
const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registered"]; const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registered"];
// TODO: Consider using activity-alias in case users may have multiple activities in their app. // TODO: Consider using activity-alias in case users may have multiple activities in their app.
// TODO: Do we want to support the other path* configs too?
fn intent_filter(domain: &AssociatedDomain) -> String { fn intent_filter(domain: &AssociatedDomain) -> String {
format!( format!(
r#"<intent-filter android:autoVerify="true"> r#"<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" /> {}
<data android:scheme="https" />
<data android:host="{}" /> <data android:host="{}" />
{} {}
{}
{}
{}
</intent-filter>"#, </intent-filter>"#,
domain
.scheme
.iter()
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
.collect::<Vec<_>>()
.join("\n "),
domain.host, domain.host,
domain
.path
.iter()
.map(|path| format!(r#"<data android:path="{path}" />"#))
.collect::<Vec<_>>()
.join("\n "),
domain
.path_pattern
.iter()
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
.collect::<Vec<_>>()
.join("\n "),
domain domain
.path_prefix .path_prefix
.iter() .iter()
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#)) .map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n ") .join("\n "),
domain
.path_suffix
.iter()
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
.collect::<Vec<_>>()
.join("\n "),
) )
} }

@ -1,5 +1,17 @@
# Changelog # Changelog
## \[2.2.2]
### Dependencies
- Upgraded to `deep-link-js@2.3.0`
## \[2.2.1]
### Dependencies
- Upgraded to `deep-link-js@2.2.1`
## \[2.2.0] ## \[2.2.0]
### Dependencies ### Dependencies

@ -1,7 +1,7 @@
{ {
"name": "deep-link-example", "name": "deep-link-example",
"private": true, "private": true,
"version": "2.2.0", "version": "2.2.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -10,12 +10,12 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.2.0", "@tauri-apps/api": "2.5.0",
"@tauri-apps/plugin-deep-link": "2.2.0" "@tauri-apps/plugin-deep-link": "2.3.0"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.2.7", "@tauri-apps/cli": "2.5.0",
"typescript": "^5.2.2", "typescript": "^5.7.3",
"vite": "^6.0.0" "vite": "^6.2.6"
} }
} }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-deep-link", "name": "@tauri-apps/plugin-deep-link",
"version": "2.2.0", "version": "2.3.0",
"description": "Set your Tauri application as the default handler for an URL", "description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [

@ -2,6 +2,8 @@
Allows reading the opened deep link via the get_current command Allows reading the opened deep link via the get_current command
#### This default permission set includes the following:
- `allow-get-current` - `allow-get-current`
## Permission Table ## Permission Table

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,47 +297,56 @@
{ {
"description": "Enables the get_current command without any pre-configured scope.", "description": "Enables the get_current command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-get-current" "const": "allow-get-current",
"markdownDescription": "Enables the get_current command without any pre-configured scope."
}, },
{ {
"description": "Denies the get_current command without any pre-configured scope.", "description": "Denies the get_current command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-get-current" "const": "deny-get-current",
"markdownDescription": "Denies the get_current command without any pre-configured scope."
}, },
{ {
"description": "Enables the is_registered command without any pre-configured scope.", "description": "Enables the is_registered command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-is-registered" "const": "allow-is-registered",
"markdownDescription": "Enables the is_registered command without any pre-configured scope."
}, },
{ {
"description": "Denies the is_registered command without any pre-configured scope.", "description": "Denies the is_registered command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-is-registered" "const": "deny-is-registered",
"markdownDescription": "Denies the is_registered command without any pre-configured scope."
}, },
{ {
"description": "Enables the register command without any pre-configured scope.", "description": "Enables the register command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-register" "const": "allow-register",
"markdownDescription": "Enables the register command without any pre-configured scope."
}, },
{ {
"description": "Denies the register command without any pre-configured scope.", "description": "Denies the register command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-register" "const": "deny-register",
"markdownDescription": "Denies the register command without any pre-configured scope."
}, },
{ {
"description": "Enables the unregister command without any pre-configured scope.", "description": "Enables the unregister command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-unregister" "const": "allow-unregister",
"markdownDescription": "Enables the unregister command without any pre-configured scope."
}, },
{ {
"description": "Denies the unregister command without any pre-configured scope.", "description": "Denies the unregister command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-unregister" "const": "deny-unregister",
"markdownDescription": "Denies the unregister command without any pre-configured scope."
}, },
{ {
"description": "Allows reading the opened deep link via the get_current command", "description": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "Allows reading the opened deep link via the get_current command\n#### This default permission set includes:\n\n- `allow-get-current`"
} }
] ]
} }

@ -9,10 +9,25 @@ use tauri_utils::config::DeepLinkProtocol;
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
pub struct AssociatedDomain { pub struct AssociatedDomain {
#[serde(default = "default_schemes")]
pub scheme: Vec<String>,
#[serde(deserialize_with = "deserialize_associated_host")] #[serde(deserialize_with = "deserialize_associated_host")]
pub host: String, pub host: String,
#[serde(default)]
pub path: Vec<String>,
#[serde(default, alias = "path-pattern", rename = "pathPattern")]
pub path_pattern: Vec<String>,
#[serde(default, alias = "path-prefix", rename = "pathPrefix")] #[serde(default, alias = "path-prefix", rename = "pathPrefix")]
pub path_prefix: Vec<String>, pub path_prefix: Vec<String>,
#[serde(default, alias = "path-suffix", rename = "pathSuffix")]
pub path_suffix: Vec<String>,
}
// TODO: Consider removing this in v3
fn default_schemes() -> Vec<String> {
vec!["https".to_string(), "http".to_string()]
} }
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<String, D::Error> fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<String, D::Error>

@ -172,7 +172,7 @@ mod imp {
use tauri::Manager; use tauri::Manager;
use tauri::{AppHandle, Runtime}; use tauri::{AppHandle, Runtime};
#[cfg(windows)] #[cfg(windows)]
use windows_registry::CURRENT_USER; use windows_registry::{CLASSES_ROOT, CURRENT_USER, LOCAL_MACHINE};
/// Access to the deep-link APIs. /// Access to the deep-link APIs.
pub struct DeepLink<R: Runtime> { pub struct DeepLink<R: Runtime> {
@ -258,25 +258,23 @@ mod imp {
pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> { pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
#[cfg(windows)] #[cfg(windows)]
{ {
let key_base = format!("Software\\Classes\\{}", _protocol.as_ref()); let protocol = _protocol.as_ref();
let key_base = format!("Software\\Classes\\{protocol}");
let exe = dunce::simplified(&tauri::utils::platform::current_exe()?) let exe = dunce::simplified(&tauri::utils::platform::current_exe()?)
.display() .display()
.to_string(); .to_string();
let key_reg = CURRENT_USER.create(&key_base)?; let key_reg = CURRENT_USER.create(&key_base)?;
key_reg.set_string( key_reg.set_string("", format!("URL:{} protocol", self.app.config().identifier))?;
"",
&format!("URL:{} protocol", self.app.config().identifier),
)?;
key_reg.set_string("URL Protocol", "")?; key_reg.set_string("URL Protocol", "")?;
let icon_reg = CURRENT_USER.create(format!("{key_base}\\DefaultIcon"))?; let icon_reg = CURRENT_USER.create(format!("{key_base}\\DefaultIcon"))?;
icon_reg.set_string("", &format!("{exe},0"))?; icon_reg.set_string("", format!("{exe},0"))?;
let cmd_reg = CURRENT_USER.create(format!("{key_base}\\shell\\open\\command"))?; let cmd_reg = CURRENT_USER.create(format!("{key_base}\\shell\\open\\command"))?;
cmd_reg.set_string("", &format!("\"{exe}\" \"%1\""))?; cmd_reg.set_string("", format!("\"{exe}\" \"%1\""))?;
Ok(()) Ok(())
} }
@ -351,13 +349,21 @@ mod imp {
/// ///
/// ## Platform-specific: /// ## Platform-specific:
/// ///
/// - **Windows**: Requires admin rights if the protocol is registered on local machine
/// (this can happen when registered from the NSIS installer when the install mode is set to both or per machine)
/// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros. /// - **Linux**: Can only unregister the scheme if it was initially registered with [`register`](`Self::register`). May not work on older distros.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn unregister<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> { pub fn unregister<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
#[cfg(windows)] #[cfg(windows)]
{ {
CURRENT_USER.remove_tree(format!("Software\\Classes\\{}", _protocol.as_ref()))?; let protocol = _protocol.as_ref();
let path = format!("Software\\Classes\\{protocol}");
if LOCAL_MACHINE.open(&path).is_ok() {
LOCAL_MACHINE.remove_tree(&path)?;
}
if CURRENT_USER.open(&path).is_ok() {
CURRENT_USER.remove_tree(&path)?;
}
Ok(()) Ok(())
} }
@ -401,10 +407,11 @@ mod imp {
pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> { pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> {
#[cfg(windows)] #[cfg(windows)]
{ {
let cmd_reg = CURRENT_USER.open(format!( let protocol = _protocol.as_ref();
"Software\\Classes\\{}\\shell\\open\\command", let Ok(cmd_reg) = CLASSES_ROOT.open(format!("{protocol}\\shell\\open\\command"))
_protocol.as_ref() else {
))?; return Ok(false);
};
let registered_cmd = cmd_reg.get_string("")?; let registered_cmd = cmd_reg.get_string("")?;

@ -1,5 +1,17 @@
# Changelog # Changelog
## \[2.2.2]
### Dependencies
- Upgraded to `fs-js@2.3.0`
## \[2.2.1]
### Dependencies
- Upgraded to `fs-js@2.2.1`
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.2.0" version = "2.2.2"
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 }
@ -34,7 +34,7 @@ tauri = { workspace = true }
log = { workspace = true } log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.2.0" } tauri-plugin-fs = { path = "../fs", version = "2.3.0" }
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] } tauri = { workspace = true, features = ["wry"] }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-dialog", "name": "@tauri-apps/plugin-dialog",
"version": "2.2.0", "version": "2.2.2",
"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"

@ -10,6 +10,8 @@ All dialog types are enabled.
#### This default permission set includes the following:
- `allow-ask` - `allow-ask`
- `allow-confirm` - `allow-confirm`
- `allow-message` - `allow-message`

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,57 +297,68 @@
{ {
"description": "Enables the ask command without any pre-configured scope.", "description": "Enables the ask command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-ask" "const": "allow-ask",
"markdownDescription": "Enables the ask command without any pre-configured scope."
}, },
{ {
"description": "Denies the ask command without any pre-configured scope.", "description": "Denies the ask command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-ask" "const": "deny-ask",
"markdownDescription": "Denies the ask command without any pre-configured scope."
}, },
{ {
"description": "Enables the confirm command without any pre-configured scope.", "description": "Enables the confirm command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-confirm" "const": "allow-confirm",
"markdownDescription": "Enables the confirm command without any pre-configured scope."
}, },
{ {
"description": "Denies the confirm command without any pre-configured scope.", "description": "Denies the confirm command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-confirm" "const": "deny-confirm",
"markdownDescription": "Denies the confirm command without any pre-configured scope."
}, },
{ {
"description": "Enables the message command without any pre-configured scope.", "description": "Enables the message command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-message" "const": "allow-message",
"markdownDescription": "Enables the message command without any pre-configured scope."
}, },
{ {
"description": "Denies the message command without any pre-configured scope.", "description": "Denies the message command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-message" "const": "deny-message",
"markdownDescription": "Denies the message command without any pre-configured scope."
}, },
{ {
"description": "Enables the open command without any pre-configured scope.", "description": "Enables the open command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-open" "const": "allow-open",
"markdownDescription": "Enables the open command without any pre-configured scope."
}, },
{ {
"description": "Denies the open command without any pre-configured scope.", "description": "Denies the open command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-open" "const": "deny-open",
"markdownDescription": "Denies the open command without any pre-configured scope."
}, },
{ {
"description": "Enables the save command without any pre-configured scope.", "description": "Enables the save command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-save" "const": "allow-save",
"markdownDescription": "Enables the save command without any pre-configured scope."
}, },
{ {
"description": "Denies the save command without any pre-configured scope.", "description": "Denies the save command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-save" "const": "deny-save",
"markdownDescription": "Denies the save command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n", "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
} }
] ]
} }

@ -1,5 +1,15 @@
# Changelog # Changelog
## \[2.3.0]
- [`dac4d537`](https://github.com/tauri-apps/plugins-workspace/commit/dac4d53724bb3430a00a3f0119857cba32a031e8) ([#2613](https://github.com/tauri-apps/plugins-workspace/pull/2613) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Reduce the overhead of `watch` and `unwatch`
## \[2.2.1]
### bug
- [`831c35ff`](https://github.com/tauri-apps/plugins-workspace/commit/831c35ff3940e841fe4418bb4cb104038b03304b) ([#2550](https://github.com/tauri-apps/plugins-workspace/pull/2550)) Fix `writeFile` ReadableStream handling due to missing async iterator support on macOS platform
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.2.0" version = "2.3.0"
description = "Access the file system." description = "Access the file system."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@ -14,7 +14,7 @@ rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support] [package.metadata.platforms.support]
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write acces in `$RESOURCES` folder" } windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" }
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" } linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
macos = { level = "full", notes = "No write access to `$RESOURCES` folder" } macos = { level = "full", notes = "No write access to `$RESOURCES` folder" }
android = { level = "partial", notes = "Access is restricted to Application folder by default" } android = { level = "partial", notes = "Access is restricted to Application folder by default" }
@ -35,7 +35,6 @@ tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
anyhow = "1" anyhow = "1"
uuid = { version = "1", features = ["v4"] }
glob = { workspace = true } glob = { workspace = true }
# TODO: Remove `serialization-compat-6` in v3 # TODO: Remove `serialization-compat-6` in v3
notify = { version = "8", optional = true, features = [ notify = { version = "8", optional = true, features = [

@ -68,9 +68,9 @@ fn main() {
Afterwards all the plugin's APIs are available through the JavaScript guest bindings: Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
```javascript ```javascript
import { metadata } from '@tauri-apps/plugin-fs' import { stat } from '@tauri-apps/plugin-fs'
await metadata('/path/to/file') await stat('/path/to/file')
``` ```
## Contributing ## Contributing

File diff suppressed because one or more lines are too long

@ -101,6 +101,7 @@ const COMMANDS: &[(&str, &[&str])] = &[
("fstat", &[]), ("fstat", &[]),
("exists", &[]), ("exists", &[]),
("watch", &[]), ("watch", &[]),
// TODO: Remove this in v3
("unwatch", &[]), ("unwatch", &[]),
("size", &[]), ("size", &[]),
]; ];

@ -1075,10 +1075,18 @@ async function writeFile(
if (data instanceof ReadableStream) { if (data instanceof ReadableStream) {
const file = await open(path, options) const file = await open(path, options)
for await (const chunk of data) { const reader = data.getReader()
await file.write(chunk)
try {
while (true) {
const { done, value } = await reader.read()
if (done) break
await file.write(value)
} }
} finally {
reader.releaseLock()
await file.close() await file.close()
}
} else { } else {
await invoke('plugin:fs|write_file', data, { await invoke('plugin:fs|write_file', data, {
headers: { headers: {
@ -1237,31 +1245,19 @@ type WatchEventKindRemove =
| { kind: 'folder' } | { kind: 'folder' }
| { kind: 'other' } | { kind: 'other' }
// TODO: Remove this in v3, return `Watcher` instead
/** /**
* @since 2.0.0 * @since 2.0.0
*/ */
type UnwatchFn = () => void type UnwatchFn = () => void
async function unwatch(rid: number): Promise<void> { class Watcher extends Resource {}
await invoke('plugin:fs|unwatch', { rid })
}
/** async function watchInternal(
* Watch changes (after a delay) on files or directories.
*
* @since 2.0.0
*/
async function watch(
paths: string | string[] | URL | URL[], paths: string | string[] | URL | URL[],
cb: (event: WatchEvent) => void, cb: (event: WatchEvent) => void,
options?: DebouncedWatchOptions options: DebouncedWatchOptions
): Promise<UnwatchFn> { ): Promise<UnwatchFn> {
const opts = {
recursive: false,
delayMs: 2000,
...options
}
const watchPaths = Array.isArray(paths) ? paths : [paths] const watchPaths = Array.isArray(paths) ? paths : [paths]
for (const path of watchPaths) { for (const path of watchPaths) {
@ -1275,15 +1271,35 @@ async function watch(
const rid: number = await invoke('plugin:fs|watch', { const rid: number = await invoke('plugin:fs|watch', {
paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)), paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)),
options: opts, options,
onEvent onEvent
}) })
const watcher = new Watcher(rid)
return () => { return () => {
void unwatch(rid) void watcher.close()
} }
} }
// TODO: Return `Watcher` instead in v3
/**
* Watch changes (after a delay) on files or directories.
*
* @since 2.0.0
*/
async function watch(
paths: string | string[] | URL | URL[],
cb: (event: WatchEvent) => void,
options?: DebouncedWatchOptions
): Promise<UnwatchFn> {
return await watchInternal(paths, cb, {
delayMs: 2000,
...options
})
}
// TODO: Return `Watcher` instead in v3
/** /**
* Watch changes on files or directories. * Watch changes on files or directories.
* *
@ -1294,32 +1310,10 @@ async function watchImmediate(
cb: (event: WatchEvent) => void, cb: (event: WatchEvent) => void,
options?: WatchOptions options?: WatchOptions
): Promise<UnwatchFn> { ): Promise<UnwatchFn> {
const opts = { return await watchInternal(paths, cb, {
recursive: false,
...options, ...options,
delayMs: null delayMs: undefined
}
const watchPaths = Array.isArray(paths) ? paths : [paths]
for (const path of watchPaths) {
if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.')
}
}
const onEvent = new Channel<WatchEvent>()
onEvent.onmessage = cb
const rid: number = await invoke('plugin:fs|watch', {
paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)),
options: opts,
onEvent
}) })
return () => {
void unwatch(rid)
}
} }
/** /**

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-fs", "name": "@tauri-apps/plugin-fs",
"version": "2.2.0", "version": "2.3.0",
"description": "Access the file system.", "description": "Access the file system.",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [

@ -24,8 +24,8 @@ This default permission set prevents access to critical components
of the Tauri application by default. of the Tauri application by default.
On Windows the webview data folder access is denied. On Windows the webview data folder access is denied.
#### Included permissions within this default permission set:
#### This default permission set includes the following:
- `create-app-specific-dirs` - `create-app-specific-dirs`
- `read-app-specific-dirs-recursive` - `read-app-specific-dirs-recursive`

@ -25,8 +25,6 @@ the `mkdir` command.
This default permission set prevents access to critical components This default permission set prevents access to critical components
of the Tauri application by default. of the Tauri application by default.
On Windows the webview data folder access is denied. On Windows the webview data folder access is denied.
#### Included permissions within this default permission set:
""" """
permissions = [ permissions = [
"create-app-specific-dirs", "create-app-specific-dirs",

File diff suppressed because it is too large Load Diff

@ -150,11 +150,6 @@ pub fn open<R: Runtime>(
Ok(rid) Ok(rid)
} }
#[tauri::command]
pub fn close<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> CommandResult<()> {
webview.resources_table().close(rid).map_err(Into::into)
}
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CopyFileOptions { pub struct CopyFileOptions {

@ -53,10 +53,7 @@ impl FilePath {
#[inline] #[inline]
pub fn into_path(self) -> Result<PathBuf> { pub fn into_path(self) -> Result<PathBuf> {
match self { match self {
Self::Url(url) => url Self::Url(url) => url.to_file_path().map_err(|_| Error::InvalidPathUrl),
.to_file_path()
.map(PathBuf::from)
.map_err(|_| Error::InvalidPathUrl),
Self::Path(p) => Ok(p), Self::Path(p) => Ok(p),
} }
} }
@ -91,10 +88,7 @@ impl SafeFilePath {
#[inline] #[inline]
pub fn into_path(self) -> Result<PathBuf> { pub fn into_path(self) -> Result<PathBuf> {
match self { match self {
Self::Url(url) => url Self::Url(url) => url.to_file_path().map_err(|_| Error::InvalidPathUrl),
.to_file_path()
.map(PathBuf::from)
.map_err(|_| Error::InvalidPathUrl),
Self::Path(p) => Ok(p.as_ref().to_owned()), Self::Path(p) => Ok(p.as_ref().to_owned()),
} }
} }

@ -397,7 +397,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
commands::create, commands::create,
commands::open, commands::open,
commands::copy_file, commands::copy_file,
commands::close,
commands::mkdir, commands::mkdir,
commands::read_dir, commands::read_dir,
commands::read, commands::read,
@ -420,8 +419,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
commands::size, commands::size,
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watcher::watch, watcher::watch,
#[cfg(feature = "watch")]
watcher::unwatch
]) ])
.setup(|app, api| { .setup(|app, api| {
let scope = Scope { let scope = Scope {

@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, RecommendedCache}; use notify_debouncer_full::{new_debouncer, DebouncedEvent, Debouncer, RecommendedCache};
use serde::Deserialize; use serde::Deserialize;
use tauri::{ use tauri::{
ipc::{Channel, CommandScope, GlobalScope}, ipc::{Channel, CommandScope, GlobalScope},
@ -11,15 +11,7 @@ use tauri::{
Manager, Resource, ResourceId, Runtime, Webview, Manager, Resource, ResourceId, Runtime, Webview,
}; };
use std::{ use std::time::Duration;
path::PathBuf,
sync::{
mpsc::{channel, Receiver},
Mutex,
},
thread::spawn,
time::Duration,
};
use crate::{ use crate::{
commands::{resolve_path, CommandResult}, commands::{resolve_path, CommandResult},
@ -27,79 +19,44 @@ use crate::{
SafeFilePath, SafeFilePath,
}; };
struct InnerWatcher { #[allow(unused)]
pub kind: WatcherKind,
paths: Vec<PathBuf>,
}
pub struct WatcherResource(Mutex<InnerWatcher>);
impl WatcherResource {
fn new(kind: WatcherKind, paths: Vec<PathBuf>) -> Self {
Self(Mutex::new(InnerWatcher { kind, paths }))
}
fn with_lock<R, F: FnMut(&mut InnerWatcher) -> R>(&self, mut f: F) -> R {
let mut watcher = self.0.lock().unwrap();
f(&mut watcher)
}
}
impl Resource for WatcherResource {}
enum WatcherKind { enum WatcherKind {
Debouncer(Debouncer<RecommendedWatcher, RecommendedCache>), Debouncer(Debouncer<RecommendedWatcher, RecommendedCache>),
Watcher(RecommendedWatcher), Watcher(RecommendedWatcher),
} }
fn watch_raw(on_event: Channel<Event>, rx: Receiver<notify::Result<Event>>) { impl Resource for WatcherKind {}
spawn(move || {
while let Ok(event) = rx.recv() {
if let Ok(event) = event {
// TODO: Should errors be emitted too?
let _ = on_event.send(event);
}
}
});
}
fn watch_debounced(on_event: Channel<Event>, rx: Receiver<DebounceEventResult>) {
spawn(move || {
while let Ok(Ok(events)) = rx.recv() {
for event in events {
// TODO: Should errors be emitted too?
let _ = on_event.send(event.event);
}
}
});
}
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct WatchOptions { pub struct WatchOptions {
base_dir: Option<BaseDirectory>, base_dir: Option<BaseDirectory>,
#[serde(default)]
recursive: bool, recursive: bool,
delay_ms: Option<u64>, delay_ms: Option<u64>,
} }
#[tauri::command] #[tauri::command]
pub async fn watch<R: Runtime>( pub fn watch<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
paths: Vec<SafeFilePath>, paths: Vec<SafeFilePath>,
options: WatchOptions, options: WatchOptions,
on_event: Channel<Event>, on_event: Channel<notify::Event>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let mut resolved_paths = Vec::with_capacity(paths.capacity()); let resolved_paths = paths
for path in paths { .into_iter()
resolved_paths.push(resolve_path( .map(|path| {
resolve_path(
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
path, path,
options.base_dir, options.base_dir,
)?); )
} })
.collect::<CommandResult<Vec<_>>>()?;
let recursive_mode = if options.recursive { let recursive_mode = if options.recursive {
RecursiveMode::Recursive RecursiveMode::Recursive
@ -107,52 +64,40 @@ pub async fn watch<R: Runtime>(
RecursiveMode::NonRecursive RecursiveMode::NonRecursive
}; };
let kind = if let Some(delay) = options.delay_ms { let watcher_kind = if let Some(delay) = options.delay_ms {
let (tx, rx) = channel(); let mut debouncer = new_debouncer(
let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?; Duration::from_millis(delay),
None,
move |events: Result<Vec<DebouncedEvent>, Vec<notify::Error>>| {
if let Ok(events) = events {
for event in events {
// TODO: Should errors be emitted too?
let _ = on_event.send(event.event);
}
}
},
)?;
for path in &resolved_paths { for path in &resolved_paths {
debouncer.watch(path, recursive_mode)?; debouncer.watch(path, recursive_mode)?;
} }
watch_debounced(on_event, rx);
WatcherKind::Debouncer(debouncer) WatcherKind::Debouncer(debouncer)
} else { } else {
let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(
let mut watcher = RecommendedWatcher::new(tx, Config::default())?; move |event| {
if let Ok(event) = event {
// TODO: Should errors be emitted too?
let _ = on_event.send(event);
}
},
Config::default(),
)?;
for path in &resolved_paths { for path in &resolved_paths {
watcher.watch(path, recursive_mode)?; watcher.watch(path, recursive_mode)?;
} }
watch_raw(on_event, rx);
WatcherKind::Watcher(watcher) WatcherKind::Watcher(watcher)
}; };
let rid = webview let rid = webview.resources_table().add(watcher_kind);
.resources_table()
.add(WatcherResource::new(kind, resolved_paths));
Ok(rid) Ok(rid)
} }
#[tauri::command]
pub async fn unwatch<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> CommandResult<()> {
let watcher = webview.resources_table().take::<WatcherResource>(rid)?;
WatcherResource::with_lock(&watcher, |watcher| {
match &mut watcher.kind {
WatcherKind::Debouncer(ref mut debouncer) => {
for path in &watcher.paths {
debouncer.unwatch(path).map_err(|e| {
format!("failed to unwatch path: {} with error: {e}", path.display())
})?;
}
}
WatcherKind::Watcher(ref mut w) => {
for path in &watcher.paths {
w.unwatch(path).map_err(|e| {
format!("failed to unwatch path: {} with error: {e}", path.display())
})?;
}
}
}
Ok(())
})
}

@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.2.4]
- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs.
## \[2.2.3] ## \[2.2.3]
- [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled. - [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled.

@ -1,7 +1,7 @@
[package] [package]
name = "tauri-plugin-geolocation" name = "tauri-plugin-geolocation"
description = "Get and track the device's current position" description = "Get and track the device's current position"
version = "2.2.3" version = "2.2.4"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }

@ -72,7 +72,7 @@ This plugin automatically adds the following permissions to your `AndroidManifes
If your app requires GPS functionality to function, **you** should add the following to your `AndroidManifest.xml` file: If your app requires GPS functionality to function, **you** should add the following to your `AndroidManifest.xml` file:
```xml ```xml
<uses-feature android:name="android.hardware.gps" android:required="true" /> <uses-feature android:name="android.hardware.location.gps" android:required="true" />
``` ```
The Google Play Store uses this property to decide whether it should show the app to devices without GPS capabilities. The Google Play Store uses this property to decide whether it should show the app to devices without GPS capabilities.

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function n(t,n,e,i){if("function"==typeof n||!n.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(t):i?i.value:n.get(t)}function e(t,n,e,i,s){if("function"==typeof n||!n.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return n.set(t,e),e}var i,s,o;"function"==typeof SuppressedError&&SuppressedError;const r="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),s.set(this,0),o.set(this,[]),this.id=function(t,n=!1){return window.__TAURI_INTERNALS__.transformCallback(t,n)}((({message:t,id:r})=>{if(r==n(this,s,"f"))for(n(this,i,"f").call(this,t),e(this,s,n(this,s,"f")+1);n(this,s,"f")in n(this,o,"f");){const t=n(this,o,"f")[n(this,s,"f")];n(this,i,"f").call(this,t),delete n(this,o,"f")[n(this,s,"f")],e(this,s,n(this,s,"f")+1)}else n(this,o,"f")[r]=t}))}set onmessage(t){e(this,i,t)}get onmessage(){return n(this,i,"f")}[(i=new WeakMap,s=new WeakMap,o=new WeakMap,r)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[r]()}}async function c(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return t.checkPermissions=async function(){return await async function(t){return c(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await c("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await c("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await c("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,n){const e=new a;return e.onmessage=t=>{"string"==typeof t?n(null,t):n(t)},await c("plugin:geolocation|watch_position",{options:t,channel:e}),e.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})} if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function n(t,n,e,i){if("function"==typeof n||!n.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===e?i:"a"===e?i.call(t):i?i.value:n.get(t)}function e(t,n,e,i,s){if("function"==typeof n||!n.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return n.set(t,e),e}var i,s,o,r;"function"==typeof SuppressedError&&SuppressedError;const a="__TAURI_TO_IPC_KEY__";class c{constructor(t){i.set(this,void 0),s.set(this,0),o.set(this,[]),r.set(this,void 0),e(this,i,t||(()=>{})),this.id=function(t,n=!1){return window.__TAURI_INTERNALS__.transformCallback(t,n)}((t=>{const a=t.index;if("end"in t)return void(a==n(this,s,"f")?this.cleanupCallback():e(this,r,a));const c=t.message;if(a==n(this,s,"f")){for(n(this,i,"f").call(this,c),e(this,s,n(this,s,"f")+1);n(this,s,"f")in n(this,o,"f");){const t=n(this,o,"f")[n(this,s,"f")];n(this,i,"f").call(this,t),delete n(this,o,"f")[n(this,s,"f")],e(this,s,n(this,s,"f")+1)}n(this,s,"f")===n(this,r,"f")&&this.cleanupCallback()}else n(this,o,"f")[a]=c}))}cleanupCallback(){Reflect.deleteProperty(window,`_${this.id}`)}set onmessage(t){e(this,i,t)}get onmessage(){return n(this,i,"f")}[(i=new WeakMap,s=new WeakMap,o=new WeakMap,r=new WeakMap,a)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[a]()}}async function _(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return t.checkPermissions=async function(){return await async function(t){return _(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await _("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await _("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await _("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,n){const e=new c;return e.onmessage=t=>{"string"==typeof t?n(null,t):n(t)},await _("plugin:geolocation|watch_position",{options:t,channel:e}),e.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})}

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-geolocation", "name": "@tauri-apps/plugin-geolocation",
"version": "2.2.3", "version": "2.2.4",
"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"

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,62 +297,74 @@
{ {
"description": "Enables the check_permissions command without any pre-configured scope.", "description": "Enables the check_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-check-permissions" "const": "allow-check-permissions",
"markdownDescription": "Enables the check_permissions command without any pre-configured scope."
}, },
{ {
"description": "Denies the check_permissions command without any pre-configured scope.", "description": "Denies the check_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-check-permissions" "const": "deny-check-permissions",
"markdownDescription": "Denies the check_permissions command without any pre-configured scope."
}, },
{ {
"description": "Enables the clear_permissions command without any pre-configured scope.", "description": "Enables the clear_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-clear-permissions" "const": "allow-clear-permissions",
"markdownDescription": "Enables the clear_permissions command without any pre-configured scope."
}, },
{ {
"description": "Denies the clear_permissions command without any pre-configured scope.", "description": "Denies the clear_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-clear-permissions" "const": "deny-clear-permissions",
"markdownDescription": "Denies the clear_permissions command without any pre-configured scope."
}, },
{ {
"description": "Enables the clear_watch command without any pre-configured scope.", "description": "Enables the clear_watch command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-clear-watch" "const": "allow-clear-watch",
"markdownDescription": "Enables the clear_watch command without any pre-configured scope."
}, },
{ {
"description": "Denies the clear_watch command without any pre-configured scope.", "description": "Denies the clear_watch command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-clear-watch" "const": "deny-clear-watch",
"markdownDescription": "Denies the clear_watch command without any pre-configured scope."
}, },
{ {
"description": "Enables the get_current_position command without any pre-configured scope.", "description": "Enables the get_current_position command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-get-current-position" "const": "allow-get-current-position",
"markdownDescription": "Enables the get_current_position command without any pre-configured scope."
}, },
{ {
"description": "Denies the get_current_position command without any pre-configured scope.", "description": "Denies the get_current_position command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-get-current-position" "const": "deny-get-current-position",
"markdownDescription": "Denies the get_current_position command without any pre-configured scope."
}, },
{ {
"description": "Enables the request_permissions command without any pre-configured scope.", "description": "Enables the request_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-request-permissions" "const": "allow-request-permissions",
"markdownDescription": "Enables the request_permissions command without any pre-configured scope."
}, },
{ {
"description": "Denies the request_permissions command without any pre-configured scope.", "description": "Denies the request_permissions command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-request-permissions" "const": "deny-request-permissions",
"markdownDescription": "Denies the request_permissions command without any pre-configured scope."
}, },
{ {
"description": "Enables the watch_position command without any pre-configured scope.", "description": "Enables the watch_position command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-watch-position" "const": "allow-watch-position",
"markdownDescription": "Enables the watch_position command without any pre-configured scope."
}, },
{ {
"description": "Denies the watch_position command without any pre-configured scope.", "description": "Denies the watch_position command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-watch-position" "const": "deny-watch-position",
"markdownDescription": "Denies the watch_position command without any pre-configured scope."
} }
] ]
} }

@ -21,9 +21,9 @@ mod models;
pub use error::{Error, Result}; pub use error::{Error, Result};
#[cfg(desktop)] #[cfg(desktop)]
use desktop::Geolocation; pub use desktop::Geolocation;
#[cfg(mobile)] #[cfg(mobile)]
use mobile::Geolocation; pub use mobile::Geolocation;
/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the geolocation APIs. /// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the geolocation APIs.
pub trait GeolocationExt<R: Runtime> { pub trait GeolocationExt<R: Runtime> {

@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.2.1]
- [`494d1fea`](https://github.com/tauri-apps/plugins-workspace/commit/494d1fea137ffd60da98b25305c9d666df62cc63) ([#2684](https://github.com/tauri-apps/plugins-workspace/pull/2684) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated `global-hotkey` crate to `0.7` to fix a panic when trying to register a key combination which was already registered by another program. No API changes.
## \[2.2.0] ## \[2.2.0]
- [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other. - [`3a79266b`](https://github.com/tauri-apps/plugins-workspace/commit/3a79266b8cf96a55b1ae6339d725567d45a44b1d) ([#2173](https://github.com/tauri-apps/plugins-workspace/pull/2173) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Bumped all plugins to `v2.2.0`. From now, the versions for the Rust and JavaScript packages of each plugin will be in sync with each other.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-global-shortcut" name = "tauri-plugin-global-shortcut"
version = "2.2.0" version = "2.2.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 }
@ -31,4 +31,4 @@ log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies] [target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
global-hotkey = { version = "0.6", features = ["serde"] } global-hotkey = { version = "0.7", features = ["serde"] }

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBAL_SHORTCUT__=function(t){"use strict";function e(t,e,r,s){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?s:"a"===r?s.call(t):s?s.value:e.get(t)}function r(t,e,r,s,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,r),r}var s,i,n;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),i.set(this,0),n.set(this,[]),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:o})=>{if(o==e(this,i,"f"))for(e(this,s,"f").call(this,t),r(this,i,e(this,i,"f")+1);e(this,i,"f")in e(this,n,"f");){const t=e(this,n,"f")[e(this,i,"f")];e(this,s,"f").call(this,t),delete e(this,n,"f")[e(this,i,"f")],r(this,i,e(this,i,"f")+1)}else e(this,n,"f")[o]=t}))}set onmessage(t){r(this,s,t)}get onmessage(){return e(this,s,"f")}[(s=new WeakMap,i=new WeakMap,n=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function _(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}return t.isRegistered=async function(t){return await _("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const r=new a;return r.onmessage=e,await _("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:r})},t.unregister=async function(t){return await _("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await _("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBAL_SHORTCUT__})} if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBAL_SHORTCUT__=function(t){"use strict";function e(t,e,s,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?i:"a"===s?i.call(t):i?i.value:e.get(t)}function s(t,e,s,i,r){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,s),s}var i,r,n,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class c{constructor(t){i.set(this,void 0),r.set(this,0),n.set(this,[]),a.set(this,void 0),s(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const o=t.index;if("end"in t)return void(o==e(this,r,"f")?this.cleanupCallback():s(this,a,o));const c=t.message;if(o==e(this,r,"f")){for(e(this,i,"f").call(this,c),s(this,r,e(this,r,"f")+1);e(this,r,"f")in e(this,n,"f");){const t=e(this,n,"f")[e(this,r,"f")];e(this,i,"f").call(this,t),delete e(this,n,"f")[e(this,r,"f")],s(this,r,e(this,r,"f")+1)}e(this,r,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,n,"f")[o]=c}))}cleanupCallback(){Reflect.deleteProperty(window,`_${this.id}`)}set onmessage(t){s(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,r=new WeakMap,n=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function u(t,e={},s){return window.__TAURI_INTERNALS__.invoke(t,e,s)}return t.isRegistered=async function(t){return await u("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const s=new c;return s.onmessage=e,await u("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:s})},t.unregister=async function(t){return await u("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await u("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBAL_SHORTCUT__})}

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-global-shortcut", "name": "@tauri-apps/plugin-global-shortcut",
"version": "2.2.0", "version": "2.2.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"

@ -6,6 +6,8 @@ application specific if specific shortcuts should be
registered or unregistered. registered or unregistered.
#### This default permission set includes the following:
## Permission Table ## Permission Table

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,57 +297,68 @@
{ {
"description": "Enables the is_registered command without any pre-configured scope.", "description": "Enables the is_registered command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-is-registered" "const": "allow-is-registered",
"markdownDescription": "Enables the is_registered command without any pre-configured scope."
}, },
{ {
"description": "Denies the is_registered command without any pre-configured scope.", "description": "Denies the is_registered command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-is-registered" "const": "deny-is-registered",
"markdownDescription": "Denies the is_registered command without any pre-configured scope."
}, },
{ {
"description": "Enables the register command without any pre-configured scope.", "description": "Enables the register command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-register" "const": "allow-register",
"markdownDescription": "Enables the register command without any pre-configured scope."
}, },
{ {
"description": "Denies the register command without any pre-configured scope.", "description": "Denies the register command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-register" "const": "deny-register",
"markdownDescription": "Denies the register command without any pre-configured scope."
}, },
{ {
"description": "Enables the register_all command without any pre-configured scope.", "description": "Enables the register_all command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-register-all" "const": "allow-register-all",
"markdownDescription": "Enables the register_all command without any pre-configured scope."
}, },
{ {
"description": "Denies the register_all command without any pre-configured scope.", "description": "Denies the register_all command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-register-all" "const": "deny-register-all",
"markdownDescription": "Denies the register_all command without any pre-configured scope."
}, },
{ {
"description": "Enables the unregister command without any pre-configured scope.", "description": "Enables the unregister command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-unregister" "const": "allow-unregister",
"markdownDescription": "Enables the unregister command without any pre-configured scope."
}, },
{ {
"description": "Denies the unregister command without any pre-configured scope.", "description": "Denies the unregister command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-unregister" "const": "deny-unregister",
"markdownDescription": "Denies the unregister command without any pre-configured scope."
}, },
{ {
"description": "Enables the unregister_all command without any pre-configured scope.", "description": "Enables the unregister_all command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-unregister-all" "const": "allow-unregister-all",
"markdownDescription": "Enables the unregister_all command without any pre-configured scope."
}, },
{ {
"description": "Denies the unregister_all command without any pre-configured scope.", "description": "Denies the unregister_all command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-unregister-all" "const": "deny-unregister-all",
"markdownDescription": "Denies the unregister_all command without any pre-configured scope."
}, },
{ {
"description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n", "description": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "No features are enabled by default, as we believe\nthe shortcuts can be inherently dangerous and it is\napplication specific if specific shortcuts should be\nregistered or unregistered.\n"
} }
] ]
} }

@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.2.4]
- [`a1b3fa27`](https://github.com/tauri-apps/plugins-workspace/commit/a1b3fa27f11022c9b6622b4fab12d93239eb05de) ([#2515](https://github.com/tauri-apps/plugins-workspace/pull/2515) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Re-exported the `Geolocation`, `Haptics`, `Notification`, and `Os` structs so that they show up on docs.rs.
## \[2.2.3] ## \[2.2.3]
- [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled. - [`406e6f48`](https://github.com/tauri-apps/plugins-workspace/commit/406e6f484cdc13d35c50fb949f7489ca9eeccc44) ([#2323](https://github.com/tauri-apps/plugins-workspace/pull/2323) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused build failures when the `haptics` or `geolocation` plugin was used without their `specta` feature flag enabled.

@ -1,7 +1,7 @@
[package] [package]
name = "tauri-plugin-haptics" name = "tauri-plugin-haptics"
description = "Haptic feedback and vibrations on Android and iOS" description = "Haptic feedback and vibrations on Android and iOS"
version = "2.2.3" version = "2.2.4"
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-haptics", "name": "@tauri-apps/plugin-haptics",
"version": "2.2.3", "version": "2.2.4",
"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"

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,42 +297,50 @@
{ {
"description": "Enables the impact_feedback command without any pre-configured scope.", "description": "Enables the impact_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-impact-feedback" "const": "allow-impact-feedback",
"markdownDescription": "Enables the impact_feedback command without any pre-configured scope."
}, },
{ {
"description": "Denies the impact_feedback command without any pre-configured scope.", "description": "Denies the impact_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-impact-feedback" "const": "deny-impact-feedback",
"markdownDescription": "Denies the impact_feedback command without any pre-configured scope."
}, },
{ {
"description": "Enables the notification_feedback command without any pre-configured scope.", "description": "Enables the notification_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-notification-feedback" "const": "allow-notification-feedback",
"markdownDescription": "Enables the notification_feedback command without any pre-configured scope."
}, },
{ {
"description": "Denies the notification_feedback command without any pre-configured scope.", "description": "Denies the notification_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-notification-feedback" "const": "deny-notification-feedback",
"markdownDescription": "Denies the notification_feedback command without any pre-configured scope."
}, },
{ {
"description": "Enables the selection_feedback command without any pre-configured scope.", "description": "Enables the selection_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-selection-feedback" "const": "allow-selection-feedback",
"markdownDescription": "Enables the selection_feedback command without any pre-configured scope."
}, },
{ {
"description": "Denies the selection_feedback command without any pre-configured scope.", "description": "Denies the selection_feedback command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-selection-feedback" "const": "deny-selection-feedback",
"markdownDescription": "Denies the selection_feedback command without any pre-configured scope."
}, },
{ {
"description": "Enables the vibrate command without any pre-configured scope.", "description": "Enables the vibrate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-vibrate" "const": "allow-vibrate",
"markdownDescription": "Enables the vibrate command without any pre-configured scope."
}, },
{ {
"description": "Denies the vibrate command without any pre-configured scope.", "description": "Denies the vibrate command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-vibrate" "const": "deny-vibrate",
"markdownDescription": "Denies the vibrate command without any pre-configured scope."
} }
] ]
} }

@ -21,9 +21,9 @@ mod models;
pub use error::{Error, Result}; pub use error::{Error, Result};
#[cfg(desktop)] #[cfg(desktop)]
use desktop::Haptics; pub use desktop::Haptics;
#[cfg(mobile)] #[cfg(mobile)]
use mobile::Haptics; pub use mobile::Haptics;
/// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the haptics APIs. /// Extensions to [`tauri::App`], [`tauri::AppHandle`], [`tauri::WebviewWindow`], [`tauri::Webview`] and [`tauri::Window`] to access the haptics APIs.
pub trait HapticsExt<R: Runtime> { pub trait HapticsExt<R: Runtime> {

@ -1,5 +1,34 @@
# Changelog # Changelog
## \[2.4.4]
- [`ff384cba`](https://github.com/tauri-apps/plugins-workspace/commit/ff384cbabe82ae715798a4ee49fd07ffcfbcdb5d) ([#2636](https://github.com/tauri-apps/plugins-workspace/pull/2636) by [@asomethings](https://github.com/tauri-apps/plugins-workspace/../../asomethings)) Properly handle responses with status code 204.
### Dependencies
- Upgraded to `fs-js@2.3.0`
## \[2.4.3]
- [`37c0477a`](https://github.com/tauri-apps/plugins-workspace/commit/37c0477afe926d326573f1827045875ce8bf8187) ([#2561](https://github.com/tauri-apps/plugins-workspace/pull/2561)) Add `zstd` cargo feature flag to enable `reqwest/zstd` flag.
- [`9ebbfb2e`](https://github.com/tauri-apps/plugins-workspace/commit/9ebbfb2e3ccef8e0f277a0c02fe6b399b41feeb6) ([#1978](https://github.com/tauri-apps/plugins-workspace/pull/1978)) Persist cookies to disk and load it on next app start.
### Dependencies
- Upgraded to `fs-js@2.2.1`
## \[2.4.2]
- [`a15eedf3`](https://github.com/tauri-apps/plugins-workspace/commit/a15eedf37854344f7ffbcb0d373d848563817011) ([#2535](https://github.com/tauri-apps/plugins-workspace/pull/2535) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `fetch` occasionally throwing an error due to trying to close the underline stream twice.
## \[2.4.1]
- [`d3183aa9`](https://github.com/tauri-apps/plugins-workspace/commit/d3183aa99da7ca67e627394132ddeb3b85ccef06) ([#2522](https://github.com/tauri-apps/plugins-workspace/pull/2522) by [@adrieljss](https://github.com/tauri-apps/plugins-workspace/../../adrieljss)) Fix `fetch` blocking until the whole response is read even if it was a streaming response.
## \[2.4.0]
- [`cb38f54f`](https://github.com/tauri-apps/plugins-workspace/commit/cb38f54f4a4ef30995283cd82166c62da17bac44) ([#2479](https://github.com/tauri-apps/plugins-workspace/pull/2479) by [@adrieljss](https://github.com/tauri-apps/plugins-workspace/../../adrieljss)) Add stream support for HTTP stream responses.
## \[2.3.0] ## \[2.3.0]
- [`10513649`](https://github.com/tauri-apps/plugins-workspace/commit/105136494c5a5bf4b1f1cc06cc71815412d17ec8) ([#2204](https://github.com/tauri-apps/plugins-workspace/pull/2204) by [@RickeyWard](https://github.com/tauri-apps/plugins-workspace/../../RickeyWard)) Add `dangerous-settings` feature flag and new JS `danger` option to disable tls hostname/certificate validation. - [`10513649`](https://github.com/tauri-apps/plugins-workspace/commit/105136494c5a5bf4b1f1cc06cc71815412d17ec8) ([#2204](https://github.com/tauri-apps/plugins-workspace/pull/2204) by [@RickeyWard](https://github.com/tauri-apps/plugins-workspace/../../RickeyWard)) Add `dangerous-settings` feature flag and new JS `danger` option to disable tls hostname/certificate validation.

@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.3.0" version = "2.4.4"
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 }
@ -34,13 +34,15 @@ serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] } tokio = { version = "1", features = ["sync", "macros"] }
tauri-plugin-fs = { path = "../fs", version = "2.2.0" } tauri-plugin-fs = { path = "../fs", version = "2.3.0" }
urlpattern = "0.3" urlpattern = "0.3"
regex = "1" regex = "1"
http = "1" http = "1"
reqwest = { version = "0.12", default-features = false } reqwest = { version = "0.12", default-features = false }
url = { workspace = true } url = { workspace = true }
data-url = "0.3" data-url = "0.3"
cookie_store = { version = "0.21.1", optional = true, features = ["serde"] }
bytes = { version = "1.9", optional = true }
tracing = { workspace = true, optional = true } tracing = { workspace = true, optional = true }
[features] [features]
@ -62,10 +64,11 @@ rustls-tls-manual-roots = ["reqwest/rustls-tls-manual-roots"]
rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"] rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"] rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"]
blocking = ["reqwest/blocking"] blocking = ["reqwest/blocking"]
cookies = ["reqwest/cookies"] cookies = ["reqwest/cookies", "dep:cookie_store", "dep:bytes"]
gzip = ["reqwest/gzip"] gzip = ["reqwest/gzip"]
brotli = ["reqwest/brotli"] brotli = ["reqwest/brotli"]
deflate = ["reqwest/deflate"] deflate = ["reqwest/deflate"]
zstd = ["reqwest/zstd"]
trust-dns = ["reqwest/trust-dns"] trust-dns = ["reqwest/trust-dns"]
socks = ["reqwest/socks"] socks = ["reqwest/socks"]
http2 = ["reqwest/http2"] http2 = ["reqwest/http2"]

@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";async function t(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}"function"==typeof SuppressedError&&SuppressedError;const r="Request canceled";return e.fetch=async function(e,n){const a=n?.signal;if(a?.aborted)throw new Error(r);const o=n?.maxRedirections,s=n?.connectTimeout,i=n?.proxy,d=n?.danger;n&&(delete n.maxRedirections,delete n.connectTimeout,delete n.proxy,delete n.danger);const c=n?.headers?n.headers instanceof Headers?n.headers:new Headers(n.headers):new Headers,u=new Request(e,n),f=await u.arrayBuffer(),_=0!==f.byteLength?Array.from(new Uint8Array(f)):null;for(const[e,t]of u.headers)c.get(e)||c.set(e,t);const h=(c instanceof Headers?Array.from(c.entries()):Array.isArray(c)?c:Object.entries(c)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(a?.aborted)throw new Error(r);const l=await t("plugin:http|fetch",{clientConfig:{method:u.method,url:u.url,headers:h,data:_,maxRedirections:o,connectTimeout:s,proxy:i,danger:d}}),p=()=>t("plugin:http|fetch_cancel",{rid:l});if(a?.aborted)throw p(),new Error(r);a?.addEventListener("abort",(()=>{p()}));const{status:w,statusText:y,url:g,headers:T,rid:A}=await t("plugin:http|fetch_send",{rid:l}),R=await t("plugin:http|fetch_read_body",{rid:A}),b=new Response(R instanceof ArrayBuffer&&0!==R.byteLength?R:R instanceof Array&&R.length>0?new Uint8Array(R):null,{status:w,statusText:y});return Object.defineProperty(b,"url",{value:g}),Object.defineProperty(b,"headers",{value:new Headers(T)}),b},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})} if("__TAURI__"in window){var __TAURI_PLUGIN_HTTP__=function(e){"use strict";function t(e,t,r,n){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?n:"a"===r?n.call(e):n?n.value:t.get(e)}function r(e,t,r,n,s){if("function"==typeof t||!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,r),r}var n,s,i,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class c{constructor(e){n.set(this,void 0),s.set(this,0),i.set(this,[]),a.set(this,void 0),r(this,n,e||(()=>{})),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((e=>{const o=e.index;if("end"in e)return void(o==t(this,s,"f")?this.cleanupCallback():r(this,a,o));const c=e.message;if(o==t(this,s,"f")){for(t(this,n,"f").call(this,c),r(this,s,t(this,s,"f")+1);t(this,s,"f")in t(this,i,"f");){const e=t(this,i,"f")[t(this,s,"f")];t(this,n,"f").call(this,e),delete t(this,i,"f")[t(this,s,"f")],r(this,s,t(this,s,"f")+1)}t(this,s,"f")===t(this,a,"f")&&this.cleanupCallback()}else t(this,i,"f")[o]=c}))}cleanupCallback(){Reflect.deleteProperty(window,`_${this.id}`)}set onmessage(e){r(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,s=new WeakMap,i=new WeakMap,a=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function d(e,t={},r){return window.__TAURI_INTERNALS__.invoke(e,t,r)}const h="Request cancelled";return e.fetch=async function(e,t){const r=t?.signal;if(r?.aborted)throw new Error(h);const n=t?.maxRedirections,s=t?.connectTimeout,i=t?.proxy,a=t?.danger;t&&(delete t.maxRedirections,delete t.connectTimeout,delete t.proxy,delete t.danger);const o=t?.headers?t.headers instanceof Headers?t.headers:new Headers(t.headers):new Headers,f=new Request(e,t),l=await f.arrayBuffer(),u=0!==l.byteLength?Array.from(new Uint8Array(l)):null;for(const[e,t]of f.headers)o.get(e)||o.set(e,t);const _=(o instanceof Headers?Array.from(o.entries()):Array.isArray(o)?o:Object.entries(o)).map((([e,t])=>[e,"string"==typeof t?t:t.toString()]));if(r?.aborted)throw new Error(h);const w=await d("plugin:http|fetch",{clientConfig:{method:f.method,url:f.url,headers:_,data:u,maxRedirections:n,connectTimeout:s,proxy:i,danger:a}}),p=()=>d("plugin:http|fetch_cancel",{rid:w});if(r?.aborted)throw p(),new Error(h);r?.addEventListener("abort",(()=>{p()}));const{status:y,statusText:m,url:b,headers:g,rid:T}=await d("plugin:http|fetch_send",{rid:w}),R=[101,103,204,205,304].includes(y)?null:new ReadableStream({start:e=>{const t=new c;t.onmessage=t=>{if(r?.aborted)return void e.error(h);const n=new Uint8Array(t),s=n[n.byteLength-1],i=n.slice(0,n.byteLength-1);1!=s?e.enqueue(i):e.close()},d("plugin:http|fetch_read_body",{rid:T,streamChannel:t}).catch((t=>{e.error(t)}))}}),A=new Response(R,{status:y,statusText:m});return Object.defineProperty(A,"url",{value:b}),Object.defineProperty(A,"headers",{value:new Headers(g)}),A},e}({});Object.defineProperty(window.__TAURI__,"http",{value:__TAURI_PLUGIN_HTTP__})}

@ -26,7 +26,7 @@
* @module * @module
*/ */
import { invoke } from '@tauri-apps/api/core' import { Channel, invoke } from '@tauri-apps/api/core'
/** /**
* Configuration of a proxy that a Client should pass requests to. * Configuration of a proxy that a Client should pass requests to.
@ -106,7 +106,7 @@ export interface DangerousSettings {
acceptInvalidHostnames?: boolean acceptInvalidHostnames?: boolean
} }
const ERROR_REQUEST_CANCELLED = 'Request canceled' const ERROR_REQUEST_CANCELLED = 'Request cancelled'
/** /**
* Fetch a resource from the network. It returns a `Promise` that resolves to the * Fetch a resource from the network. It returns a `Promise` that resolves to the
@ -229,31 +229,53 @@ export async function fetch(
rid rid
}) })
const body = await invoke<ArrayBuffer | number[]>( // no body for 101, 103, 204, 205 and 304
'plugin:http|fetch_read_body', // see https://fetch.spec.whatwg.org/#null-body-status
{ const body = [101, 103, 204, 205, 304].includes(status)
rid: responseRid ? null
: new ReadableStream({
start: (controller) => {
const streamChannel = new Channel<ArrayBuffer | number[]>()
streamChannel.onmessage = (res: ArrayBuffer | number[]) => {
// close early if aborted
if (signal?.aborted) {
controller.error(ERROR_REQUEST_CANCELLED)
return
}
const resUint8 = new Uint8Array(res)
const lastByte = resUint8[resUint8.byteLength - 1]
const actualRes = resUint8.slice(0, resUint8.byteLength - 1)
// close when the signal to close (last byte is 1) is sent from the IPC.
if (lastByte == 1) {
controller.close()
return
}
controller.enqueue(actualRes)
} }
)
const res = new Response( // run a non-blocking body stream fetch
body instanceof ArrayBuffer && body.byteLength !== 0 invoke('plugin:http|fetch_read_body', {
? body rid: responseRid,
: body instanceof Array && body.length > 0 streamChannel
? new Uint8Array(body) }).catch((e) => {
: null, controller.error(e)
{ })
}
})
const res = new Response(body, {
status, status,
statusText statusText
} })
)
// url and headers are read only properties // Set `Response` properties that are ignored by the
// but seems like we can set them like this // constructor, like url and some headers
// //
// we define theme like this, because using `Response` // Since url and headers are read only properties
// constructor, it removes url and some headers // this is the only way to set them.
// like `set-cookie` headers
Object.defineProperty(res, 'url', { value: url }) Object.defineProperty(res, 'url', { value: url })
Object.defineProperty(res, 'headers', { Object.defineProperty(res, 'headers', {
value: new Headers(responseHeaders) value: new Headers(responseHeaders)

@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-http", "name": "@tauri-apps/plugin-http",
"version": "2.3.0", "version": "2.4.4",
"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"

@ -13,6 +13,8 @@ All fetch operations are enabled.
#### This default permission set includes the following:
- `allow-fetch` - `allow-fetch`
- `allow-fetch-cancel` - `allow-fetch-cancel`
- `allow-fetch-read-body` - `allow-fetch-read-body`

@ -49,7 +49,7 @@
"minimum": 1.0 "minimum": 1.0
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -111,7 +111,7 @@
"type": "string" "type": "string"
}, },
"description": { "description": {
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.", "description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
"type": [ "type": [
"string", "string",
"null" "null"
@ -297,47 +297,56 @@
{ {
"description": "Enables the fetch command without any pre-configured scope.", "description": "Enables the fetch command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-fetch" "const": "allow-fetch",
"markdownDescription": "Enables the fetch command without any pre-configured scope."
}, },
{ {
"description": "Denies the fetch command without any pre-configured scope.", "description": "Denies the fetch command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-fetch" "const": "deny-fetch",
"markdownDescription": "Denies the fetch command without any pre-configured scope."
}, },
{ {
"description": "Enables the fetch_cancel command without any pre-configured scope.", "description": "Enables the fetch_cancel command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-fetch-cancel" "const": "allow-fetch-cancel",
"markdownDescription": "Enables the fetch_cancel command without any pre-configured scope."
}, },
{ {
"description": "Denies the fetch_cancel command without any pre-configured scope.", "description": "Denies the fetch_cancel command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-fetch-cancel" "const": "deny-fetch-cancel",
"markdownDescription": "Denies the fetch_cancel command without any pre-configured scope."
}, },
{ {
"description": "Enables the fetch_read_body command without any pre-configured scope.", "description": "Enables the fetch_read_body command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-fetch-read-body" "const": "allow-fetch-read-body",
"markdownDescription": "Enables the fetch_read_body command without any pre-configured scope."
}, },
{ {
"description": "Denies the fetch_read_body command without any pre-configured scope.", "description": "Denies the fetch_read_body command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-fetch-read-body" "const": "deny-fetch-read-body",
"markdownDescription": "Denies the fetch_read_body command without any pre-configured scope."
}, },
{ {
"description": "Enables the fetch_send command without any pre-configured scope.", "description": "Enables the fetch_send command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "allow-fetch-send" "const": "allow-fetch-send",
"markdownDescription": "Enables the fetch_send command without any pre-configured scope."
}, },
{ {
"description": "Denies the fetch_send command without any pre-configured scope.", "description": "Denies the fetch_send command without any pre-configured scope.",
"type": "string", "type": "string",
"const": "deny-fetch-send" "const": "deny-fetch-send",
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
}, },
{ {
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n", "description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`",
"type": "string", "type": "string",
"const": "default" "const": "default",
"markdownDescription": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n\n#### This default permission set includes:\n\n- `allow-fetch`\n- `allow-fetch-cancel`\n- `allow-fetch-read-body`\n- `allow-fetch-send`"
} }
] ]
} }

@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{
async_runtime::Mutex, async_runtime::Mutex,
command, command,
ipc::{CommandScope, GlobalScope}, ipc::{Channel, CommandScope, GlobalScope},
Manager, ResourceId, ResourceTable, Runtime, State, Webview, Manager, ResourceId, ResourceTable, Runtime, State, Webview,
}; };
use tokio::sync::oneshot::{channel, Receiver, Sender}; use tokio::sync::oneshot::{channel, Receiver, Sender};
@ -315,6 +315,7 @@ pub async fn fetch<R: Runtime>(
tracing::trace!("{:?}", request); tracing::trace!("{:?}", request);
let fut = async move { request.send().await.map_err(Into::into) }; let fut = async move { request.send().await.map_err(Into::into) };
let mut resources_table = webview.resources_table(); let mut resources_table = webview.resources_table();
let rid = resources_table.add_request(Box::pin(fut)); let rid = resources_table.add_request(Box::pin(fut));
@ -358,7 +359,7 @@ pub fn fetch_cancel<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> crate::
Ok(()) Ok(())
} }
#[tauri::command] #[command]
pub async fn fetch_send<R: Runtime>( pub async fn fetch_send<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
rid: ResourceId, rid: ResourceId,
@ -410,17 +411,31 @@ pub async fn fetch_send<R: Runtime>(
}) })
} }
#[tauri::command] #[command]
pub(crate) async fn fetch_read_body<R: Runtime>( pub async fn fetch_read_body<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
rid: ResourceId, rid: ResourceId,
) -> crate::Result<tauri::ipc::Response> { stream_channel: Channel<tauri::ipc::InvokeResponseBody>,
) -> crate::Result<()> {
let res = { let res = {
let mut resources_table = webview.resources_table(); let mut resources_table = webview.resources_table();
resources_table.take::<ReqwestResponse>(rid)? resources_table.take::<ReqwestResponse>(rid)?
}; };
let res = Arc::into_inner(res).unwrap().0;
Ok(tauri::ipc::Response::new(res.bytes().await?.to_vec())) let mut res = Arc::into_inner(res).unwrap().0;
// send response through IPC channel
while let Some(chunk) = res.chunk().await? {
let mut chunk = chunk.to_vec();
// append 0 to indicate we are not done yet
chunk.push(0);
stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(chunk))?;
}
// send 1 to indicate we are done
stream_channel.send(tauri::ipc::InvokeResponseBody::Raw(vec![1]))?;
Ok(())
} }
// forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers // forbidden headers per fetch spec https://fetch.spec.whatwg.org/#terminology-headers

@ -14,30 +14,77 @@ pub use error::{Error, Result};
mod commands; mod commands;
mod error; mod error;
#[cfg(feature = "cookies")]
mod reqwest_cookie_store;
mod scope; mod scope;
#[cfg(feature = "cookies")]
const COOKIES_FILENAME: &str = ".cookies";
pub(crate) struct Http { pub(crate) struct Http {
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
cookies_jar: std::sync::Arc<reqwest::cookie::Jar>, cookies_jar: std::sync::Arc<crate::reqwest_cookie_store::CookieStoreMutex>,
} }
pub fn init<R: Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::<R>::new("http") Builder::<R>::new("http")
.setup(|app, _| { .setup(|app, _| {
#[cfg(feature = "cookies")]
let cookies_jar = {
use crate::reqwest_cookie_store::*;
use std::fs::File;
use std::io::BufReader;
let cache_dir = app.path().app_cache_dir()?;
std::fs::create_dir_all(&cache_dir)?;
let path = cache_dir.join(COOKIES_FILENAME);
let file = File::options()
.create(true)
.append(true)
.read(true)
.open(&path)?;
let reader = BufReader::new(file);
CookieStoreMutex::load(path.clone(), reader).unwrap_or_else(|_e| {
#[cfg(feature = "tracing")]
tracing::warn!(
"failed to load cookie store: {_e}, falling back to empty store"
);
CookieStoreMutex::new(path, Default::default())
})
};
let state = Http { let state = Http {
#[cfg(feature = "cookies")] #[cfg(feature = "cookies")]
cookies_jar: std::sync::Arc::new(reqwest::cookie::Jar::default()), cookies_jar: std::sync::Arc::new(cookies_jar),
}; };
app.manage(state); app.manage(state);
Ok(()) Ok(())
}) })
.on_event(|app, event| {
#[cfg(feature = "cookies")]
if let tauri::RunEvent::Exit = event {
let state = app.state::<Http>();
match state.cookies_jar.request_save() {
Ok(rx) => {
let _ = rx.recv();
}
Err(_e) => {
#[cfg(feature = "tracing")]
tracing::error!("failed to save cookie jar: {_e}");
}
}
}
})
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::fetch, commands::fetch,
commands::fetch_cancel, commands::fetch_cancel,
commands::fetch_send, commands::fetch_send,
commands::fetch_read_body, commands::fetch_read_body
]) ])
.build() .build()
} }

@ -0,0 +1,133 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
// taken from https://github.com/pfernie/reqwest_cookie_store/blob/2ec4afabcd55e24d3afe3f0626ee6dc97bed938d/src/lib.rs
use std::{
path::PathBuf,
sync::{mpsc::Receiver, Mutex},
};
use cookie_store::{CookieStore, RawCookie, RawCookieParseError};
use reqwest::header::HeaderValue;
fn set_cookies(
cookie_store: &mut CookieStore,
cookie_headers: &mut dyn Iterator<Item = &HeaderValue>,
url: &url::Url,
) {
let cookies = cookie_headers.filter_map(|val| {
std::str::from_utf8(val.as_bytes())
.map_err(RawCookieParseError::from)
.and_then(RawCookie::parse)
.map(|c| c.into_owned())
.ok()
});
cookie_store.store_response_cookies(cookies, url);
}
fn cookies(cookie_store: &CookieStore, url: &url::Url) -> Option<HeaderValue> {
let s = cookie_store
.get_request_values(url)
.map(|(name, value)| format!("{}={}", name, value))
.collect::<Vec<_>>()
.join("; ");
if s.is_empty() {
return None;
}
HeaderValue::from_maybe_shared(bytes::Bytes::from(s)).ok()
}
/// A [`cookie_store::CookieStore`] wrapped internally by a [`std::sync::Mutex`], suitable for use in
/// async/concurrent contexts.
#[derive(Debug)]
pub struct CookieStoreMutex {
pub path: PathBuf,
store: Mutex<CookieStore>,
save_task: Mutex<Option<CancellableTask>>,
}
impl CookieStoreMutex {
/// Create a new [`CookieStoreMutex`] from an existing [`cookie_store::CookieStore`].
pub fn new(path: PathBuf, cookie_store: CookieStore) -> CookieStoreMutex {
CookieStoreMutex {
path,
store: Mutex::new(cookie_store),
save_task: Default::default(),
}
}
pub fn load<R: std::io::BufRead>(
path: PathBuf,
reader: R,
) -> cookie_store::Result<CookieStoreMutex> {
cookie_store::serde::load(reader, |c| serde_json::from_str(c))
.map(|store| CookieStoreMutex::new(path, store))
}
fn cookies_to_str(&self) -> Result<String, serde_json::Error> {
let mut cookies = Vec::new();
for cookie in self
.store
.lock()
.expect("poisoned cookie jar mutex")
.iter_unexpired()
{
if cookie.is_persistent() {
cookies.push(cookie.clone());
}
}
serde_json::to_string(&cookies)
}
pub fn request_save(&self) -> cookie_store::Result<Receiver<()>> {
let cookie_str = self.cookies_to_str()?;
let path = self.path.clone();
let (tx, rx) = std::sync::mpsc::channel();
let task = tauri::async_runtime::spawn(async move {
match tokio::fs::write(&path, &cookie_str).await {
Ok(()) => {
let _ = tx.send(());
}
Err(_e) => {
#[cfg(feature = "tracing")]
tracing::error!("failed to save cookie jar: {_e}");
}
}
});
self.save_task
.lock()
.unwrap()
.replace(CancellableTask(task));
Ok(rx)
}
}
impl reqwest::cookie::CookieStore for CookieStoreMutex {
fn set_cookies(&self, cookie_headers: &mut dyn Iterator<Item = &HeaderValue>, url: &url::Url) {
set_cookies(&mut self.store.lock().unwrap(), cookie_headers, url);
// try to persist cookies immediately asynchronously
if let Err(_e) = self.request_save() {
#[cfg(feature = "tracing")]
tracing::error!("failed to save cookie jar: {_e}");
}
}
fn cookies(&self, url: &url::Url) -> Option<HeaderValue> {
let store = self.store.lock().unwrap();
cookies(&store, url)
}
}
#[derive(Debug)]
struct CancellableTask(tauri::async_runtime::JoinHandle<()>);
impl Drop for CancellableTask {
fn drop(&mut self) {
self.0.abort();
}
}

@ -1,5 +1,29 @@
# Changelog # Changelog
## \[2.4.0]
- [`c9b21f6f`](https://github.com/tauri-apps/plugins-workspace/commit/c9b21f6f4345806eff5f495885f20dea0082b7d7) ([#2625](https://github.com/tauri-apps/plugins-workspace/pull/2625) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Export the `LogLevel` type.
- [`9629c2f4`](https://github.com/tauri-apps/plugins-workspace/commit/9629c2f4f90a56b5c2d265d1d13d3af40fc0c525) ([#2600](https://github.com/tauri-apps/plugins-workspace/pull/2600) by [@exoego](https://github.com/tauri-apps/plugins-workspace/../../exoego)) Adds a new varient `TargetKind::Dispatch` that allows you to construct arbitrary log targets
- [`686a839c`](https://github.com/tauri-apps/plugins-workspace/commit/686a839c96fae1b0334f2df9dc76ca5cdbe00dbe) ([#2626](https://github.com/tauri-apps/plugins-workspace/pull/2626) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix iOS app stuck when using the iOS Simulator and the log plugin due to a deadlock when calling os_log too early.
### feat
- [`60fc35d3`](https://github.com/tauri-apps/plugins-workspace/commit/60fc35d35cccaf1654eceb4446ecf0f89dc15502) ([#2576](https://github.com/tauri-apps/plugins-workspace/pull/2576) by [@3lpsy](https://github.com/tauri-apps/plugins-workspace/../../3lpsy)) Add a `tracing` feature to the `log` plugin that emits log messages to the `tracing` system.
## \[2.3.1]
- [`1bb1ced5`](https://github.com/tauri-apps/plugins-workspace/commit/1bb1ced53820127204aa7adf57510c1cbce55e12) ([#2524](https://github.com/tauri-apps/plugins-workspace/pull/2524) by [@elwerene](https://github.com/tauri-apps/plugins-workspace/../../elwerene)) enable TargetKind::LogDir on mobile
## \[2.3.0]
### feat
- [`02481501`](https://github.com/tauri-apps/plugins-workspace/commit/024815018fbc63a37afc716796a454925aa7d25e) ([#2377](https://github.com/tauri-apps/plugins-workspace/pull/2377) by [@3lpsy](https://github.com/tauri-apps/plugins-workspace/../../3lpsy)) Add a `is_skip_logger` flag to the Log Plugin `Builder` struct, a `skip_logger()` method to the Builder, and logic to avoid acquiring (creating) a logger and attaching it to the global logger. Since acquire_logger is pub, a `LoggerNotInitialized` is added and returned if it's called when the `is_skip_looger` flag is set. Overall, this feature permits a user to avoid calling `attach_logger` which can only be called once in a program's lifetime and allows the user to control the logger returned from `logger()`. Additionally, it also will allow users to generate multiple Tauri Mock apps in test suites that run and parallel and have the `log` plugin attached (assuming they use `skip_logger()`).
## \[2.2.3]
- [`1a984659`](https://github.com/tauri-apps/plugins-workspace/commit/1a9846599b6a71faf330845847a30f6bf9735898) ([#2469](https://github.com/tauri-apps/plugins-workspace/pull/2469) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Update `objc2` crate to 0.6. No user facing changes.
## \[2.2.2] ## \[2.2.2]
- [`6b4c3917`](https://github.com/tauri-apps/plugins-workspace/commit/6b4c3917389f4bc489d03b48a837557ac0584175) ([#2401](https://github.com/tauri-apps/plugins-workspace/pull/2401) by [@Seishiin](https://github.com/tauri-apps/plugins-workspace/../../Seishiin)) Fix timezone_strategy overwriting previously set LogLevels. - [`6b4c3917`](https://github.com/tauri-apps/plugins-workspace/commit/6b4c3917389f4bc489d03b48a837557ac0584175) ([#2401](https://github.com/tauri-apps/plugins-workspace/pull/2401) by [@Seishiin](https://github.com/tauri-apps/plugins-workspace/../../Seishiin)) Fix timezone_strategy overwriting previously set LogLevels.

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

Loading…
Cancel
Save