Compare commits

..

2 Commits

Author SHA1 Message Date
FabianLars 15fc071773 add corenfc framework 2025-08-11 22:21:03 +02:00
FabianLars 73c6047b78 fix(nfc): use xcode project instead of swift package 2025-08-11 22:08:58 +02:00
288 changed files with 5677 additions and 7556 deletions
+8 -2
View File
@@ -1,5 +1,11 @@
[advisories]
ignore = [
# time crate can't be updated in the repo because of MSRV, users are unaffected
"RUSTSEC-2026-0009",
# time 0.1
"RUSTSEC-2020-0071",
# needs sqlx 0.7 (still in alpha)
"RUSTSEC-2022-0090",
# wry needs kuchiki on Android
"RUSTSEC-2023-0019",
# atty is only used when the `colored` feature is enabled on tauri-plugin-log
"RUSTSEC-2021-0145",
]
+6
View File
@@ -0,0 +1,6 @@
---
fs: patch
fs-js: patch
---
Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
+6
View File
@@ -0,0 +1,6 @@
---
nfc: patch
nfc-js: patch
---
On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
+6
View File
@@ -0,0 +1,6 @@
---
"opener": 'minor:enhance'
"opener-js": 'minor:enhance'
---
Allow reveal multiple items in the file explorer.
+6
View File
@@ -0,0 +1,6 @@
---
store: minor
store-js: minor
---
Allow setting defaults from the JavaScript API
+6
View File
@@ -0,0 +1,6 @@
---
store: minor
store-js: minor
---
Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
-11
View File
@@ -5,7 +5,6 @@ Hi! We, the maintainers, are really excited that you are interested in contribut
- [Issue Reporting Guidelines](#issue-reporting-guidelines)
- [Pull Request Guidelines](#pull-request-guidelines)
- [Development Guide](#development-guide)
- [AI Tool Policy](#ai-tool-policy)
## Issue Reporting Guidelines
@@ -61,13 +60,3 @@ The easiest way to test your changes is to use the [example app](https://github.
To test local changes against your own application simply point the plugin create to your local repository, for example:
`tauri-plugin-sample = { path = "path/to/local/tauri-plugin-sample/" }`
## AI Tool Policy
It takes a lot of time to review a Pull Request while it's very easy to make a nonsensical but plausible looking one using AI tools.
It is unfair for other contributors and the reviewers to spend much of the time dealing with this, hence these rules:
1. Review and test all LLM-generated content before submitting, you're the one responsible for it, not the AI.
2. Don't use AI to respond to review comments (except for translations).
We will close the Pull Request with a `ai-slop` tag if you failed to do so.
-63
View File
@@ -1,63 +0,0 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: 🐞 Bug Report
description: Report a bug
labels: ['type: bug']
body:
- type: markdown
attributes:
value: |
## First of all
1. Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) about this problem first.
2. Make sure `rustc` and all relevant Tauri packages are up to date.
3. Make sure it's an issue with a tauri plugin and not something else you are using.
4. Remember to follow our community guidelines and be friendly.
- type: textarea
id: description
attributes:
label: Describe the bug
description: A clear description of what the bug is. Include screenshots if applicable.
placeholder: Bug description
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction
description: A link to a reproduction repo or steps to reproduce the behaviour.
placeholder: |
Please provide a minimal reproduction or steps to reproduce, see this guide https://stackoverflow.com/help/minimal-reproducible-example
Why reproduction is required? see this article https://antfu.me/posts/why-reproductions-are-required
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear description of what you expected to happen.
- type: textarea
id: info
attributes:
label: Full `tauri info` output
description: 'Output of `npm run tauri info` or `cargo tauri info`. Issues without this will be closed!'
render: text
validations:
required: true
- type: textarea
id: logs
attributes:
label: Stack trace
description: A stack trace or ANY error messages you may see.
render: text
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here. For example a link to a Discord discussion.
-12
View File
@@ -1,12 +0,0 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
#blank_issues_enabled: false
contact_links:
- name: 💡 Request a new plugin
url: https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests
about: Propose a new Plugin to the community or the Tauri org.
- name: 💬 Discord Chat
url: https://discord.com/invite/tauri
about: Ask questions and talk to other Tauri users and the maintainers
-5
View File
@@ -1,5 +0,0 @@
---
name: 📚 Docs Report
about: Create a report to help us improve the docs
labels: 'type: documentation'
---
@@ -1,46 +0,0 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: 💡 Feature Request
description: Request a feature or enhancement for an existing plugin
labels: ['type: feature request']
body:
- type: markdown
attributes:
value: |
## First of all
- Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) for this request first.
- Only requests for plugins that exist in this repo are allowed.
- You can request new plugins [here](https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests)
- type: textarea
id: problem
attributes:
label: Describe the problem
description: A clear description of the problem this feature would solve
placeholder: "I'm always frustrated when..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: "Describe the solution you'd like"
description: A clear description of what change you would like
placeholder: 'I would like to...'
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: "Any alternative solutions you've considered"
- type: textarea
id: context
attributes:
label: Additional context
description: Add any other context about the problem here.
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v2
- uses: rustsec/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# https://github.com/tauri-apps/plugins-workspace/issues/774
@@ -11,7 +11,7 @@ on:
- v2
permissions:
# required for oidc token
# required for npm provenance
id-token: write
# required to create the GitHub Release
contents: write
@@ -62,6 +62,7 @@ jobs:
id: covector
env:
CARGO_TARGET_DIR: /mnt/target
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
command: 'version-or-publish'
-1
View File
@@ -5,7 +5,6 @@
name: integration tests
on:
workflow_dispatch:
push:
branches:
- v1
-1
View File
@@ -154,5 +154,4 @@ jobs:
run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings
- name: clippy ${{ matrix.package }} --all-features
if: matrix.package != 'tauri-plugin-dialog'
run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings
+2 -2
View File
@@ -246,9 +246,9 @@ jobs:
run: cargo +stable install cross --git https://github.com/cross-rs/cross
- name: test ${{ matrix.package }}
if: ${{ matrix.package != 'tauri-plugin-http' && matrix.package != 'tauri-plugin-dialog' }}
if: matrix.package != 'tauri-plugin-http'
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features
- name: test ${{ matrix.package }}
if: ${{ matrix.package == 'tauri-plugin-http' || matrix.package == 'tauri-plugin-dialog' }}
if: matrix.package == 'tauri-plugin-http'
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets
+1 -2
View File
@@ -21,7 +21,6 @@ target/
package-lock.json
yarn.lock
bun.lockb
bun.lock
# rust compiled folders
target/
@@ -59,4 +58,4 @@ pids
.idea
debug.log
TODO.md
.aider.*
.aider*
Generated
+278 -466
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -12,10 +12,10 @@ resolver = "2"
serde = { version = "1", features = ["derive"] }
tracing = "0.1"
log = "0.4"
tauri = { version = "2.10", default-features = false }
tauri-build = "2.5"
tauri-plugin = "2.5"
tauri-utils = "2.8"
tauri = { version = "2.6", default-features = false }
tauri-build = "2.3"
tauri-plugin = "2.3"
tauri-utils = "2.5"
serde_json = "1"
thiserror = "2"
url = "2"
+1 -2
View File
@@ -3,12 +3,11 @@
// SPDX-License-Identifier: MIT
import eslint from '@eslint/js'
import { defineConfig } from 'eslint/config'
import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginSecurity from 'eslint-plugin-security'
import tseslint from 'typescript-eslint'
export default defineConfig(
export default tseslint.config(
{
ignores: [
'**/target',
-107
View File
@@ -1,112 +1,5 @@
# Changelog
## \[2.0.38]
### Dependencies
- Upgraded to `fs-js@2.5.0`
- Upgraded to `http-js@2.5.8`
- Upgraded to `updater-js@2.10.1`
- Upgraded to `nfc-js@2.3.5`
- Upgraded to `dialog-js@2.7.0`
## \[2.0.37]
### Dependencies
- Upgraded to `updater-js@2.10.0`
- Upgraded to `barcode-scanner-js@2.4.4`
- Upgraded to `http-js@2.5.7`
- Upgraded to `shell-js@2.3.5`
## \[2.0.36]
### Dependencies
- Upgraded to `http-js@2.5.6`
- Upgraded to `dialog-js@2.6.0`
## \[2.0.35]
### Dependencies
- Upgraded to `dialog-js@2.5.0`
- Upgraded to `log-js@2.8.0`
- Upgraded to `shell-js@2.3.4`
- Upgraded to `barcode-scanner-js@2.4.3`
- Upgraded to `fs-js@2.4.5`
- Upgraded to `http-js@2.5.5`
- Upgraded to `nfc-js@2.3.4`
- Upgraded to `opener-js@2.5.3`
- Upgraded to `store-js@2.4.2`
## \[2.0.34]
### Dependencies
- Upgraded to `barcode-scanner-js@2.4.2`
- Upgraded to `biometric-js@2.3.2`
- Upgraded to `cli-js@2.4.1`
- Upgraded to `clipboard-manager-js@2.3.2`
- Upgraded to `dialog-js@2.4.2`
- Upgraded to `fs-js@2.4.4`
- Upgraded to `global-shortcut-js@2.3.1`
- Upgraded to `http-js@2.5.4`
- Upgraded to `log-js@2.7.1`
- Upgraded to `nfc-js@2.3.3`
- Upgraded to `notification-js@2.3.3`
- Upgraded to `opener-js@2.5.2`
- Upgraded to `os-js@2.3.2`
- Upgraded to `process-js@2.3.1`
- Upgraded to `shell-js@2.3.3`
- Upgraded to `store-js@2.4.1`
## \[2.0.33]
### Dependencies
- Upgraded to `barcode-scanner-js@2.4.1`
- Upgraded to `biometric-js@2.3.1`
- Upgraded to `clipboard-manager-js@2.3.1`
- Upgraded to `dialog-js@2.4.1`
- Upgraded to `fs-js@2.4.3`
- Upgraded to `nfc-js@2.3.2`
- Upgraded to `notification-js@2.3.2`
- Upgraded to `opener-js@2.5.1`
- Upgraded to `shell-js@2.3.2`
- Upgraded to `http-js@2.5.3`
## \[2.0.32]
### Dependencies
- Upgraded to `dialog-js@2.4.0`
- Upgraded to `log-js@2.7.0`
## \[2.0.31]
### Dependencies
- Upgraded to `shell-js@2.3.1`
## \[2.0.30]
### Dependencies
- Upgraded to `notification-js@2.3.1`
## \[2.0.29]
### Dependencies
- Upgraded to `fs-js@2.4.2`
- Upgraded to `nfc-js@2.3.1`
- Upgraded to `opener-js@2.5.0`
- Upgraded to `os-js@2.3.1`
- Upgraded to `store-js@2.4.0`
- Upgraded to `dialog-js@2.3.3`
- Upgraded to `http-js@2.5.2`
## \[2.0.28]
### Dependencies
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=0"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<title>Svelte + Vite App</title>
</head>
+28 -28
View File
@@ -1,7 +1,7 @@
{
"name": "api",
"private": true,
"version": "2.0.38",
"version": "2.0.28",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
@@ -10,36 +10,36 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-barcode-scanner": "^2.4.4",
"@tauri-apps/plugin-biometric": "^2.3.2",
"@tauri-apps/plugin-cli": "^2.4.1",
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
"@tauri-apps/plugin-dialog": "^2.7.0",
"@tauri-apps/plugin-fs": "^2.5.0",
"@tauri-apps/plugin-geolocation": "^2.3.2",
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
"@tauri-apps/plugin-haptics": "^2.3.2",
"@tauri-apps/plugin-http": "^2.5.8",
"@tauri-apps/plugin-nfc": "^2.3.5",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-store": "^2.4.2",
"@tauri-apps/plugin-updater": "^2.10.1",
"@tauri-apps/plugin-upload": "^2.4.0",
"@tauri-apps/api": "2.7.0",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
"@tauri-apps/plugin-biometric": "^2.3.0",
"@tauri-apps/plugin-cli": "^2.4.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
"@tauri-apps/plugin-haptics": "^2.2.0",
"@tauri-apps/plugin-http": "^2.5.1",
"@tauri-apps/plugin-nfc": "^2.3.0",
"@tauri-apps/plugin-notification": "^2.3.0",
"@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-process": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-store": "^2.3.0",
"@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11"
},
"devDependencies": {
"@iconify-json/codicon": "^1.2.49",
"@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tauri-apps/cli": "2.10.1",
"@unocss/extractor-svelte": "^66.6.7",
"svelte": "^5.54.0",
"unocss": "^66.6.7",
"vite": "^8.0.1"
"@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tauri-apps/cli": "2.7.1",
"@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4",
"unocss": "^66.3.3",
"vite": "^7.0.4"
}
}
-111
View File
@@ -1,116 +1,5 @@
# Changelog
## \[2.0.42]
### Dependencies
- Upgraded to `fs@2.5.0`
- Upgraded to `http@2.5.8`
- Upgraded to `updater@2.10.1`
- Upgraded to `nfc@2.3.5`
- Upgraded to `dialog@2.7.0`
## \[2.0.41]
### Dependencies
- Upgraded to `updater@2.10.0`
- Upgraded to `barcode-scanner@2.4.4`
- Upgraded to `http@2.5.7`
- Upgraded to `shell@2.3.5`
## \[2.0.40]
### Dependencies
- Upgraded to `http@2.5.6`
- Upgraded to `dialog@2.6.0`
## \[2.0.39]
### Dependencies
- Upgraded to `dialog@2.5.0`
- Upgraded to `log@2.8.0`
- Upgraded to `shell@2.3.4`
- Upgraded to `barcode-scanner@2.4.3`
- Upgraded to `fs@2.4.5`
- Upgraded to `http@2.5.5`
- Upgraded to `nfc@2.3.4`
- Upgraded to `opener@2.5.3`
- Upgraded to `store@2.4.2`
## \[2.0.38]
### Dependencies
- Upgraded to `barcode-scanner@2.4.2`
- Upgraded to `biometric@2.3.2`
- Upgraded to `cli@2.4.1`
- Upgraded to `clipboard-manager@2.3.2`
- Upgraded to `dialog@2.4.2`
- Upgraded to `fs@2.4.4`
- Upgraded to `geolocation@2.3.2`
- Upgraded to `global-shortcut@2.3.1`
- Upgraded to `haptics@2.3.2`
- Upgraded to `http@2.5.4`
- Upgraded to `log@2.7.1`
- Upgraded to `nfc@2.3.3`
- Upgraded to `notification@2.3.3`
- Upgraded to `opener@2.5.2`
- Upgraded to `os@2.3.2`
- Upgraded to `process@2.3.1`
- Upgraded to `shell@2.3.3`
- Upgraded to `store@2.4.1`
## \[2.0.37]
### Dependencies
- Upgraded to `barcode-scanner@2.4.1`
- Upgraded to `biometric@2.3.1`
- Upgraded to `clipboard-manager@2.3.1`
- Upgraded to `dialog@2.4.1`
- Upgraded to `fs@2.4.3`
- Upgraded to `geolocation@2.3.1`
- Upgraded to `haptics@2.3.1`
- Upgraded to `nfc@2.3.2`
- Upgraded to `notification@2.3.2`
- Upgraded to `opener@2.5.1`
- Upgraded to `shell@2.3.2`
- Upgraded to `http@2.5.3`
## \[2.0.36]
### Dependencies
- Upgraded to `dialog@2.4.0`
- Upgraded to `log@2.7.0`
## \[2.0.35]
### Dependencies
- Upgraded to `shell@2.3.1`
## \[2.0.34]
### Dependencies
- Upgraded to `notification@2.3.1`
## \[2.0.33]
### Dependencies
- Upgraded to `fs@2.4.2`
- Upgraded to `nfc@2.3.1`
- Upgraded to `opener@2.5.0`
- Upgraded to `os@2.3.1`
- Upgraded to `store@2.4.0`
- Upgraded to `dialog@2.3.3`
- Upgraded to `http@2.5.2`
## \[2.0.32]
### Dependencies
+20 -20
View File
@@ -1,7 +1,7 @@
[package]
name = "api"
publish = false
version = "2.0.42"
version = "2.0.32"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = { workspace = true }
@@ -20,24 +20,24 @@ serde = { workspace = true }
tiny_http = "0.12"
time = "0.3"
log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.8.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.5.0", features = [
tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features = [
"watch",
] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.2" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.7.0" }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.2" }
tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart",
"cookies",
], version = "2.5.8" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.3", features = [
], version = "2.5.1" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.0", features = [
"windows7-compat",
] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.2" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.1" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.3" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.5" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.2" }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.0" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
[dependencies.tauri]
@@ -55,17 +55,17 @@ features = [
]
[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.4.1" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.1" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.10.1" }
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.0" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.0" }
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" }
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.4" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.5" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.2" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.2" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.2" }
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.0" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" }
[features]
prod = ["tauri/custom-protocol"]
@@ -23,7 +23,11 @@
"core:window:allow-start-dragging",
"notification:default",
"os:allow-platform",
"dialog:default",
"dialog:allow-open",
"dialog:allow-ask",
"dialog:allow-save",
"dialog:allow-confirm",
"dialog:allow-message",
{
"identifier": "shell:allow-spawn",
"allow": [
@@ -64,9 +68,6 @@
"fs:allow-rename",
"fs:allow-mkdir",
"fs:allow-remove",
"fs:allow-stat",
"fs:allow-fstat",
"fs:allow-lstat",
"fs:allow-write-text-file",
"fs:read-meta",
"fs:scope-download-recursive",
@@ -74,9 +75,6 @@
{
"identifier": "fs:scope-appdata-recursive",
"allow": [
{
"path": "$APPDATA/db/"
},
{
"path": "$APPDATA/db/**"
}
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
}
android {
compileSdk = 36
compileSdk = 34
namespace = "com.tauri.api"
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.api"
minSdk = 24
targetSdk = 36
targetSdk = 34
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
}
@@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.0")
classpath("com.android.tools.build:gradle:8.5.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
}
}
@@ -18,6 +18,6 @@ repositories {
dependencies {
compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.11.0")
implementation("com.android.tools.build:gradle:8.5.1")
}
+56 -94
View File
@@ -8,8 +8,6 @@
let filter = null;
let multiple = false;
let directory = false;
let pickerMode = "document";
let fileAccessMode = "scoped";
function arrayBufferToBase64(buffer, callback) {
var blob = new Blob([buffer], {
@@ -43,70 +41,56 @@
}
async function msg() {
await message("Tauri is awesome!").then((res) => onMessage(res));
await message("Tauri is awesome!");
}
async function msgCustom(result) {
const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
await message(`Tauri is: `, { buttons })
.then((res) => onMessage(`Tauri is ${res}`))
.catch(onMessage);
}
async function openDialog() {
try {
var result = await open({
title: "My wonderful open dialog",
defaultPath,
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
pickerMode,
fileAccessMode,
})
if (Array.isArray(result)) {
onMessage(result);
} else {
var pathToRead = result;
var isFile = pathToRead.match(/\S+\.\S+$/g);
await readFile(pathToRead)
.then(function (res) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(res),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
function openDialog() {
open({
title: "My wonderful open dialog",
defaultPath,
filters: filter
? [
{
name: "Tauri Example",
extensions: filter.split(",").map((f) => f.trim()),
},
]
: [],
multiple,
directory,
})
.then(function (res) {
if (Array.isArray(res)) {
onMessage(res);
} else {
var pathToRead = res;
var isFile = pathToRead.match(/\S+\.\S+$/g);
readFile(pathToRead)
.then(function (response) {
if (isFile) {
if (
pathToRead.includes(".png") ||
pathToRead.includes(".jpg") ||
pathToRead.includes(".jpeg")
) {
arrayBufferToBase64(
new Uint8Array(response),
function (base64) {
var src = "data:image/png;base64," + base64;
insecureRenderHtml('<img src="' + src + '"></img>');
}
);
} else {
onMessage(res);
}
} else {
// Convert byte array to UTF-8 string
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(new Uint8Array(res));
onMessage(text);
onMessage(res);
}
} else {
onMessage(res);
}
})
}
} catch(exception) {
onMessage(exception)
}
})
.catch(onMessage(res));
}
})
.catch(onMessage);
}
function saveDialog() {
@@ -121,7 +105,7 @@
},
]
: [],
})
})
.then(onMessage)
.catch(onMessage);
}
@@ -151,35 +135,13 @@
<input type="checkbox" id="dialog-directory" bind:checked={directory} />
<label for="dialog-directory">Directory</label>
</div>
<div>
<label for="dialog-picker-mode">Picker Mode:</label>
<select id="dialog-picker-mode" bind:value={pickerMode}>
<option value="">None</option>
<option value="media">Media</option>
<option value="image">Image</option>
<option value="video">Video</option>
<option value="document">Document</option>
</select>
</div>
<div>
<label for="dialog-file-access-mode">File Access Mode:</label>
<select id="dialog-file-access-mode" bind:value={fileAccessMode}>
<option value="copy">Copy</option>
<option value="scoped">Scoped</option>
</select>
</div>
<br />
<div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog}
>Open save dialog</button
>
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
>Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
<button class="btn" id="message-dialog" on:click={msgCustom}>Message (custom)</button>
</div>
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog}
>Open save dialog</button
>
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
>Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
+2 -27
View File
@@ -1,9 +1,8 @@
<script>
import * as fs from '@tauri-apps/plugin-fs'
import * as os from '@tauri-apps/plugin-os'
import { convertFileSrc } from '@tauri-apps/api/core'
import { arrayBufferToBase64 } from '../lib/utils'
import { onDestroy, onMount } from 'svelte'
import { onDestroy } from 'svelte'
const { onMessage, insecureRenderHtml } = $props()
@@ -19,12 +18,6 @@
let baseDir = $state()
let unwatchFn
let unwatchPath = ''
let isMobile = $state(false)
onMount(() => {
let platform = os.platform()
isMobile = platform === 'android' || platform === 'ios'
})
const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
isNaN(parseInt(key))
@@ -45,7 +38,7 @@
}
function mkdir() {
fs.mkdir(path, { baseDir, recursive: true })
fs.mkdir(path, { baseDir })
.then(() => {
onMessage(`Created dir ${path}`)
})
@@ -80,16 +73,6 @@
.catch(onMessage)
}
function write() {
const encoder = new TextEncoder()
file
.write(encoder.encode('Hello from Tauri :)'))
.then(() => {
onMessage(`wrote to file`)
})
.catch(onMessage)
}
function stat() {
file
.stat()
@@ -197,13 +180,6 @@
</script>
<div class="flex flex-col">
{#if isMobile}
<div>
On mobile, paths outside of App* paths require the use of dialogs
regardless of Tauri's scope mechanism.
</div>
<br />
{/if}
<div class="flex gap-1">
<select class="input" bind:value={baseDir}>
<option value={undefined} selected>None</option>
@@ -231,7 +207,6 @@
</div>
{#if file}
<div>
<button class="btn" onclick={write}>Write</button>
<button class="btn" onclick={truncate}>Truncate</button>
<button class="btn" onclick={stat}>Stat</button>
</div>
+4 -14
View File
@@ -1,21 +1,16 @@
<script>
import { sendNotification } from '@tauri-apps/plugin-notification'
export let onMessage
let sound = ''
// send the notification directly
// the backend is responsible for checking the permission
function _sendNotification() {
sendNotification({
title: 'Notification title',
body: 'This is the notification body',
sound: sound || null
new Notification('Notification title', {
body: 'This is the notification body'
})
}
// alternatively, check the permission ourselves
function triggerNotification() {
function sendNotification() {
if (Notification.permission === 'default') {
Notification.requestPermission()
.then(function (response) {
@@ -34,11 +29,6 @@
}
</script>
<input
class="input grow"
placeholder="Notification sound..."
bind:value={sound}
/>
<button class="btn" id="notification" on:click={triggerNotification}>
<button class="btn" id="notification" on:click={sendNotification}>
Send test notification
</button>
+3 -44
View File
@@ -1,5 +1,5 @@
<script>
import { download, upload, HttpMethod } from '@tauri-apps/plugin-upload'
import { download, upload } from '@tauri-apps/plugin-upload'
import { open } from '@tauri-apps/plugin-dialog'
import { JsonView } from '@zerodevx/svelte-json-view'
import { appDataDir } from '@tauri-apps/api/path'
@@ -16,22 +16,6 @@
let uploadUrl = 'https://httpbin.org/post'
let uploadFilePath = ''
let uploadMethod = HttpMethod.Post
// Update URL when method changes
$: {
switch (uploadMethod) {
case HttpMethod.Post:
uploadUrl = 'https://httpbin.org/post'
break
case HttpMethod.Put:
uploadUrl = 'https://httpbin.org/put'
break
case HttpMethod.Patch:
uploadUrl = 'https://httpbin.org/patch'
break
}
}
let uploadProgress = null
let uploadResult = null
let isUploading = false
@@ -213,8 +197,7 @@
},
new Map([
['User-Agent', 'Tauri Upload Plugin Demo']
]),
uploadMethod
])
)
uploadResult = {
@@ -357,36 +340,12 @@
</div>
</div>
<div>
<label for="upload-method" class="block text-sm font-medium text-gray-700 mb-1">HTTP Method:</label>
<select
id="upload-method"
bind:value={uploadMethod}
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
disabled={isUploading}
>
<option value={HttpMethod.Post}>POST</option>
<option value={HttpMethod.Put}>PUT</option>
<option value={HttpMethod.Patch}>PATCH</option>
</select>
<p class="text-xs text-gray-500 mt-1">Choose the HTTP method for the upload request</p>
</div>
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
<div class="text-sm text-blue-800">
<strong>Upload Configuration:</strong>
<div class="font-mono text-xs mt-1">
Method: {uploadMethod} | URL: {uploadUrl || 'Not set'}
</div>
</div>
</div>
<button
on:click={startUpload}
class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isUploading || !uploadUrl || !uploadFilePath}
>
{isUploading ? `Uploading (${uploadMethod})...` : `Upload File (${uploadMethod})`}
{isUploading ? 'Uploading...' : 'Upload File'}
</button>
{#if uploadProgress}
+12 -12
View File
@@ -11,20 +11,20 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
},
"devDependencies": {
"@eslint/js": "10.0.1",
"@rollup/plugin-node-resolve": "16.0.3",
"@rollup/plugin-terser": "1.0.0",
"@rollup/plugin-typescript": "12.3.0",
"eslint": "10.1.0",
"@eslint/js": "9.32.0",
"@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "12.1.4",
"covector": "^0.12.4",
"eslint": "9.32.0",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "4.0.0",
"prettier": "3.8.1",
"rollup": "4.59.0",
"eslint-plugin-security": "3.0.1",
"prettier": "3.6.2",
"rollup": "4.46.2",
"tslib": "2.8.1",
"typescript": "5.9.3",
"typescript-eslint": "8.57.1"
"typescript": "5.9.2",
"typescript-eslint": "8.39.0"
},
"minimumReleaseAge": 4320,
"pnpm": {
"overrides": {
"esbuild@<0.25.0": ">=0.25.0"
@@ -34,6 +34,6 @@
]
},
"engines": {
"pnpm": "^10.16.0"
"pnpm": "^10.0.0"
}
}
-4
View File
@@ -1,9 +1,5 @@
# Changelog
## \[2.5.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.5.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+5 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-autostart"
version = "2.5.1"
version = "2.5.0"
description = "Automatically launch your application at startup."
authors = { workspace = true }
license = { workspace = true }
@@ -9,6 +9,10 @@ rust-version = { workspace = true }
repository = { workspace = true }
links = "tauri-plugin-autostart"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support]
windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" }
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-autostart",
"version": "2.5.1",
"version": "2.5.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-20
View File
@@ -1,25 +1,5 @@
# Changelog
## \[2.4.4]
- [`82fbb0c7`](https://github.com/tauri-apps/plugins-workspace/commit/82fbb0c790288eca72af9ade13828ded7700ff90) ([#3221](https://github.com/tauri-apps/plugins-workspace/pull/3221)) On iOS, fixed an application crash happening when the scanner was started when user denied permission before.
- [`b60dd887`](https://github.com/tauri-apps/plugins-workspace/commit/b60dd88702a2be2af942a3d104d728970a4c42d6) ([#3223](https://github.com/tauri-apps/plugins-workspace/pull/3223)) On iOS, start the scanning session on a separate thread to fix performance issues.
- [`c27af912`](https://github.com/tauri-apps/plugins-workspace/commit/c27af9128d6cc7a2424ec49e98005e68b2d0eb1a) ([#3222](https://github.com/tauri-apps/plugins-workspace/pull/3222)) On iOS, fixed an application crash happening when the scanner was started on the iOS Simulator (no camera available).
## \[2.4.3]
- [`d8bfe61f`](https://github.com/tauri-apps/plugins-workspace/commit/d8bfe61f20f235314bad93a9c50d8b7f3eade734) ([#3121](https://github.com/tauri-apps/plugins-workspace/pull/3121) by [@NKIPSC](https://github.com/tauri-apps/plugins-workspace/../../NKIPSC)) Remove unnecessary checks on Android when requesting camera permission.
- [`631d0e25`](https://github.com/tauri-apps/plugins-workspace/commit/631d0e256a37946b6a9102ca35511abfbebb92c5) ([#2440](https://github.com/tauri-apps/plugins-workspace/pull/2440) by [@kingsword09](https://github.com/tauri-apps/plugins-workspace/../../kingsword09)) Fix the `cameraView` is not removed after scanning in iOS.
## \[2.4.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.1]
- [`654bf489`](https://github.com/tauri-apps/plugins-workspace/commit/654bf4891a35769f7e82971641d3ad99974b2dfe) ([#3038](https://github.com/tauri-apps/plugins-workspace/pull/3038) by [@daniel-mader](https://github.com/tauri-apps/plugins-workspace/../../daniel-mader)) Update `androidx.camera` from `1.1.0` to `1.5.1` to support 16 KB memory page sizes.
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.4.0]
- [`aa9140e1`](https://github.com/tauri-apps/plugins-workspace/commit/aa9140e1ac239ab9f015f92b2ed52bbf0eda7c12) ([#2437](https://github.com/tauri-apps/plugins-workspace/pull/2437) by [@enkhjile](https://github.com/tauri-apps/plugins-workspace/../../enkhjile)) Added support for GS1 DataBar on iOS 15.4+
+3 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-barcode-scanner"
version = "2.4.4"
version = "2.4.0"
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
edition = { workspace = true }
authors = { workspace = true }
@@ -10,6 +10,8 @@ repository = { workspace = true }
links = "tauri-plugin-barcode-scanner"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"]
[package.metadata.platforms.support]
@@ -5,10 +5,10 @@ plugins {
android {
namespace = "app.tauri.barcodescanner"
compileSdk = 36
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
@@ -33,14 +33,16 @@ android {
}
dependencies {
val camerax_version = "1.5.1"
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
implementation("com.google.android.material:material:1.7.0")
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
implementation("androidx.camera:camera-view:${camerax_version}")
implementation("androidx.camera:camera-core:1.1.0")
implementation("androidx.camera:camera-view:1.1.0")
implementation("androidx.camera:camera-lifecycle:1.1.0")
implementation("androidx.camera:camera-camera2:1.1.0")
implementation("androidx.camera:camera-lifecycle:1.1.0")
implementation("androidx.camera:camera-view:1.1.0")
implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
@@ -54,6 +54,7 @@ import java.util.concurrent.ExecutionException
private const val PERMISSION_ALIAS_CAMERA = "camera"
private const val PERMISSION_NAME = Manifest.permission.CAMERA
private const val PREFS_PERMISSION_FIRST_TIME_ASKING = "PREFS_PERMISSION_FIRST_TIME_ASKING"
@InvokeArg
class ScanOptions {
@@ -353,6 +354,17 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
}
}
private fun markFirstPermissionRequest() {
val sharedPreference: SharedPreferences =
activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
sharedPreference.edit().putBoolean(PERMISSION_NAME, false).apply()
}
private fun firstPermissionRequest(): Boolean {
return activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
.getBoolean(PERMISSION_NAME, true)
}
@SuppressLint("ObsoleteSdkInt")
@PermissionCallback
fun cameraPermissionCallback(invoke: Invoke) {
@@ -368,7 +380,9 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
if (!activity.shouldShowRequestPermissionRationale(PERMISSION_NAME)) {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
}
} else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
}
@@ -387,12 +401,20 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissionForAlias(
PERMISSION_ALIAS_CAMERA,
invoke,
"cameraPermissionCallback"
)
return
if (firstPermissionRequest() || activity.shouldShowRequestPermissionRationale(
PERMISSION_NAME
)
) {
markFirstPermissionRequest()
requestPermissionForAlias(
PERMISSION_ALIAS_CAMERA,
invoke,
"cameraPermissionCallback"
)
return
} else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
}
} else {
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
}
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_BARCODE_SCANNER__=function(n){"use strict";async function a(n,a={},e){return window.__TAURI_INTERNALS__.invoke(n,a,e)}var e;return"function"==typeof SuppressedError&&SuppressedError,n.Format=void 0,(e=n.Format||(n.Format={})).QRCode="QR_CODE",e.UPC_A="UPC_A",e.UPC_E="UPC_E",e.EAN8="EAN_8",e.EAN13="EAN_13",e.Code39="CODE_39",e.Code93="CODE_93",e.Code128="CODE_128",e.Codabar="CODABAR",e.ITF="ITF",e.Aztec="AZTEC",e.DataMatrix="DATA_MATRIX",e.PDF417="PDF_417",e.GS1DataBar="GS1_DATA_BAR",e.GS1DataBarLimited="GS1_DATA_BAR_LIMITED",e.GS1DataBarExpanded="GS1_DATA_BAR_EXPANDED",n.cancel=async function(){await a("plugin:barcode-scanner|cancel")},n.checkPermissions=async function(){return await async function(n){return a(`plugin:${n}|check_permissions`)}("barcode-scanner").then(n=>n.camera)},n.openAppSettings=async function(){await a("plugin:barcode-scanner|open_app_settings")},n.requestPermissions=async function(){return await async function(n){return a(`plugin:${n}|request_permissions`)}("barcode-scanner").then(n=>n.camera)},n.scan=async function(n){return await a("plugin:barcode-scanner|scan",{...n})},n}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_PLUGIN_BARCODE_SCANNER__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_BARCODE_SCANNER__=function(n){"use strict";async function a(n,a={},e){return window.__TAURI_INTERNALS__.invoke(n,a,e)}var e;return"function"==typeof SuppressedError&&SuppressedError,n.Format=void 0,(e=n.Format||(n.Format={})).QRCode="QR_CODE",e.UPC_A="UPC_A",e.UPC_E="UPC_E",e.EAN8="EAN_8",e.EAN13="EAN_13",e.Code39="CODE_39",e.Code93="CODE_93",e.Code128="CODE_128",e.Codabar="CODABAR",e.ITF="ITF",e.Aztec="AZTEC",e.DataMatrix="DATA_MATRIX",e.PDF417="PDF_417",e.GS1DataBar="GS1_DATA_BAR",e.GS1DataBarLimited="GS1_DATA_BAR_LIMITED",e.GS1DataBarExpanded="GS1_DATA_BAR_EXPANDED",n.cancel=async function(){await a("plugin:barcode-scanner|cancel")},n.checkPermissions=async function(){return await async function(n){return a(`plugin:${n}|check_permissions`)}("barcode-scanner").then((n=>n.camera))},n.openAppSettings=async function(){await a("plugin:barcode-scanner|open_app_settings")},n.requestPermissions=async function(){return await async function(n){return a(`plugin:${n}|request_permissions`)}("barcode-scanner").then((n=>n.camera))},n.scan=async function(n){return await a("plugin:barcode-scanner|scan",{...n})},n}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_PLUGIN_BARCODE_SCANNER__})}
@@ -191,7 +191,6 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
if self.captureSession != nil {
self.captureSession!.stopRunning()
self.cameraView.removePreviewLayer()
self.cameraView.removeFromSuperview()
self.captureVideoPreviewLayer = nil
self.metaOutput = nil
self.captureSession = nil
@@ -261,34 +260,27 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
}
private func runScanner(_ invoke: Invoke, args: ScanOptions) {
if getPermissionState() != "granted" {
invoke.reject("Camera permission denied or not yet requested")
return
}
scanFormats = [AVMetadataObject.ObjectType]()
(args.formats ?? []).forEach { format in
if let formatValue = format.value {
scanFormats.append(formatValue)
} else {
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
return
}
if let formatValue = format.value {
scanFormats.append(formatValue)
} else {
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
return
}
}
if scanFormats.isEmpty {
for supportedFormat in SupportedFormat.allCases {
if let formatValue = supportedFormat.value {
scanFormats.append(formatValue)
for supportedFormat in SupportedFormat.allCases {
if let formatValue = supportedFormat.value {
scanFormats.append(formatValue)
}
}
}
}
self.metaOutput!.metadataObjectTypes = self.scanFormats
DispatchQueue.main.async {
self.captureSession!.startRunning()
}
self.captureSession!.startRunning()
self.isScanning = true
}
@@ -305,13 +297,6 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
return
}
// Check if camera is available on this platform (iOS simulator doesn't have cameras)
let availableVideoDevices = discoverCaptureDevices()
if availableVideoDevices.isEmpty {
invoke.reject("No camera available on this device (e.g., iOS Simulator)")
return
}
var iOS14min: Bool = false
if #available(iOS 14.0, *) { iOS14min = true }
if !iOS14min && self.getPermissionState() != "granted" {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-barcode-scanner",
"version": "2.4.4",
"version": "2.4.0",
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-8
View File
@@ -1,13 +1,5 @@
# Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+3 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-biometric"
version = "2.3.2"
version = "2.3.0"
description = "Prompt the user for biometric authentication on Android and iOS."
edition = { workspace = true }
authors = { workspace = true }
@@ -9,6 +9,8 @@ repository = { workspace = true }
links = "tauri-plugin-biometric"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"]
[package.metadata.platforms.support]
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android {
namespace = "app.tauri.biometric"
compileSdk = 36
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-biometric",
"version": "2.3.2",
"version": "2.3.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -25,6 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-4
View File
@@ -1,9 +1,5 @@
# Changelog
## \[2.4.1]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+5 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-cli"
version = "2.4.1"
version = "2.4.0"
description = "Parse arguments from your Tauri application's command line interface."
edition = { workspace = true }
authors = { workspace = true }
@@ -9,6 +9,10 @@ rust-version = { workspace = true }
repository = { workspace = true }
links = "tauri-plugin-cli"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.platforms.support]
windows = { level = "full", notes = "" }
linux = { level = "full", notes = "" }
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-cli",
"version": "2.4.1",
"version": "2.4.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-8
View File
@@ -1,13 +1,5 @@
# Changelog
## \[2.3.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.3.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+3 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-clipboard-manager"
version = "2.3.2"
version = "2.3.0"
description = "Read and write to the system clipboard."
edition = { workspace = true }
authors = { workspace = true }
@@ -10,7 +10,8 @@ repository = { workspace = true }
links = "tauri-plugin-clipboard-manager"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
[package.metadata.platforms.support]
@@ -5,10 +5,10 @@ plugins {
android {
namespace = "app.tauri.clipboard"
compileSdk = 36
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARD_MANAGER__=function(e){"use strict";var n;async function t(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}"function"==typeof SuppressedError&&SuppressedError;class r{get rid(){return function(e,n,t,r){if("function"==typeof n?e!==n||!r:!n.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?r:"a"===t?r.call(e):r?r.value:n.get(e)}(this,n,"f")}constructor(e){n.set(this,void 0),function(e,n,t){if("function"==typeof n||!n.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");n.set(e,t)}(this,n,e)}async close(){return t("plugin:resources|close",{rid:this.rid})}}n=new WeakMap;class a extends r{constructor(e){super(e)}static async new(e,n,r){return t("plugin:image|new",{rgba:i(e),width:n,height:r}).then(e=>new a(e))}static async fromBytes(e){return t("plugin:image|from_bytes",{bytes:i(e)}).then(e=>new a(e))}static async fromPath(e){return t("plugin:image|from_path",{path:e}).then(e=>new a(e))}async rgba(){return t("plugin:image|rgba",{rid:this.rid}).then(e=>new Uint8Array(e))}async size(){return t("plugin:image|size",{rid:this.rid})}}function i(e){return null==e?null:"string"==typeof e?e:e instanceof a?e.rid:e}return e.clear=async function(){await t("plugin:clipboard-manager|clear")},e.readImage=async function(){return await t("plugin:clipboard-manager|read_image").then(e=>new a(e))},e.readText=async function(){return await t("plugin:clipboard-manager|read_text")},e.writeHtml=async function(e,n){await t("plugin:clipboard-manager|write_html",{html:e,altText:n})},e.writeImage=async function(e){await t("plugin:clipboard-manager|write_image",{image:i(e)})},e.writeText=async function(e,n){await t("plugin:clipboard-manager|write_text",{label:n?.label,text:e})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARD_MANAGER__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_CLIPBOARD_MANAGER__=function(e){"use strict";var n;async function t(e,n={},t){return window.__TAURI_INTERNALS__.invoke(e,n,t)}"function"==typeof SuppressedError&&SuppressedError;class r{get rid(){return function(e,n,t,r){if("function"==typeof n?e!==n||!r:!n.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===t?r:"a"===t?r.call(e):r?r.value:n.get(e)}(this,n,"f")}constructor(e){n.set(this,void 0),function(e,n,t){if("function"==typeof n||!n.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");n.set(e,t)}(this,n,e)}async close(){return t("plugin:resources|close",{rid:this.rid})}}n=new WeakMap;class a extends r{constructor(e){super(e)}static async new(e,n,r){return t("plugin:image|new",{rgba:i(e),width:n,height:r}).then((e=>new a(e)))}static async fromBytes(e){return t("plugin:image|from_bytes",{bytes:i(e)}).then((e=>new a(e)))}static async fromPath(e){return t("plugin:image|from_path",{path:e}).then((e=>new a(e)))}async rgba(){return t("plugin:image|rgba",{rid:this.rid}).then((e=>new Uint8Array(e)))}async size(){return t("plugin:image|size",{rid:this.rid})}}function i(e){return null==e?null:"string"==typeof e?e:e instanceof a?e.rid:e}return e.clear=async function(){await t("plugin:clipboard-manager|clear")},e.readImage=async function(){return await t("plugin:clipboard-manager|read_image").then((e=>new a(e)))},e.readText=async function(){return await t("plugin:clipboard-manager|read_text")},e.writeHtml=async function(e,n){await t("plugin:clipboard-manager|write_html",{html:e,altText:n})},e.writeImage=async function(e){await t("plugin:clipboard-manager|write_image",{image:i(e)})},e.writeText=async function(e,n){await t("plugin:clipboard-manager|write_text",{label:n?.label,text:e})},e}({});Object.defineProperty(window.__TAURI__,"clipboardManager",{value:__TAURI_PLUGIN_CLIPBOARD_MANAGER__})}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-clipboard-manager",
"version": "2.3.2",
"version": "2.3.0",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-30
View File
@@ -1,35 +1,5 @@
# Changelog
## \[2.4.8]
- [`024ec0c2`](https://github.com/tauri-apps/plugins-workspace/commit/024ec0c29c20cf94579dab9b79d6be0da61a8daa) ([#3214](https://github.com/tauri-apps/plugins-workspace/pull/3214) by [@joshIsCoding](https://github.com/tauri-apps/plugins-workspace/../../joshIsCoding)) Account for differing Android VIEW intent in ChromeOS, fixing deep-link behaviour on Chromium platforms.
- [`015e817c`](https://github.com/tauri-apps/plugins-workspace/commit/015e817cf2d7f66c1b9268606af8318dfe0bc4ee) ([#3186](https://github.com/tauri-apps/plugins-workspace/pull/3186) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Validate Android new intent is actually a deep link before triggering the onOpenUrl event.
## \[2.4.7]
- [`8374e997`](https://github.com/tauri-apps/plugins-workspace/commit/8374e997b82c95516fc0c1f6d665d9fc3b52edf8) ([#3258](https://github.com/tauri-apps/plugins-workspace/pull/3258) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Fix runtime deep link registration failing on Linux when the app path has spaces.
## \[2.4.6]
- [`28048039`](https://github.com/tauri-apps/plugins-workspace/commit/28048039496e84b46847c008416d341f1349e30e) ([#3143](https://github.com/tauri-apps/plugins-workspace/pull/3143) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Fix clippy warnings. No user facing changes.
## \[2.4.5]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
## \[2.4.4]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
- [`e25a9339`](https://github.com/tauri-apps/plugins-workspace/commit/e25a9339f46268f70dc9afd0c5ab3decbf79b330) ([#3019](https://github.com/tauri-apps/plugins-workspace/pull/3019) by [@kevinschoonover](https://github.com/tauri-apps/plugins-workspace/../../kevinschoonover)) Fix Exec= field in desktop handler if executable path changes
## \[2.4.3]
- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928).
## \[2.4.2]
- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail.
## \[2.4.1]
- [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux
+3 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-deep-link"
version = "2.4.8"
version = "2.4.1"
description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true }
license = { workspace = true }
@@ -10,6 +10,8 @@ repository = { workspace = true }
links = "tauri-plugin-deep-link"
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"]
[package.metadata.platforms.support]
@@ -25,9 +27,6 @@ serde_json = { workspace = true }
tauri-utils = { workspace = true }
tauri-plugin = { workspace = true, features = ["build"] }
[target."cfg(target_os = \"macos\")".build-dependencies]
plist = "1"
[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android {
namespace = "app.tauri.deep_link"
compileSdk = 36
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
@@ -6,8 +6,9 @@ package app.tauri.deep_link
import android.app.Activity
import android.content.Intent
import android.os.PatternMatcher
import android.os.Bundle
import android.webkit.WebView
import app.tauri.Logger
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Command
import app.tauri.annotation.TauriPlugin
@@ -15,43 +16,23 @@ import app.tauri.plugin.Channel
import app.tauri.plugin.JSObject
import app.tauri.plugin.Plugin
import app.tauri.plugin.Invoke
import androidx.core.net.toUri
@InvokeArg
class SetEventHandlerArgs {
lateinit var handler: Channel
}
@InvokeArg
class AssociatedDomain {
var scheme: List<String> = listOf("https", "http")
var host: String? = null
var path: List<String> = listOf()
var pathPattern: List<String> = listOf()
var pathPrefix: List<String> = listOf()
var pathSuffix: List<String> = listOf()
}
@InvokeArg
class PluginConfig {
var mobile: List<AssociatedDomain> = listOf()
}
@TauriPlugin
class DeepLinkPlugin(private val activity: Activity): Plugin(activity) {
//private val implementation = Example()
private var webView: WebView? = null
private var currentUrl: String? = null
private var channel: Channel? = null
private var config: PluginConfig? = null
companion object {
var instance: DeepLinkPlugin? = null
}
private fun isViewIntent(action: String?): Boolean {
return action == Intent.ACTION_VIEW || action == "org.chromium.arc.intent.action.VIEW"
}
@Command
fun getCurrent(invoke: Invoke) {
val ret = JSObject()
@@ -70,105 +51,27 @@ class DeepLinkPlugin(private val activity: Activity): Plugin(activity) {
override fun load(webView: WebView) {
instance = this
config = getConfig(PluginConfig::class.java)
super.load(webView)
this.webView = webView
val intent = activity.intent
if (isViewIntent(intent.action) && intent.data != null) {
val url = intent.data.toString()
if (isDeepLink(url)) {
// TODO: check if it makes sense to split up init url and last url
this.currentUrl = url
val event = JSObject()
event.put("url", this.currentUrl)
this.channel?.send(event)
}
if (intent.action == Intent.ACTION_VIEW) {
// TODO: check if it makes sense to split up init url and last url
this.currentUrl = intent.data.toString()
val event = JSObject()
event.put("url", this.currentUrl)
this.channel?.send(event)
}
super.load(webView)
this.webView = webView
}
override fun onNewIntent(intent: Intent) {
if (isViewIntent(intent.action) && intent.data != null) {
val url = intent.data.toString()
if (isDeepLink(url)) {
this.currentUrl = url
val event = JSObject()
event.put("url", this.currentUrl)
this.channel?.send(event)
}
if (intent.action == Intent.ACTION_VIEW) {
this.currentUrl = intent.data.toString()
val event = JSObject()
event.put("url", this.currentUrl)
this.channel?.send(event)
}
}
private fun isDeepLink(url: String): Boolean {
val config = this.config ?: return false
if (config.mobile.isEmpty()) {
return false
}
val uri = try {
url.toUri()
} catch (_: Exception) {
// not a URL
return false
}
val scheme = uri.scheme ?: return false
val host = uri.host
val path = uri.path ?: ""
// Check if URL matches any configured mobile deep link
for (domain in config.mobile) {
// Check scheme
if (!domain.scheme.any { it.equals(scheme, ignoreCase = true) }) {
continue
}
// Check host (if configured)
if (domain.host != null) {
if (!host.equals(domain.host, ignoreCase = true)) {
continue
}
}
// Check path constraints
// According to Android docs:
// - path: exact match, must begin with /
// - pathPrefix: matches initial part of path
// - pathSuffix: matches ending part, doesn't need to begin with /
// - pathPattern: simple glob pattern (., *, .*)
val pathMatches = when {
// Exact path match (must begin with /)
domain.path.isNotEmpty() && domain.path.any { it == path } -> true
// Path pattern match (simple glob: ., *, .*)
domain.pathPattern.isNotEmpty() && domain.pathPattern.any { pattern ->
try {
PatternMatcher(pattern, PatternMatcher.PATTERN_SIMPLE_GLOB).match(path)
} catch (e: Exception) {
false
}
} -> true
// Path prefix match
domain.pathPrefix.isNotEmpty() && domain.pathPrefix.any { prefix ->
path.startsWith(prefix)
} -> true
// Path suffix match
domain.pathSuffix.isNotEmpty() && domain.pathSuffix.any { suffix ->
path.endsWith(suffix)
} -> true
// If no path constraints, any path is allowed
domain.path.isEmpty() && domain.pathPattern.isEmpty() &&
domain.pathPrefix.isEmpty() && domain.pathSuffix.isEmpty() -> true
else -> false
}
if (pathMatches) {
return true
}
}
return false
}
}
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_DEEP_LINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const _={kind:"Any"};return r("plugin:event|listen",{event:e,target:_,handler:n(t)}).then(n=>async()=>async function(e,n){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,n),await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n))}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(t||(t={})),e.getCurrent=async function(){return await r("plugin:deep-link|get_current")},e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){return await i("deep-link://new-url",n=>{e(n.payload)})},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEP_LINK__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_DEEP_LINK__=function(e){"use strict";function n(e,n=!1){return window.__TAURI_INTERNALS__.transformCallback(e,n)}async function r(e,n={},r){return window.__TAURI_INTERNALS__.invoke(e,n,r)}var t;async function i(e,t,i){const _={kind:"Any"};return r("plugin:event|listen",{event:e,target:_,handler:n(t)}).then((n=>async()=>async function(e,n){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(e,n),await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}return"function"==typeof SuppressedError&&SuppressedError,function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WINDOW_CREATED="tauri://window-created",e.WEBVIEW_CREATED="tauri://webview-created",e.DRAG_ENTER="tauri://drag-enter",e.DRAG_OVER="tauri://drag-over",e.DRAG_DROP="tauri://drag-drop",e.DRAG_LEAVE="tauri://drag-leave"}(t||(t={})),e.getCurrent=async function(){return await r("plugin:deep-link|get_current")},e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){return await i("deep-link://new-url",(n=>{e(n.payload)}))},e.register=async function(e){return await r("plugin:deep-link|register",{protocol:e})},e.unregister=async function(e){return await r("plugin:deep-link|unregister",{protocol:e})},e}({});Object.defineProperty(window.__TAURI__,"deepLink",{value:__TAURI_PLUGIN_DEEP_LINK__})}
+26 -115
View File
@@ -10,66 +10,50 @@ const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registe
// TODO: Consider using activity-alias in case users may have multiple activities in their app.
fn intent_filter(domain: &AssociatedDomain) -> String {
let host = domain
.host
.as_ref()
.map(|h| format!(r#"<data android:host="{h}" />"#))
.unwrap_or_default();
let auto_verify = if domain.is_app_link() {
r#"android:autoVerify="true" "#.to_string()
} else {
String::new()
};
format!(
r#"<intent-filter {auto_verify}>
r#"<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<!-- ChromeOS ARC++ uses a different action for deep links -->
<action android:name="org.chromium.arc.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
{schemes}
{host}
{domains}
{path_patterns}
{path_prefixes}
{path_suffixes}
{}
<data android:host="{}" />
{}
{}
{}
{}
</intent-filter>"#,
schemes = domain
domain
.scheme
.iter()
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
.collect::<Vec<_>>()
.join("\n "),
host = host,
domains = domain
domain.host,
domain
.path
.iter()
.map(|path| format!(r#"<data android:path="{path}" />"#))
.collect::<Vec<_>>()
.join("\n "),
path_patterns = domain
domain
.path_pattern
.iter()
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
.collect::<Vec<_>>()
.join("\n "),
path_prefixes = domain
domain
.path_prefix
.iter()
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
.collect::<Vec<_>>()
.join("\n "),
path_suffixes = domain
domain
.path_suffix
.iter()
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
.collect::<Vec<_>>()
.join("\n "),
)
.trim()
.to_string()
}
fn main() {
@@ -84,16 +68,6 @@ fn main() {
}
if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") {
let errors: Vec<String> = config
.mobile
.iter()
.filter_map(|d| d.validate().err())
.collect();
if !errors.is_empty() {
panic!("Deep link config validation failed:\n{}", errors.join("\n"));
}
tauri_plugin::mobile::update_android_manifest(
"DEEP LINK PLUGIN",
"activity",
@@ -106,83 +80,20 @@ fn main() {
)
.expect("failed to rewrite AndroidManifest.xml");
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[cfg(target_os = "macos")]
{
// we need to ensure that the entitlements are only
// generated for explicit app links and not
// other deep links because then they
// are just going to complain and not be built or signed
let has_app_links = config.mobile.iter().any(|d| d.is_app_link());
if !has_app_links {
tauri_plugin::mobile::update_entitlements(|entitlements| {
entitlements.remove("com.apple.developer.associated-domains");
})
.expect("failed to update entitlements");
} else {
tauri_plugin::mobile::update_entitlements(|entitlements| {
entitlements.insert(
"com.apple.developer.associated-domains".into(),
config
.mobile
.iter()
.filter(|d| d.is_app_link())
.filter_map(|d| d.host.as_ref())
.map(|host| format!("applinks:{}", host).into())
.collect::<Vec<_>>()
.into(),
);
})
.expect("failed to update entitlements");
}
let deep_link_domains = config
.mobile
.iter()
.filter(|domain| domain.is_app_link())
.collect::<Vec<_>>();
if deep_link_domains.is_empty() {
tauri_plugin::mobile::update_info_plist(|info_plist| {
info_plist.remove("CFBundleURLTypes");
})
.expect("failed to update Info.plist");
} else {
tauri_plugin::mobile::update_info_plist(|info_plist| {
info_plist.insert(
"CFBundleURLTypes".into(),
deep_link_domains
.iter()
.map(|domain| {
let schemes = domain
.scheme
.iter()
.filter(|scheme| {
scheme.as_str() != "https" && scheme.as_str() != "http"
})
.collect::<Vec<_>>();
let mut dict = plist::Dictionary::new();
dict.insert(
"CFBundleURLSchemes".into(),
schemes
.iter()
.map(|s| s.to_string().into())
.collect::<Vec<_>>()
.into(),
);
dict.insert(
"CFBundleURLName".into(),
domain.scheme[0].clone().into(),
);
plist::Value::Dictionary(dict)
})
.collect::<Vec<_>>()
.into(),
);
})
.expect("failed to update Info.plist");
}
tauri_plugin::mobile::update_entitlements(|entitlements| {
entitlements.insert(
"com.apple.developer.associated-domains".into(),
config
.mobile
.into_iter()
.map(|d| format!("applinks:{}", d.host).into())
.collect::<Vec<_>>()
.into(),
);
})
.expect("failed to update entitlements");
}
}
}
@@ -1,47 +1,5 @@
# Changelog
## \[2.2.11]
### Dependencies
- Upgraded to `deep-link-js@2.4.8`
## \[2.2.10]
### Dependencies
- Upgraded to `deep-link-js@2.4.7`
## \[2.2.9]
### Dependencies
- Upgraded to `deep-link-js@2.4.6`
## \[2.2.8]
### Dependencies
- Upgraded to `deep-link-js@2.4.5`
## \[2.2.7]
### Dependencies
- Upgraded to `deep-link-js@2.4.4`
## \[2.2.6]
### Dependencies
- Upgraded to `deep-link-js@2.4.3`
## \[2.2.5]
### Dependencies
- Upgraded to `deep-link-js@2.4.2`
## \[2.2.4]
### Dependencies
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "deep-link-example",
"private": true,
"version": "2.2.11",
"version": "2.2.4",
"type": "module",
"scripts": {
"dev": "vite",
@@ -10,12 +10,12 @@
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-deep-link": "2.4.8"
"@tauri-apps/api": "2.7.0",
"@tauri-apps/plugin-deep-link": "2.4.1"
},
"devDependencies": {
"@tauri-apps/cli": "2.10.1",
"typescript": "^5.9.3",
"vite": "^8.0.1"
"@tauri-apps/cli": "2.7.1",
"typescript": "^5.7.3",
"vite": "^7.0.4"
}
}
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
}
android {
compileSdk = 36
compileSdk = 34
namespace = "com.tauri.deep_link_example"
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.deep_link_example"
minSdk = 24
targetSdk = 36
targetSdk = 34
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
}
@@ -58,10 +58,9 @@ rust {
}
dependencies {
implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
implementation("androidx.webkit:webkit:1.6.1")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.8.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
@@ -23,40 +23,23 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
<intent-filter android:autoVerify="true" >
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="fabianlars.de" />
<data android:pathPrefix="/intent" />
</intent-filter>
<intent-filter android:autoVerify="true" >
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="tauri.app" />
</intent-filter>
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="taurideeplink" />
</intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
</activity>
@@ -1,11 +1,7 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
package com.tauri.deep_link_example
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
}
}
class MainActivity : TauriActivity()
@@ -4,7 +4,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.11.0")
classpath("com.android.tools.build:gradle:8.5.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
}
}
@@ -18,6 +18,6 @@ repositories {
dependencies {
compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.11.0")
implementation("com.android.tools.build:gradle:8.5.1")
}
@@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
@@ -1,116 +1,116 @@
{
"images" : [
"images": [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "AppIcon-20x20@2x.png",
"scale" : "2x"
"size": "20x20",
"idiom": "iphone",
"filename": "AppIcon-20x20@2x.png",
"scale": "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "AppIcon-20x20@3x.png",
"scale" : "3x"
"size": "20x20",
"idiom": "iphone",
"filename": "AppIcon-20x20@3x.png",
"scale": "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29x29@2x-1.png",
"scale" : "2x"
"size": "29x29",
"idiom": "iphone",
"filename": "AppIcon-29x29@2x-1.png",
"scale": "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "AppIcon-29x29@3x.png",
"scale" : "3x"
"size": "29x29",
"idiom": "iphone",
"filename": "AppIcon-29x29@3x.png",
"scale": "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-40x40@2x.png",
"scale" : "2x"
"size": "40x40",
"idiom": "iphone",
"filename": "AppIcon-40x40@2x.png",
"scale": "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "AppIcon-40x40@3x.png",
"scale" : "3x"
"size": "40x40",
"idiom": "iphone",
"filename": "AppIcon-40x40@3x.png",
"scale": "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60x60@2x.png",
"scale" : "2x"
"size": "60x60",
"idiom": "iphone",
"filename": "AppIcon-60x60@2x.png",
"scale": "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "AppIcon-60x60@3x.png",
"scale" : "3x"
"size": "60x60",
"idiom": "iphone",
"filename": "AppIcon-60x60@3x.png",
"scale": "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "AppIcon-20x20@1x.png",
"scale" : "1x"
"size": "20x20",
"idiom": "ipad",
"filename": "AppIcon-20x20@1x.png",
"scale": "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "AppIcon-20x20@2x-1.png",
"scale" : "2x"
"size": "20x20",
"idiom": "ipad",
"filename": "AppIcon-20x20@2x-1.png",
"scale": "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "AppIcon-29x29@1x.png",
"scale" : "1x"
"size": "29x29",
"idiom": "ipad",
"filename": "AppIcon-29x29@1x.png",
"scale": "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "AppIcon-29x29@2x.png",
"scale" : "2x"
"size": "29x29",
"idiom": "ipad",
"filename": "AppIcon-29x29@2x.png",
"scale": "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "AppIcon-40x40@1x.png",
"scale" : "1x"
"size": "40x40",
"idiom": "ipad",
"filename": "AppIcon-40x40@1x.png",
"scale": "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "AppIcon-40x40@2x-1.png",
"scale" : "2x"
"size": "40x40",
"idiom": "ipad",
"filename": "AppIcon-40x40@2x-1.png",
"scale": "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon-76x76@1x.png",
"scale" : "1x"
"size": "76x76",
"idiom": "ipad",
"filename": "AppIcon-76x76@1x.png",
"scale": "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "AppIcon-76x76@2x.png",
"scale" : "2x"
"size": "76x76",
"idiom": "ipad",
"filename": "AppIcon-76x76@2x.png",
"scale": "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "AppIcon-83.5x83.5@2x.png",
"scale" : "2x"
"size": "83.5x83.5",
"idiom": "ipad",
"filename": "AppIcon-83.5x83.5@2x.png",
"scale": "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "AppIcon-512@2x.png",
"scale" : "1x"
"size": "1024x1024",
"idiom": "ios-marketing",
"filename": "AppIcon-512@2x.png",
"scale": "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"info": {
"version": 1,
"author": "xcode"
}
}
}
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"info": {
"version": 1,
"author": "xcode"
}
}
}
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17122"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 63;
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
@@ -167,8 +167,6 @@
dependencies = (
);
name = "deep-link-example_iOS";
packageProductDependencies = (
);
productName = "deep-link-example_iOS";
productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */;
productType = "com.apple.product-type.application";
@@ -191,7 +189,6 @@
en,
);
mainGroup = 1DC58B1705AA3ECC6B887FE7;
minimizedProjectReferenceProxies = 1;
projectDirPath = "";
projectRoot = "";
targets = (
@@ -230,6 +227,7 @@
outputPaths = (
"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a",
"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a",
"$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -316,13 +314,18 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = arm64;
ARCHS = (
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "Q93MBH6S2F";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = Q93MBH6S2F;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -332,6 +335,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -350,7 +360,7 @@
PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = arm64;
VALID_ARCHS = "arm64 arm64-sim";
};
name = debug;
};
@@ -414,13 +424,18 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = arm64;
ARCHS = (
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer";
DEVELOPMENT_TEAM = "Q93MBH6S2F";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = Q93MBH6S2F;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"\".\"",
@@ -430,6 +445,13 @@
"$(inherited)",
"@executable_path/Frameworks",
);
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -448,7 +470,7 @@
PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = arm64;
VALID_ARCHS = "arm64 arm64-sim";
};
name = release;
};
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.0.0</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>LSRequiresIPhoneOS</key>
@@ -40,16 +40,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>taurideeplink</string>
</array>
<key>CFBundleURLName</key>
<string>taurideeplink</string>
</dict>
</array>
</dict>
</plist>
@@ -1,3 +1,7 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: deep-link-example
options:
bundleIdPrefix: com.tauri.deep-link-example
@@ -12,6 +16,7 @@ settingGroups:
base:
PRODUCT_NAME: deep-link-example
PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example
DEVELOPMENT_TEAM: Q93MBH6S2F
targetTemplates:
app:
type: application
@@ -51,8 +56,8 @@ targets:
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
CFBundleShortVersionString: 0.1.0
CFBundleVersion: '0.1.0'
CFBundleShortVersionString: 0.0.0
CFBundleVersion: 0.0.0
entitlements:
path: deep-link-example_iOS/deep-link-example_iOS.entitlements
scheme:
@@ -62,12 +67,14 @@ targets:
settings:
base:
ENABLE_BITCODE: false
ARCHS: [arm64]
VALID_ARCHS: arm64
ARCHS: [arm64, arm64-sim]
VALID_ARCHS: arm64 arm64-sim
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64
EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64
EXCLUDED_ARCHS[sdk=iphoneos*]: arm64-sim x86_64
groups: [app]
dependencies:
- framework: libapp.a
@@ -86,3 +93,4 @@ targets:
outputFiles:
- $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a
- $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a
- $(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a
@@ -35,9 +35,6 @@
},
{
"host": "tauri.app"
},
{
"scheme": ["taurideeplink"]
}
],
"desktop": {
+2 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-deep-link",
"version": "2.4.8",
"version": "2.4.1",
"description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0",
"authors": [
@@ -25,9 +25,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
},
"devDependencies": {
"@tauri-apps/cli": "2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
+12 -46
View File
@@ -11,8 +11,10 @@ use tauri_utils::config::DeepLinkProtocol;
pub struct AssociatedDomain {
#[serde(default = "default_schemes")]
pub scheme: Vec<String>,
#[serde(default, deserialize_with = "deserialize_associated_host")]
pub host: Option<String>, // Optional custom uri schemes dont NEED a host (may have one still), but required for https/http schemes
#[serde(deserialize_with = "deserialize_associated_host")]
pub host: String,
#[serde(default)]
pub path: Vec<String>,
#[serde(default, alias = "path-pattern", rename = "pathPattern")]
@@ -21,41 +23,6 @@ pub struct AssociatedDomain {
pub path_prefix: Vec<String>,
#[serde(default, alias = "path-suffix", rename = "pathSuffix")]
pub path_suffix: Vec<String>,
#[serde(default, alias = "app-link", rename = "appLink")]
pub app_link: Option<bool>,
}
impl AssociatedDomain {
/// Returns true if the domain uses http or https scheme.
pub fn is_web_link(&self) -> bool {
self.scheme.iter().any(|s| s == "https" || s == "http")
}
/// Returns true if the domain uses http or https scheme and has proper host configuration.
pub fn is_app_link(&self) -> bool {
self.app_link
.unwrap_or_else(|| self.is_web_link() && self.host.is_some())
}
pub fn validate(&self) -> Result<(), String> {
// Rule 1: All web links require a host.
if self.is_web_link() && self.host.is_none() {
return Err("Web link requires a host".into());
}
// Rule 2: If it's an App Link, ensure http(s) and host.
if self.is_app_link() {
if !self.is_web_link() {
return Err("AppLink must be a valid web link (https/http + host)".into());
}
if self.scheme.iter().any(|s| s == "http") && !self.scheme.iter().any(|s| s == "https")
{
eprintln!("Warning: AppLink uses only 'http' — allowed on Android but not secure for production.");
}
}
Ok(())
}
}
// TODO: Consider removing this in v3
@@ -63,19 +30,18 @@ fn default_schemes() -> Vec<String> {
vec!["https".to_string(), "http".to_string()]
}
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let opt = Option::<String>::deserialize(deserializer)?;
if let Some(ref host) = opt {
if let Some((scheme, _)) = host.split_once("://") {
return Err(serde::de::Error::custom(format!(
"host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
)));
}
let host = String::deserialize(deserializer)?;
if let Some((scheme, _host)) = host.split_once("://") {
Err(serde::de::Error::custom(format!(
"host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
)))
} else {
Ok(host)
}
Ok(opt)
}
#[derive(Deserialize, Clone)]
-10
View File
@@ -28,16 +28,6 @@ pub enum Error {
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
}
// TODO(v3): change this into an error in v3,
// see <https://github.com/tauri-apps/plugins-workspace/pull/2970#issuecomment-3244660138>.
#[inline]
#[cfg(target_os = "linux")]
pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a {
move |e| {
tracing::error!("Failed to run OS command `{command}`: {e}");
}
}
impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
+6 -30
View File
@@ -254,7 +254,6 @@ mod imp {
///
/// ## Platform-specific:
///
/// - **Linux**: Needs the `xdg-mime` and `update-desktop-database` commands available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
#[cfg(windows)]
@@ -293,7 +292,6 @@ mod imp {
.unwrap_or_else(|| bin.into_os_string())
.to_string_lossy()
.to_string();
let qualified_exec = format!("\"{}\" %u", exec);
let target = self.app.path().data_dir()?.join("applications");
@@ -305,28 +303,12 @@ mod imp {
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
// it's ok to remove it - we only write to the file if it's missing
// and in that case we include old_mimes
let old_mimes = section.remove("MimeType").unwrap_or_default();
let mut change = false;
// if the mime type is not present, append it to the list
if !old_mimes.split(';').any(|mime| mime == mime_type) {
section.append("MimeType", format!("{mime_type};{old_mimes}"));
change = true;
} else {
section.insert("MimeType".to_string(), old_mimes);
}
// if the exec command doesnt match, update to the new one
let old_exec = section.remove("Exec").unwrap_or_default();
if old_exec != qualified_exec {
section.append("Exec", qualified_exec);
change = true;
} else {
section.insert("Exec".to_string(), old_exec.to_string());
}
// if any property has changed, rewrite the .desktop file
if change {
desktop_file.write_to_file(&target_file)?;
}
}
@@ -341,7 +323,7 @@ mod imp {
.product_name
.clone()
.unwrap_or_else(|| file_name.clone()),
qualified_exec = qualified_exec,
exec = exec,
mime_type = mime_type
)
.as_bytes(),
@@ -350,15 +332,11 @@ mod imp {
Command::new("update-desktop-database")
.arg(target)
.status()
.inspect_err(crate::error::inspect_command_error(
"update-desktop-database",
))?;
.status()?;
Command::new("xdg-mime")
.args(["default", &file_name, mime_type.as_str()])
.status()
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
.status()?;
Ok(())
}
@@ -427,7 +405,6 @@ mod imp {
///
/// ## Platform-specific:
///
/// - **Linux**: Needs the `xdg-mime` command available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> {
#[cfg(windows)]
@@ -462,8 +439,7 @@ mod imp {
"default",
&format!("x-scheme-handler/{}", _protocol.as_ref()),
])
.output()
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
.output()?;
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
}
+2 -2
View File
@@ -1,7 +1,7 @@
[Desktop Entry]
Type=Application
Name={name}
Exec={qualified_exec}
Exec={exec} %u
Terminal=false
MimeType={mime_type}
NoDisplay=true
NoDisplay=true
-50
View File
@@ -1,55 +1,5 @@
# Changelog
## \[2.7.0]
- [`24154472`](https://github.com/tauri-apps/plugins-workspace/commit/24154472a6710a690173df0a121125d1f1b871e8) ([#3287](https://github.com/tauri-apps/plugins-workspace/pull/3287) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Re-use `message` command in Rust side for `ask` and `confirm` commands, `allow-ask` and `allow-confirm` permissions are now aliases to `allow-message`
### Dependencies
- Upgraded to `fs-js@2.5.0`
## \[2.6.0]
- [`d7a0bb32`](https://github.com/tauri-apps/plugins-workspace/commit/d7a0bb325dad919d6cc132eb3898c33540de77c4) ([#3136](https://github.com/tauri-apps/plugins-workspace/pull/3136) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `fileAccessMode` option to file picker.
## \[2.5.0]
- [`dff6fa98`](https://github.com/tauri-apps/plugins-workspace/commit/dff6fa986a9a05ba98b6ca660fea78ae97251fc2) ([#3034](https://github.com/tauri-apps/plugins-workspace/pull/3034) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `pickerMode` option to file picker (currently only used on iOS)
### Dependencies
- Upgraded to `fs-js@2.4.5`
### feat
- [`c23fa03f`](https://github.com/tauri-apps/plugins-workspace/commit/c23fa03f07d5c1c220bcf0bca482364513e3f754) ([#3098](https://github.com/tauri-apps/plugins-workspace/pull/3098) by [@Lepidopteran](https://github.com/tauri-apps/plugins-workspace/../../Lepidopteran)) Add `xdg-portal` as an optional feature for `rfd`
## \[2.4.2]
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
### Dependencies
- Upgraded to `fs-js@2.4.4`
## \[2.4.1]
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
### Dependencies
- Upgraded to `fs-js@2.4.3`
## \[2.4.0]
- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons.
## \[2.3.3]
### Dependencies
- Upgraded to `fs-js@2.4.2`
## \[2.3.2]
- [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set.
+7 -9
View File
@@ -1,6 +1,6 @@
[package]
name = "tauri-plugin-dialog"
version = "2.7.0"
version = "2.3.2"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true }
authors = { workspace = true }
@@ -9,12 +9,9 @@ rust-version = { workspace = true }
repository = { workspace = true }
links = "tauri-plugin-dialog"
[features]
default = ["gtk3"]
xdg-portal = ["rfd/xdg-portal", "rfd/tokio", "rfd/wayland"]
gtk3 = ["rfd/gtk3"]
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
[package.metadata.platforms.support]
@@ -37,14 +34,15 @@ tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.5.0" }
tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
[target.'cfg(target_os = "ios")'.dependencies]
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]
rfd = { version = "0.16", default-features = false, features = [
rfd = { version = "0.15", default-features = false, features = [
"tokio",
"gtk3",
"common-controls-v6",
] }
raw-window-handle = "0.6"
-18
View File
@@ -31,24 +31,6 @@ tauri-plugin-dialog = "2.0.0"
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
```
### Linux XDG Desktop Portal Support
By default, this plugin uses gtk to show dialogs, however since `v2.5.0` you can switch to using [XDG Desktop Portal](https://flatpak.github.io/xdg-desktop-portal/) by adding the following to your `Cargo.toml` file:
```toml
[dependencies]
tauri-plugin-dialog = { version = "2.5.0", default-features = false, features = ["xdg-portal"] }
# alternatively with Git:
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2", default-features = false, features = ["xdg-portal"] }
```
Do note if you use the `xdg-portal` feature, you need to ensure that [`zenity`](https://gitlab.gnome.org/GNOME/zenity) and an [XDG Desktop Portal backend](https://flatpak.github.io/xdg-desktop-portal#using-portals) is installed with your program.
For more information, see [XDG Desktop Portal documentation](https://flatpak.github.io/xdg-desktop-portal/) and [`rfd` documentation](https://docs.rs/rfd/latest/rfd#xdg-desktop-portal-backend).
### JavaScript
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
```sh
+2 -2
View File
@@ -5,10 +5,10 @@ plugins {
android {
namespace = "app.tauri.dialog"
compileSdk = 36
compileSdk = 34
defaultConfig {
minSdk = 24
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
@@ -31,7 +31,6 @@ class Filter {
class FilePickerOptions {
lateinit var filters: Array<Filter>
var multiple: Boolean? = null
var pickerMode: String? = null
}
@InvokeArg
@@ -39,7 +38,6 @@ class MessageOptions {
var title: String? = null
lateinit var message: String
var okButtonLabel: String? = null
var noButtonLabel: String? = null
var cancelButtonLabel: String? = null
}
@@ -62,19 +60,10 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
// TODO: ACTION_OPEN_DOCUMENT ??
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
if (args.pickerMode == "image") {
intent.type = "image/*"
} else if (args.pickerMode == "video") {
intent.type = "video/*"
} else if (args.pickerMode == "media") {
intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("video/*", "image/*"))
} else if (parsedTypes.isNotEmpty()) {
intent.type = "*/*"
if (parsedTypes.isNotEmpty()) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
} else {
intent.type = "*/*"
}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
@@ -150,8 +139,9 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return
}
val handler = { value: String ->
val handler = { cancelled: Boolean, value: Boolean ->
val ret = JSObject()
ret.put("cancelled", cancelled)
ret.put("value", value)
invoke.resolve(ret)
}
@@ -163,34 +153,24 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
if (args.title != null) {
builder.setTitle(args.title)
}
val okButtonLabel = args.okButtonLabel ?: "Ok"
builder
.setMessage(args.message)
.setPositiveButton(okButtonLabel) { dialog, _ ->
.setPositiveButton(
args.okButtonLabel ?: "OK"
) { dialog, _ ->
dialog.dismiss()
handler(okButtonLabel)
handler(false, true)
}
.setOnCancelListener { dialog ->
dialog.dismiss()
handler(args.cancelButtonLabel ?: "Cancel")
handler(true, false)
}
if (args.noButtonLabel != null) {
builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(args.noButtonLabel!!)
}
}
if (args.cancelButtonLabel != null) {
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(args.cancelButtonLabel!!)
handler(false, false)
}
}
val dialog = builder.create()
dialog.show()
}
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(n){"use strict";async function e(n,e={},t){return window.__TAURI_INTERNALS__.invoke(n,e,t)}function t(n){if(void 0!==n)return"string"==typeof n?n:"ok"in n&&"cancel"in n?{OkCancelCustom:[n.ok,n.cancel]}:"yes"in n&&"no"in n&&"cancel"in n?{YesNoCancelCustom:[n.yes,n.no,n.cancel]}:"ok"in n?{OkCustom:n.ok}:void 0}async function o(n,o){return await e("plugin:dialog|message",{message:n,title:o?.title,kind:o?.kind,buttons:t(o?.buttons)})}return"function"==typeof SuppressedError&&SuppressedError,n.ask=async function(n,e){const t="string"==typeof e?{title:e}:e,i=t?.okLabel||t?.cancelLabel,a=t?.okLabel??"Yes";return await o(n,{title:t?.title,kind:t?.kind,buttons:i?{ok:a,cancel:t.cancelLabel??"No"}:"YesNo"})===a},n.confirm=async function(n,e){const t="string"==typeof e?{title:e}:e,i=t?.okLabel||t?.cancelLabel,a=t?.okLabel??"Ok";return await o(n,{title:t?.title,kind:t?.kind,buttons:i?{ok:a,cancel:t.cancelLabel??"Cancel"}:"OkCancel"})===a},n.message=async function(n,e){const t="string"==typeof e?{title:e}:e;return t&&!t.buttons&&t.okLabel&&(t.buttons={ok:t.okLabel}),o(n,t)},n.open=async function(n={}){return"object"==typeof n&&Object.freeze(n),await e("plugin:dialog|open",{options:n})},n.save=async function(n={}){return"object"==typeof n&&Object.freeze(n),await e("plugin:dialog|save",{options:n})},n}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,yesButtonLabel:i?.okLabel?.toString(),noButtonLabel:i?.cancelLabel?.toString()})},t.confirm=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),cancelButtonLabel:i?.cancelLabel?.toString()})},t.message=async function(t,e){const i="string"==typeof e?{title:e}:e;await n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString()})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
+1 -1
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
const COMMANDS: &[&str] = &["open", "save", "message"];
const COMMANDS: &[&str] = &["open", "save", "message", "ask", "confirm"];
fn main() {
let result = tauri_plugin::Builder::new(COMMANDS)
+23 -250
View File
@@ -14,16 +14,6 @@ interface DialogFilter {
name: string
/**
* Extensions to filter, without a `.` prefix.
*
* **Note:** Mobile platforms have different APIs for filtering that may not support extensions.
* iOS: Extensions are supported in the document picker, but not in the media picker.
* Android: Extensions are not supported.
*
* For these platforms, MIME types are the primary way to filter files, as opposed to extensions.
* This means the string values here labeled as `extensions` may also be a MIME type.
* This property name of `extensions` is being kept for backwards compatibility, but this may be revisited to
* specify the difference between extension or MIME type filtering.
*
* @example
* ```typescript
* extensions: ['svg', 'png']
@@ -40,14 +30,7 @@ interface DialogFilter {
interface OpenDialogOptions {
/** The title of the dialog window (desktop only). */
title?: string
/**
* The filters of the dialog.
* On mobile platforms, if either:
* A) the {@linkcode pickerMode} is set to `media`, `image`, or `video`
* -- or --
* B) the filters include **only** either image or video mime types, the media picker will be displayed.
* Otherwise, the document picker will be displayed.
*/
/** The filters of the dialog. */
filters?: DialogFilter[]
/**
* Initial directory or file path.
@@ -69,38 +52,6 @@ interface OpenDialogOptions {
recursive?: boolean
/** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */
canCreateDirectories?: boolean
/**
* The preferred mode of the dialog.
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
* On desktop, this option is ignored.
*/
pickerMode?: PickerMode
/**
* The file access mode of the dialog.
* If not provided, `copy` is used, which matches the behavior of the {@linkcode open} method before the introduction of this option.
*
* **Usage**
* If a file is opened with {@linkcode fileAccessMode: 'copy'}, it will be copied to the app's sandbox.
* This means the file can be read, edited, deleted, copied, or any other operation without any issues, since the file
* now belongs to the app.
* This also means that the caller has responsibility of deleting the file if this file is not meant to be retained
* in the app sandbox.
*
* If a file is opened with {@linkcode fileAccessMode: 'scoped'}, the file will remain in its original location
* and security-scoped access will be automatically managed by the system.
*
* **Note**
* This is specifically meant for document pickers on iOS or MacOS, in conjunction with [security scoped resources](https://developer.apple.com/documentation/foundation/nsurl/startaccessingsecurityscopedresource()).
*
* Why only document pickers, and not image or video pickers?
* The image and video pickers on iOS behave differently from the document pickers, and return [NSItemProvider](https://developer.apple.com/documentation/foundation/nsitemprovider) objects instead of file URLs.
* These are meant to be ephemeral (only available within the callback of the picker), and are not accessible outside of the callback.
* So for image and video pickers, the only way to access the file is to copy it to the app's sandbox, and this is the URL that is returned from this API.
* This means there is no provision for using `scoped` mode with image or video pickers.
* If an image or video picker is used, `copy` is always used.
*/
fileAccessMode?: FileAccessMode
}
/**
@@ -126,100 +77,6 @@ interface SaveDialogOptions {
canCreateDirectories?: boolean
}
/**
* The preferred mode of the dialog.
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
* On desktop, this option is ignored.
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
*
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
*/
export type PickerMode = 'document' | 'media' | 'image' | 'video'
/**
* The file access mode of the dialog.
*
* - `copy`: copy/move the picked file to the app sandbox; no scoped access required.
* - `scoped`: keep file in place; security-scoped access is automatically managed.
*
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
*/
export type FileAccessMode = 'copy' | 'scoped'
/**
* Default buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogDefaultButtons =
| 'Ok'
| 'OkCancel'
| 'YesNo'
| 'YesNoCancel'
/** All possible button keys. */
type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no'
/** Ban everything except a set of keys. */
type BanExcept<Allowed extends ButtonKey> = Partial<
Record<Exclude<ButtonKey, Allowed>, never>
>
/**
* The Yes, No and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
} & BanExcept<'yes' | 'no' | 'cancel'>
/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
ok: string
/** The Cancel button. */
cancel: string
} & BanExcept<'ok' | 'cancel'>
/**
* The Ok button of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
ok: string
} & BanExcept<'ok'>
/**
* Custom buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogCustomButtons =
| MessageDialogButtonsYesNoCancel
| MessageDialogButtonsOkCancel
| MessageDialogButtonsOk
/**
* The buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtons =
| MessageDialogDefaultButtons
| MessageDialogCustomButtons
/**
* @since 2.0.0
*/
@@ -228,58 +85,8 @@ interface MessageDialogOptions {
title?: string
/** The kind of the dialog. Defaults to `info`. */
kind?: 'info' | 'warning' | 'error'
/**
* The label of the Ok button.
*
* @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
*/
/** The label of the confirm button. */
okLabel?: string
/**
* The buttons of the dialog.
*
* @example
*
* ```ts
* // Use system default buttons texts
* await message('Hello World!', { buttons: 'Ok' })
* await message('Hello World!', { buttons: 'OkCancel' })
*
* // Or with custom button texts
* await message('Hello World!', { buttons: { ok: 'Yes!' } })
* await message('Take on the task?', {
* buttons: { ok: 'Accept', cancel: 'Cancel' }
* })
* await message('Show the file content?', {
* buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' }
* })
* ```
*
* @since 2.4.0
*/
buttons?: MessageDialogButtons
}
/**
* Internal function to convert the buttons to the Rust type.
*/
function buttonsToRust(buttons: MessageDialogButtons | undefined) {
if (buttons === undefined) {
return undefined
}
if (typeof buttons === 'string') {
return buttons
} else if ('ok' in buttons && 'cancel' in buttons) {
return { OkCancelCustom: [buttons.ok, buttons.cancel] }
} else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
return {
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
}
} else if ('ok' in buttons) {
return { OkCustom: buttons.ok }
}
return undefined
}
interface ConfirmDialogOptions {
@@ -395,28 +202,6 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
return await invoke('plugin:dialog|save', { options })
}
/**
* The result of a message dialog.
*
* The result is a string if the dialog has custom buttons,
* otherwise it is one of the default buttons.
*
* @since 2.4.0
*/
export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})
async function messageCommand(
message: string,
options?: Omit<MessageDialogOptions, 'okLabel'>
) {
return await invoke<MessageDialogResult>('plugin:dialog|message', {
message,
title: options?.title,
kind: options?.kind,
buttons: buttonsToRust(options?.buttons)
})
}
/**
* Shows a message dialog with an `Ok` button.
* @example
@@ -437,19 +222,18 @@ async function messageCommand(
async function message(
message: string,
options?: string | MessageDialogOptions
): Promise<MessageDialogResult> {
): Promise<void> {
const opts = typeof options === 'string' ? { title: options } : options
if (opts && !opts.buttons && opts.okLabel) {
opts.buttons = { ok: opts.okLabel }
}
return messageCommand(message, opts)
await invoke('plugin:dialog|message', {
message: message.toString(),
title: opts?.title?.toString(),
kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString()
})
}
/**
* Shows a question dialog with `Yes` and `No` buttons.
*
* Convenient wrapper for `await message('msg', { buttons: 'YesNo' }) === 'Yes'`
*
* @example
* ```typescript
* import { ask } from '@tauri-apps/plugin-dialog';
@@ -469,24 +253,17 @@ async function ask(
options?: string | ConfirmDialogOptions
): Promise<boolean> {
const opts = typeof options === 'string' ? { title: options } : options
const customButtons = opts?.okLabel || opts?.cancelLabel
const okLabel = opts?.okLabel ?? 'Yes'
return (
(await messageCommand(message, {
title: opts?.title,
kind: opts?.kind,
buttons: customButtons
? { ok: okLabel, cancel: opts.cancelLabel ?? 'No' }
: 'YesNo'
})) === okLabel
)
return await invoke('plugin:dialog|ask', {
message: message.toString(),
title: opts?.title?.toString(),
kind: opts?.kind,
yesButtonLabel: opts?.okLabel?.toString(),
noButtonLabel: opts?.cancelLabel?.toString()
})
}
/**
* Shows a question dialog with `Ok` and `Cancel` buttons.
*
* Convenient wrapper for `await message('msg', { buttons: 'OkCancel' }) === 'Ok'`
*
* @example
* ```typescript
* import { confirm } from '@tauri-apps/plugin-dialog';
@@ -506,17 +283,13 @@ async function confirm(
options?: string | ConfirmDialogOptions
): Promise<boolean> {
const opts = typeof options === 'string' ? { title: options } : options
const customButtons = opts?.okLabel || opts?.cancelLabel
const okLabel = opts?.okLabel ?? 'Ok'
return (
(await messageCommand(message, {
title: opts?.title,
kind: opts?.kind,
buttons: customButtons
? { ok: okLabel, cancel: opts.cancelLabel ?? 'Cancel' }
: 'OkCancel'
})) === okLabel
)
return await invoke('plugin:dialog|confirm', {
message: message.toString(),
title: opts?.title?.toString(),
kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString(),
cancelButtonLabel: opts?.cancelLabel?.toString()
})
}
export type {
+66 -138
View File
@@ -8,7 +8,6 @@ import PhotosUI
import SwiftRs
import Tauri
import UIKit
import UniformTypeIdentifiers
import WebKit
enum FilePickerEvent {
@@ -21,7 +20,6 @@ struct MessageDialogOptions: Decodable {
var title: String?
let message: String
var okButtonLabel: String?
var noButtonLabel: String?
var cancelButtonLabel: String?
}
@@ -33,8 +31,6 @@ struct FilePickerOptions: Decodable {
var multiple: Bool?
var filters: [Filter]?
var defaultPath: String?
var pickerMode: PickerMode?
var fileAccessMode: FileAccessMode?
}
struct SaveFileDialogOptions: Decodable {
@@ -42,18 +38,6 @@ struct SaveFileDialogOptions: Decodable {
var defaultPath: String?
}
enum FileAccessMode: String, Decodable {
case copy
case scoped
}
enum PickerMode: String, Decodable {
case document
case media
case image
case video
}
class DialogPlugin: Plugin {
var filePickerController: FilePickerController!
@@ -62,12 +46,31 @@ class DialogPlugin: Plugin {
override init() {
super.init()
filePickerController = FilePickerController(self)
}
@objc public func showFilePicker(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(FilePickerOptions.self)
let parsedTypes = parseFiltersOption(args.filters ?? [])
var isMedia = !parsedTypes.isEmpty
var uniqueMimeType: Bool? = nil
var mimeKind: String? = nil
if !parsedTypes.isEmpty {
uniqueMimeType = true
for mime in parsedTypes {
let kind = mime.components(separatedBy: "/")[0]
if kind != "image" && kind != "video" {
isMedia = false
}
if mimeKind == nil {
mimeKind = kind
} else if mimeKind != kind {
uniqueMimeType = false
}
}
}
onFilePickerResult = { (event: FilePickerEvent) -> Void in
switch event {
case .selected(let urls):
@@ -79,59 +82,49 @@ class DialogPlugin: Plugin {
}
}
if #available(iOS 14, *) {
let parsedTypes = parseFiltersOption(args.filters ?? [])
let mimeKinds = Set(
parsedTypes.compactMap { $0.preferredMIMEType?.components(separatedBy: "/")[0] })
let filtersIncludeImage = mimeKinds.contains("image")
let filtersIncludeVideo = mimeKinds.contains("video")
let filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
// If the picker mode is media, images, or videos, we always want to show the media picker regardless of what's in the filters.
// Otherwise, if the filters A) do not include non-media types and B) include either image or video, we want to show the media picker.
if args.pickerMode == .media
|| args.pickerMode == .image
|| args.pickerMode == .video
|| (!filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo))
{
DispatchQueue.main.async {
if uniqueMimeType == true || isMedia {
DispatchQueue.main.async {
if #available(iOS 14, *) {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
// If the filters include image or video, use the appropriate filter.
// If both are true, don't define a filter, which means we will display all media.
if args.pickerMode == .image || (filtersIncludeImage && !filtersIncludeVideo) {
configuration.filter = .images
} else if args.pickerMode == .video || (filtersIncludeVideo && !filtersIncludeImage) {
configuration.filter = .videos
if uniqueMimeType == true {
if mimeKind == "image" {
configuration.filter = .images
} else if mimeKind == "video" {
configuration.filter = .videos
}
}
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self.filePickerController
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
} else {
DispatchQueue.main.async {
// The UTType.item is the catch-all, allowing for any file type to be selected.
let contentTypes = parsedTypes.isEmpty ? [UTType.item] : parsedTypes
let picker: UIDocumentPickerViewController = UIDocumentPickerViewController(
forOpeningContentTypes: contentTypes,
asCopy: args.fileAccessMode == .scoped ? false : true)
} else {
let picker = UIImagePickerController()
picker.delegate = self.filePickerController
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
if uniqueMimeType == true && mimeKind == "image" {
picker.sourceType = .photoLibrary
}
picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.sourceType = .photoLibrary
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
}
} else {
showFilePickerLegacy(args: args)
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
DispatchQueue.main.async {
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
}
picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
}
}
@@ -179,68 +172,7 @@ class DialogPlugin: Plugin {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
}
@available(iOS 14, *)
private func parseFiltersOption(_ filters: [Filter]) -> [UTType] {
var parsedTypes: [UTType] = []
for filter in filters {
for ext in filter.extensions ?? [] {
// We need to support extensions as well as MIME types.
if let utType = UTType(mimeType: ext) {
parsedTypes.append(utType)
} else if let utType = UTType(filenameExtension: ext) {
parsedTypes.append(utType)
}
}
}
return parsedTypes
}
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
private func showFilePickerLegacy(args: FilePickerOptions) {
let parsedTypes = parseFiltersOptionLegacy(args.filters ?? [])
var filtersIncludeImage: Bool = false
var filtersIncludeVideo: Bool = false
var filtersIncludeNonMedia: Bool = false
if !parsedTypes.isEmpty {
let mimeKinds = Set(parsedTypes.map { $0.components(separatedBy: "/")[0] })
filtersIncludeImage = mimeKinds.contains("image")
filtersIncludeVideo = mimeKinds.contains("video")
filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
}
if !filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo) {
DispatchQueue.main.async {
let picker = UIImagePickerController()
picker.delegate = self.filePickerController
if filtersIncludeImage && !filtersIncludeVideo {
picker.sourceType = .photoLibrary
}
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
} else {
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
DispatchQueue.main.async {
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
if let defaultPath = args.defaultPath {
picker.directoryURL = URL(string: defaultPath)
}
picker.delegate = self.filePickerController
picker.allowsMultipleSelection = args.multiple ?? false
picker.modalPresentationStyle = .fullScreen
self.presentViewController(picker)
}
}
}
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
private func parseFiltersOptionLegacy(_ filters: [Filter]) -> [String] {
private func parseFiltersOption(_ filters: [Filter]) -> [String] {
var parsedTypes: [String] = []
for filter in filters {
for ext in filter.extensions ?? [] {
@@ -253,7 +185,6 @@ class DialogPlugin: Plugin {
parsedTypes.append(utType)
}
}
return parsedTypes
}
@@ -269,42 +200,39 @@ class DialogPlugin: Plugin {
let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
if let cancelButtonLabel = args.cancelButtonLabel {
let cancelButtonLabel = args.cancelButtonLabel ?? ""
if !cancelButtonLabel.isEmpty {
alert.addAction(
UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": cancelButtonLabel])
}
)
)
Logger.error("cancel")
invoke.resolve([
"value": false,
"cancelled": false,
])
}))
}
if let noButtonLabel = args.noButtonLabel {
let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
if !okButtonLabel.isEmpty {
alert.addAction(
UIAlertAction(
title: noButtonLabel, style: UIAlertAction.Style.default,
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": noButtonLabel])
}
)
)
}
Logger.error("ok")
let okButtonLabel = args.okButtonLabel ?? "Ok"
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": okButtonLabel])
}
)
)
invoke.resolve([
"value": true,
"cancelled": false,
])
}))
}
manager.viewController?.present(alert, animated: true, completion: nil)
}
}
}
@_cdecl("init_plugin_dialog")
@@ -95,33 +95,16 @@ public class FilePickerController: NSObject {
return nil
}
}
/// ## In which cases do we need to save a copy of a file selected by a user to the app sandbox?
/// In short, only when the file is **not** selected using UIDocumentPickerDelegate.
/// For the rest of the cases, we need to write a copy of the file to the app sandbox.
///
/// For PHPicker (used for photos and videos), `NSItemProvider.loadFileRepresentation` returns a temporary file URL that is deleted after the completion handler.
/// The recommendation is to [Persist](https://developer.apple.com/documentation/foundation/nsitemprovider/2888338-loadfilerepresentation) the file by moving/copying
/// it to your apps directory within the completion handler.
///
/// If available, `loadInPlaceFileRepresentation` can open a file in place; Photos assets typically do not support true in-place access,
/// so fall back to persisting a local file.
/// Ref: https://developer.apple.com/documentation/foundation/nsitemprovider/2888335-loadinplacefilerepresentation
///
/// For UIDocumentPicker, prefer "open in place" and avoid copying when possible.
/// Ref: https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
directory = cachesDirectory
}
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do {
try deleteFile(targetUrl)
}
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl
}
@@ -136,7 +119,8 @@ public class FilePickerController: NSObject {
extension FilePickerController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
self.plugin.onFilePickerEvent(.selected(urls))
let temporaryUrls = try urls.map { try saveTemporaryFile($0) }
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
@@ -207,8 +191,6 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return
}
do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
@@ -230,8 +212,6 @@ extension FilePickerController: PHPickerViewControllerDelegate {
return
}
do {
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@tauri-apps/plugin-dialog",
"version": "2.7.0",
"version": "2.3.2",
"license": "MIT OR Apache-2.0",
"authors": [
"Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE"
],
"dependencies": {
"@tauri-apps/api": "^2.10.1"
"@tauri-apps/api": "^2.6.0"
}
}
-11
View File
@@ -1,11 +0,0 @@
"$schema" = "schemas/schema.json"
[[permission]]
identifier = "allow-ask"
description = "Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
commands.allow = ["message"]
[[permission]]
identifier = "deny-ask"
description = "Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
commands.deny = ["message"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-ask"
description = "Enables the ask command without any pre-configured scope."
commands.allow = ["ask"]
[[permission]]
identifier = "deny-ask"
description = "Denies the ask command without any pre-configured scope."
commands.deny = ["ask"]
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!
"$schema" = "../../schemas/schema.json"
[[permission]]
identifier = "allow-confirm"
description = "Enables the confirm command without any pre-configured scope."
commands.allow = ["confirm"]
[[permission]]
identifier = "deny-confirm"
description = "Denies the confirm command without any pre-configured scope."
commands.deny = ["confirm"]
@@ -9,6 +9,8 @@ All dialog types are enabled.
#### This default permission set includes the following:
- `allow-ask`
- `allow-confirm`
- `allow-message`
- `allow-save`
- `allow-open`
@@ -30,7 +32,7 @@ All dialog types are enabled.
</td>
<td>
Enables the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)
Enables the ask command without any pre-configured scope.
</td>
</tr>
@@ -43,7 +45,33 @@ Enables the ask command without any pre-configured scope. (**DEPRECATED**: This
</td>
<td>
Denies the ask command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)
Denies the ask command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`dialog:allow-confirm`
</td>
<td>
Enables the confirm command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`dialog:deny-confirm`
</td>
<td>
Denies the confirm command without any pre-configured scope.
</td>
</tr>
@@ -123,32 +151,6 @@ Enables the save command without any pre-configured scope.
Denies the save command without any pre-configured scope.
</td>
</tr>
<tr>
<td>
`dialog:allow-confirm`
</td>
<td>
Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)
</td>
</tr>
<tr>
<td>
`dialog:deny-confirm`
</td>
<td>
Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)
</td>
</tr>
</table>
-11
View File
@@ -1,11 +0,0 @@
"$schema" = "schemas/schema.json"
[[permission]]
identifier = "allow-confirm"
description = "Enables the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `allow-message` and will be removed in v3)"
commands.allow = ["message"]
[[permission]]
identifier = "deny-confirm"
description = "Denies the confirm command without any pre-configured scope. (**DEPRECATED**: This is now an alias to `deny-message` and will be removed in v3)"
commands.deny = ["message"]
+7 -1
View File
@@ -11,4 +11,10 @@ All dialog types are enabled.
"""
permissions = ["allow-message", "allow-save", "allow-open"]
permissions = [
"allow-ask",
"allow-confirm",
"allow-message",
"allow-save",
"allow-open",
]

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