mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-22 14:59:56 +02:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c61b224564 | |||
| 719cd9398e | |||
| 84bfcea116 | |||
| 021547be1c | |||
| eb5c600eda | |||
| ee5332fa84 | |||
| 802e416a51 | |||
| e0da7a8b8d | |||
| e9937b2220 | |||
| 4dfbb13520 | |||
| 60728ea22a | |||
| 7dee8ab6e2 | |||
| 30dd3f8b45 | |||
| 49a4f78806 | |||
| 7f025e5240 | |||
| 5700bd2213 | |||
| d402c3865a | |||
| 90ef77c872 | |||
| 51856e9e0a | |||
| 9741b97e8c | |||
| e421b9a2c0 | |||
| 371a2f7361 | |||
| 52c093ac9d | |||
| 6d6508f18e | |||
| 3fa814d1f0 | |||
| 1fe3dab64c | |||
| 5dadd205f5 | |||
| 3e15acea9a | |||
| 3e78173df9 | |||
| 64fac08bfb | |||
| fdc382dff0 | |||
| b2aea04567 | |||
| 3449dd5a8f | |||
| 57f69c6615 | |||
| cfb3ec0e21 | |||
| b8bf4ad360 | |||
| 87cc58527d | |||
| e0d2e2c53f | |||
| 9a7092ca44 | |||
| 606fa08dae | |||
| 62f1e40682 | |||
| c8e5614063 | |||
| 03c255ae20 | |||
| a706748dd3 | |||
| 7c1046d239 | |||
| a90f36b07e | |||
| e5249cff0b | |||
| d57df4debe | |||
| 1f649c7f1f | |||
| 0ca4cc914c | |||
| 9dcad78f44 | |||
| 4341d7f500 | |||
| 77149dc432 | |||
| 525abc4be5 | |||
| 14cee64c82 |
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"upload": "minor"
|
||||||
|
"upload-js": "minor"
|
||||||
|
---
|
||||||
|
|
||||||
|
Added a new field `progressTotal` to track the total amount of data transferred during the upload/download process.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
deep-link: patch
|
||||||
|
deep-link-js: patch
|
||||||
|
---
|
||||||
|
|
||||||
|
`onOpenUrl()` will now not call `getCurrent()` anymore, matching the documented behavior.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'log-plugin': 'patch'
|
||||||
|
'log-js': 'patch'
|
||||||
|
---
|
||||||
|
|
||||||
|
Make webview log target more consistent that it always starts with `webview`
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"fs": "patch"
|
||||||
|
"fs-js": "patch"
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix `readDir` function failing to read directories that contain broken symlinks.
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'localhost': 'minor'
|
||||||
|
---
|
||||||
|
|
||||||
|
Add custom host binding to allow external access
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"sql": "patch"
|
||||||
|
---
|
||||||
|
|
||||||
|
Allow blocking on async code without creating a nested runtime.
|
||||||
Generated
+193
-632
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -11,12 +11,12 @@ resolver = "2"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
tauri = { version = "2.0.4", default-features = false }
|
tauri = { version = "2", default-features = false }
|
||||||
tauri-build = "2.0.1"
|
tauri-build = "2"
|
||||||
tauri-plugin = "2.0.1"
|
tauri-plugin = "2"
|
||||||
tauri-utils = "2.0.1"
|
tauri-utils = "2"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
thiserror = "1"
|
thiserror = "2"
|
||||||
url = "2"
|
url = "2"
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
dunce = "1"
|
dunce = "1"
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
|
# Official Tauri Plugins
|
||||||
|
|
||||||
|
This repo and all plugins require a Rust version of at least **1.77.2**
|
||||||
|
|
||||||
## Plugins Found Here
|
## Plugins Found Here
|
||||||
|
|
||||||
| | | Win | Mac | Lin | iOS | And |
|
| | | Win | Mac | Lin | iOS | And |
|
||||||
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- |
|
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | --- | --- | --- | --- |
|
||||||
| [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | ? | ? |
|
| [autostart](plugins/autostart) | Automatically launch your app at system startup. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| [barcode-scanner](plugins/barcode-scanner) | Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. | ? | ? | ? | ✅ | ✅ |
|
| [barcode-scanner](plugins/barcode-scanner) | Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. | ? | ? | ? | ✅ | ✅ |
|
||||||
| [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ |
|
| [biometric](plugins/biometric) | Prompt the user for biometric authentication on Android and iOS. | ? | ? | ? | ✅ | ✅ |
|
||||||
| [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | ? | ? |
|
| [cli](plugins/cli) | Parse arguments from your Command Line Interface | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [clipboard-manager](plugins/clipboard-manager) | Read and write to the system clipboard. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [deep-link](plugins/deep-link) | Set your Tauri application as the default handler for an URL. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [dialog](plugins/dialog) | Native system dialogs for opening and saving files along with message dialogs. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? |
|
| [fs](plugins/fs) | Access the file system. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
|
| [geolocation](plugins/geolocation) | Get and track current device position. | ? | ? | ? | ✅ | ✅ |
|
||||||
| [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? |
|
| [global-shortcut](plugins/global-shortcut) | Register global shortcuts. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
|
| [haptics](plugins/haptics) | Haptic feedback and vibrations. | ? | ? | ? | ✅ | ✅ |
|
||||||
| [http](plugins/http) | Access the HTTP client written in Rust. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [http](plugins/http) | Access the HTTP client written in Rust. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [localhost](plugins/localhost) | Use a localhost server in production apps. | ✅ | ✅ | ✅ | ? | ? |
|
| [localhost](plugins/localhost) | Use a localhost server in production apps. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [log](plugins/log) | Configurable logging. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
@@ -18,19 +24,21 @@
|
|||||||
| [notification](plugins/notification) | Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [notification](plugins/notification) | Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [os](plugins/os) | Read information about the operating system. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [os](plugins/os) | Read information about the operating system. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? |
|
| [persisted-scope](plugins/persisted-scope) | Persist runtime scope changes on the filesystem. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ? | ? |
|
| [positioner](plugins/positioner) | Move windows to common locations. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| [process](plugins/process) | This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. | ✅ | ✅ | ✅ | ? | ? |
|
| [process](plugins/process) | This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [shell](plugins/shell) | Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? |
|
| [shell](plugins/shell) | Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | ? | ✅ | ? | ? |
|
| [single-instance](plugins/single-instance) | Ensure a single instance of your tauri app is running. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | ? |
|
| [sql](plugins/sql) | Interface with SQL databases. | ✅ | ✅ | ✅ | ? | ✅ |
|
||||||
| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? |
|
| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | ? | ? |
|
| [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? |
|
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
|
| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
|
||||||
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ? | ? |
|
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
|
||||||
_This repo and all plugins require a Rust version of at least **1.77.2**_
|
- ✅: (Partially) Supported
|
||||||
|
- ❌: Not supported
|
||||||
|
- `?` : Unknown/Untested or Planned
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.2]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.0.2`
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
+11
-10
@@ -1,21 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte-app",
|
"name": "api",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --clearScreen false",
|
"dev": "vite --clearScreen false",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview"
|
"serve": "vite preview",
|
||||||
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.0.2",
|
"@tauri-apps/api": "2.1.1",
|
||||||
"@tauri-apps/plugin-barcode-scanner": "2.0.0",
|
"@tauri-apps/plugin-barcode-scanner": "2.0.0",
|
||||||
"@tauri-apps/plugin-biometric": "2.0.0",
|
"@tauri-apps/plugin-biometric": "2.0.0",
|
||||||
"@tauri-apps/plugin-cli": "2.0.0",
|
"@tauri-apps/plugin-cli": "2.0.0",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
"@tauri-apps/plugin-clipboard-manager": "2.0.0",
|
||||||
"@tauri-apps/plugin-dialog": "2.0.1",
|
"@tauri-apps/plugin-dialog": "2.0.1",
|
||||||
"@tauri-apps/plugin-fs": "2.0.1",
|
"@tauri-apps/plugin-fs": "2.0.2",
|
||||||
"@tauri-apps/plugin-geolocation": "2.0.0",
|
"@tauri-apps/plugin-geolocation": "2.0.0",
|
||||||
"@tauri-apps/plugin-global-shortcut": "2.0.0",
|
"@tauri-apps/plugin-global-shortcut": "2.0.0",
|
||||||
"@tauri-apps/plugin-haptics": "2.0.0",
|
"@tauri-apps/plugin-haptics": "2.0.0",
|
||||||
@@ -32,11 +33,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/codicon": "^1.1.37",
|
"@iconify-json/codicon": "^1.1.37",
|
||||||
"@iconify-json/ph": "^1.1.8",
|
"@iconify-json/ph": "^1.1.8",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.1",
|
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||||
"@tauri-apps/cli": "2.0.3",
|
"@tauri-apps/cli": "2.1.0",
|
||||||
"@unocss/extractor-svelte": "^0.63.0",
|
"@unocss/extractor-svelte": "^0.64.0",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^5.0.0",
|
||||||
"unocss": "^0.63.0",
|
"unocss": "^0.64.0",
|
||||||
"vite": "^5.4.7"
|
"vite": "^5.4.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.5]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `clipboard-manager@2.0.2`
|
||||||
|
- Upgraded to `log-plugin@2.0.2`
|
||||||
|
|
||||||
|
## \[2.0.4]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs@2.0.3`
|
||||||
|
- Upgraded to `dialog@2.0.3`
|
||||||
|
- Upgraded to `http@2.0.3`
|
||||||
|
|
||||||
## \[2.0.3]
|
## \[2.0.3]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "api"
|
name = "api"
|
||||||
publish = false
|
publish = false
|
||||||
version = "2.0.3"
|
version = "2.0.5"
|
||||||
description = "An example Tauri Application showcasing the api"
|
description = "An example Tauri Application showcasing the api"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
@@ -19,15 +19,15 @@ serde_json = { workspace = true }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.1" }
|
tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.2" }
|
||||||
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.2", features = [
|
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.3", features = [
|
||||||
"watch",
|
"watch",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.1" }
|
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.2" }
|
||||||
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.2" }
|
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.3" }
|
||||||
tauri-plugin-http = { path = "../../../plugins/http", features = [
|
tauri-plugin-http = { path = "../../../plugins/http", features = [
|
||||||
"multipart",
|
"multipart",
|
||||||
], version = "2.0.2" }
|
], version = "2.0.3" }
|
||||||
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.1", features = [
|
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.0.1", features = [
|
||||||
"windows7-compat",
|
"windows7-compat",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"shell:allow-open",
|
||||||
"shell:allow-kill",
|
"shell:allow-kill",
|
||||||
"shell:allow-stdin-write",
|
"shell:allow-stdin-write",
|
||||||
"process:allow-exit",
|
"process:allow-exit",
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
"updater:default",
|
"updater:default",
|
||||||
"global-shortcut:allow-unregister",
|
"global-shortcut:allow-unregister",
|
||||||
"global-shortcut:allow-register",
|
"global-shortcut:allow-register",
|
||||||
"global-shortcut:allow-unregister-all"
|
"global-shortcut:allow-unregister-all",
|
||||||
|
{ "identifier": "fs:allow-watch", "allow": ["*", "**/*"] },
|
||||||
|
"fs:allow-unwatch"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,10 @@
|
|||||||
"geolocation:allow-check-permissions",
|
"geolocation:allow-check-permissions",
|
||||||
"geolocation:allow-request-permissions",
|
"geolocation:allow-request-permissions",
|
||||||
"geolocation:allow-watch-position",
|
"geolocation:allow-watch-position",
|
||||||
"geolocation:allow-get-current-position"
|
"geolocation:allow-get-current-position",
|
||||||
|
"haptics:allow-impact-feedback",
|
||||||
|
"haptics:allow-notification-feedback",
|
||||||
|
"haptics:allow-selection-feedback",
|
||||||
|
"haptics:allow-vibrate"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-41
@@ -21,6 +21,7 @@
|
|||||||
import Scanner from './views/Scanner.svelte'
|
import Scanner from './views/Scanner.svelte'
|
||||||
import Biometric from './views/Biometric.svelte'
|
import Biometric from './views/Biometric.svelte'
|
||||||
import Geolocation from './views/Geolocation.svelte'
|
import Geolocation from './views/Geolocation.svelte'
|
||||||
|
import Haptics from './views/Haptics.svelte'
|
||||||
|
|
||||||
import { onMount, tick } from 'svelte'
|
import { onMount, tick } from 'svelte'
|
||||||
import { ask } from '@tauri-apps/plugin-dialog'
|
import { ask } from '@tauri-apps/plugin-dialog'
|
||||||
@@ -130,6 +131,11 @@
|
|||||||
label: 'Geolocation',
|
label: 'Geolocation',
|
||||||
component: Geolocation,
|
component: Geolocation,
|
||||||
icon: 'i-ph-map-pin'
|
icon: 'i-ph-map-pin'
|
||||||
|
},
|
||||||
|
isMobile && {
|
||||||
|
label: 'Haptics',
|
||||||
|
component: Haptics,
|
||||||
|
icon: 'i-ph-vibrate'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -205,7 +211,7 @@
|
|||||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function is renders HTML without sanitizing it so it's insecure
|
// this function renders HTML without sanitizing it so it's insecure
|
||||||
// we only use it with our own input data
|
// we only use it with our own input data
|
||||||
async function insecureRenderHtml(html) {
|
async function insecureRenderHtml(html) {
|
||||||
messages.update((r) => [
|
messages.update((r) => [
|
||||||
@@ -334,42 +340,46 @@
|
|||||||
children:h-100% children:w-12 children:inline-flex
|
children:h-100% children:w-12 children:inline-flex
|
||||||
children:items-center children:justify-center"
|
children:items-center children:justify-center"
|
||||||
>
|
>
|
||||||
<span
|
<button
|
||||||
|
aria-label="Toggle dark mode"
|
||||||
title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
|
title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
|
||||||
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
||||||
on:click={toggleDark}
|
on:click={toggleDark}
|
||||||
>
|
>
|
||||||
{#if isDark}
|
{#if isDark}
|
||||||
<div class="i-ph-sun" />
|
<div class="i-ph-sun"></div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="i-ph-moon" />
|
<div class="i-ph-moon"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</button>
|
||||||
<span
|
<button
|
||||||
|
aria-label="Minimize window"
|
||||||
title="Minimize"
|
title="Minimize"
|
||||||
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
||||||
on:click={minimize}
|
on:click={minimize}
|
||||||
>
|
>
|
||||||
<div class="i-codicon-chrome-minimize" />
|
<div class="i-codicon-chrome-minimize"></div>
|
||||||
</span>
|
</button>
|
||||||
<span
|
<button
|
||||||
|
aria-label="Maximize window"
|
||||||
title={isWindowMaximized ? 'Restore' : 'Maximize'}
|
title={isWindowMaximized ? 'Restore' : 'Maximize'}
|
||||||
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
class="bg-inherit border-none hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
|
||||||
on:click={toggleMaximize}
|
on:click={toggleMaximize}
|
||||||
>
|
>
|
||||||
{#if isWindowMaximized}
|
{#if isWindowMaximized}
|
||||||
<div class="i-codicon-chrome-restore" />
|
<div class="i-codicon-chrome-restore"></div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="i-codicon-chrome-maximize" />
|
<div class="i-codicon-chrome-maximize"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</button>
|
||||||
<span
|
<button
|
||||||
|
aria-label="Close window"
|
||||||
title="Close"
|
title="Close"
|
||||||
class="hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText"
|
class="bg-inherit border-none hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText"
|
||||||
on:click={close}
|
on:click={close}
|
||||||
>
|
>
|
||||||
<div class="i-codicon-chrome-close" />
|
<div class="i-codicon-chrome-close"></div>
|
||||||
</span>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -377,13 +387,13 @@
|
|||||||
<!-- Sidebar toggle, only visible on small screens -->
|
<!-- Sidebar toggle, only visible on small screens -->
|
||||||
<div
|
<div
|
||||||
id="sidebarToggle"
|
id="sidebarToggle"
|
||||||
class="z-2000 sidebar-toggle display-none lt-sm:flex justify-center absolute items-center w-8 h-8 rd-8
|
class="z-2000 sidebar-toggle hidden lt-sm:flex justify-center absolute items-center w-8 h-8 rd-8
|
||||||
bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark"
|
bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark"
|
||||||
>
|
>
|
||||||
{#if isSideBarOpen}
|
{#if isSideBarOpen}
|
||||||
<span class="i-codicon-close animate-duration-300ms animate-fade-in" />
|
<span class="i-codicon-close animate-duration-300ms animate-fade-in"></span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="i-codicon-menu animate-duration-300ms animate-fade-in" />
|
<span class="i-codicon-menu animate-duration-300ms animate-fade-in"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -395,24 +405,21 @@
|
|||||||
class="lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999
|
class="lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999
|
||||||
bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid select-none px-2"
|
bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid select-none px-2"
|
||||||
>
|
>
|
||||||
<img
|
<a href="https://tauri.app" target="_blank">
|
||||||
on:click={() => open('https://tauri.app/')}
|
<img class="p-7" src="tauri_logo.png" alt="Tauri logo" />
|
||||||
class="self-center p-7 cursor-pointer"
|
</a>
|
||||||
src="tauri_logo.png"
|
|
||||||
alt="Tauri logo"
|
|
||||||
/>
|
|
||||||
{#if !isWindows}
|
{#if !isWindows}
|
||||||
<a href="##" class="nv justify-between h-8" on:click={toggleDark}>
|
<a href="##" class="nv justify-between h-8" on:click={toggleDark}>
|
||||||
{#if isDark}
|
{#if isDark}
|
||||||
Switch to Light mode
|
Switch to Light mode
|
||||||
<div class="i-ph-sun" />
|
<div class="i-ph-sun"></div>
|
||||||
{:else}
|
{:else}
|
||||||
Switch to Dark mode
|
Switch to Dark mode
|
||||||
<div class="i-ph-moon" />
|
<div class="i-ph-moon"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<div class="bg-white/5 h-2px" />
|
<div class="bg-white/5 h-2px"></div>
|
||||||
<br />
|
<br />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -422,7 +429,7 @@
|
|||||||
href="https://tauri.app/v1/guides/"
|
href="https://tauri.app/v1/guides/"
|
||||||
>
|
>
|
||||||
Documentation
|
Documentation
|
||||||
<span class="i-codicon-link-external" />
|
<span class="i-codicon-link-external"></span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="nv justify-between h-8"
|
class="nv justify-between h-8"
|
||||||
@@ -430,7 +437,7 @@
|
|||||||
href="https://github.com/tauri-apps/tauri"
|
href="https://github.com/tauri-apps/tauri"
|
||||||
>
|
>
|
||||||
GitHub
|
GitHub
|
||||||
<span class="i-codicon-link-external" />
|
<span class="i-codicon-link-external"></span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
class="nv justify-between h-8"
|
class="nv justify-between h-8"
|
||||||
@@ -438,10 +445,10 @@
|
|||||||
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
|
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
|
||||||
>
|
>
|
||||||
Source
|
Source
|
||||||
<span class="i-codicon-link-external" />
|
<span class="i-codicon-link-external"></span>
|
||||||
</a>
|
</a>
|
||||||
<br />
|
<br />
|
||||||
<div class="bg-white/5 h-2px" />
|
<div class="bg-white/5 h-2px"></div>
|
||||||
<br />
|
<br />
|
||||||
<div
|
<div
|
||||||
class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
|
class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
|
||||||
@@ -456,7 +463,7 @@
|
|||||||
isSideBarOpen = false
|
isSideBarOpen = false
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="{view.icon} mr-2" />
|
<div class="{view.icon} mr-2"></div>
|
||||||
<p>{view.label}</p></a
|
<p>{view.label}</p></a
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -485,21 +492,23 @@
|
|||||||
id="console"
|
id="console"
|
||||||
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
|
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
|
||||||
>
|
>
|
||||||
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
on:mousedown={startResizingConsole}
|
on:mousedown={startResizingConsole}
|
||||||
class="bg-black/20 h-2px cursor-ns-resize"
|
class="bg-black/20 h-2px cursor-ns-resize"
|
||||||
/>
|
></div>
|
||||||
<div class="flex justify-between items-center px-2">
|
<div class="flex justify-between items-center px-2">
|
||||||
<p class="font-semibold">Console</p>
|
<p class="font-semibold">Console</p>
|
||||||
<div
|
<button
|
||||||
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center
|
aria-label="Clear Console"
|
||||||
|
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center border-none bg-inherit
|
||||||
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
|
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
|
||||||
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
|
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
|
||||||
"
|
"
|
||||||
on:click={clear}
|
on:click={clear}
|
||||||
>
|
>
|
||||||
<div class="i-codicon-clear-all" />
|
<div class="i-codicon-clear-all"></div>
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
bind:this={consoleTextEl}
|
bind:this={consoleTextEl}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
import 'uno.css'
|
import 'uno.css'
|
||||||
import './app.css'
|
import './app.css'
|
||||||
import App from './App.svelte'
|
import App from './App.svelte'
|
||||||
|
import { mount } from 'svelte'
|
||||||
|
|
||||||
const app = new App({
|
const app = mount(App, {
|
||||||
target: document.querySelector('#app')
|
target: document.querySelector('#app')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getMatches } from "@tauri-apps/plugin-cli";
|
import { getMatches } from '@tauri-apps/plugin-cli'
|
||||||
|
|
||||||
export let onMessage;
|
export let onMessage
|
||||||
|
|
||||||
function cliMatches() {
|
function cliMatches() {
|
||||||
getMatches().then(onMessage).catch(onMessage);
|
getMatches().then(onMessage).catch(onMessage)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p>
|
<div>
|
||||||
This binary can be run from the terminal and takes the following arguments:
|
This binary can be run from the terminal and takes the following arguments:
|
||||||
<code class="code-block flex flex-wrap my-2">
|
<code class="code-block flex flex-wrap my-2">
|
||||||
<pre>
|
<pre>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
--verbose</pre>
|
--verbose</pre>
|
||||||
</code>
|
</code>
|
||||||
Additionally, it has a <code>update --background</code> subcommand.
|
Additionally, it has a <code>update --background</code> subcommand.
|
||||||
</p>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div class="note">
|
<div class="note">
|
||||||
Note that the arguments are only parsed, not implemented.
|
Note that the arguments are only parsed, not implemented.
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script>
|
||||||
|
import {
|
||||||
|
vibrate,
|
||||||
|
impactFeedback,
|
||||||
|
notificationFeedback,
|
||||||
|
selectionFeedback
|
||||||
|
} from '@tauri-apps/plugin-haptics'
|
||||||
|
|
||||||
|
export let onMessage
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
on:click={() => vibrate(300).then(onMessage).catch(onMessage)}
|
||||||
|
>vibrate short</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
on:click={() => vibrate(1500).then(onMessage).catch(onMessage)}
|
||||||
|
>vibrate long</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
on:click={() => impactFeedback('medium').then(onMessage).catch(onMessage)}
|
||||||
|
>impact medium</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
on:click={() =>
|
||||||
|
notificationFeedback('warning').then(onMessage).catch(onMessage)}
|
||||||
|
>notification warning</button
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
on:click={() => selectionFeedback().then(onMessage).catch(onMessage)}
|
||||||
|
>selection</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Depending on your device settings for haptic feedback some of the buttons may
|
||||||
|
not work.
|
||||||
|
</p>
|
||||||
@@ -1,69 +1,69 @@
|
|||||||
<script>
|
<script>
|
||||||
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
|
import { fetch as tauriFetch } from '@tauri-apps/plugin-http'
|
||||||
import { JsonView } from "@zerodevx/svelte-json-view";
|
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||||
|
|
||||||
let httpMethod = "GET";
|
let httpMethod = 'GET'
|
||||||
let httpBody = "";
|
let httpBody = ''
|
||||||
|
|
||||||
export let onMessage;
|
export let onMessage
|
||||||
|
|
||||||
async function makeHttpRequest() {
|
async function makeHttpRequest() {
|
||||||
let method = httpMethod || "GET";
|
let method = httpMethod || 'GET'
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: method || "GET",
|
method: method || 'GET',
|
||||||
headers: {},
|
headers: {}
|
||||||
};
|
}
|
||||||
|
|
||||||
let bodyType;
|
let bodyType
|
||||||
|
|
||||||
if (method !== "GET") {
|
if (method !== 'GET') {
|
||||||
options.body = httpBody;
|
options.body = httpBody
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(httpBody.startsWith("{") && httpBody.endsWith("}")) ||
|
(httpBody.startsWith('{') && httpBody.endsWith('}')) ||
|
||||||
(httpBody.startsWith("[") && httpBody.endsWith("]"))
|
(httpBody.startsWith('[') && httpBody.endsWith(']'))
|
||||||
) {
|
) {
|
||||||
options.headers["Content-Type"] = "application/json";
|
options.headers['Content-Type'] = 'application/json'
|
||||||
bodyType = "json";
|
bodyType = 'json'
|
||||||
} else if (httpBody !== "") {
|
} else if (httpBody !== '') {
|
||||||
bodyType = "text";
|
bodyType = 'text'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await tauriFetch("http://localhost:3003", options);
|
const response = await tauriFetch('http://localhost:3003', options)
|
||||||
const body =
|
const body =
|
||||||
bodyType === "json" ? await response.json() : await response.text();
|
bodyType === 'json' ? await response.json() : await response.text()
|
||||||
|
|
||||||
onMessage({
|
onMessage({
|
||||||
url: response.url,
|
url: response.url,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
ok: response.ok,
|
ok: response.ok,
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
body,
|
body
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// http form
|
/// http form
|
||||||
let foo = "baz";
|
let foo = 'baz'
|
||||||
let bar = "qux";
|
let bar = 'qux'
|
||||||
let result = null;
|
let result = null
|
||||||
|
|
||||||
async function doPost() {
|
async function doPost() {
|
||||||
const form = new FormData();
|
const form = new FormData()
|
||||||
form.append("foo", foo);
|
form.append('foo', foo)
|
||||||
form.append("bar", bar);
|
form.append('bar', bar)
|
||||||
const response = await tauriFetch("http://localhost:3003/tauri", {
|
const response = await tauriFetch('http://localhost:3003/tauri', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body: form,
|
body: form
|
||||||
});
|
})
|
||||||
result = {
|
result = {
|
||||||
url: response.url,
|
url: response.url,
|
||||||
status: response.status,
|
status: response.status,
|
||||||
ok: response.ok,
|
ok: response.ok,
|
||||||
headers: Object.fromEntries(response.headers.entries()),
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
body: await response.text(),
|
body: await response.text()
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
placeholder="Request body"
|
placeholder="Request body"
|
||||||
rows="5"
|
rows="5"
|
||||||
bind:value={httpBody}
|
bind:value={httpBody}
|
||||||
/>
|
></textarea>
|
||||||
<br />
|
<br />
|
||||||
<button class="btn" id="make-request"> Make request </button>
|
<button class="btn" id="make-request"> Make request </button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
<script>
|
<script>
|
||||||
import { scan, checkPermissions, requestPermissions, Format, cancel } from "@tauri-apps/plugin-barcode-scanner";
|
import {
|
||||||
|
scan,
|
||||||
|
checkPermissions,
|
||||||
|
requestPermissions,
|
||||||
|
Format,
|
||||||
|
cancel
|
||||||
|
} from '@tauri-apps/plugin-barcode-scanner'
|
||||||
|
|
||||||
export let onMessage;
|
export let onMessage
|
||||||
|
|
||||||
let scanning = false;
|
let scanning = false
|
||||||
let windowed = true;
|
let windowed = true
|
||||||
let formats = [Format.QRCode];
|
let formats = [Format.QRCode]
|
||||||
const supportedFormats = [Format.QRCode, Format.EAN13];
|
const supportedFormats = [Format.QRCode, Format.EAN13]
|
||||||
|
|
||||||
async function startScan() {
|
async function startScan() {
|
||||||
let permission = await checkPermissions();
|
let permission = await checkPermissions()
|
||||||
if (permission === 'prompt') {
|
if (permission === 'prompt') {
|
||||||
permission = await requestPermissions();
|
permission = await requestPermissions()
|
||||||
}
|
}
|
||||||
if (permission === 'granted') {
|
if (permission === 'granted') {
|
||||||
scanning = true;
|
scanning = true
|
||||||
scan({ windowed, formats })
|
scan({ windowed, formats })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
scanning = false;
|
scanning = false
|
||||||
onMessage(res);
|
onMessage(res)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
scanning = false;
|
scanning = false
|
||||||
onMessage(error);
|
onMessage(error)
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
onMessage('Permission denied')
|
onMessage('Permission denied')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancelScan() {
|
async function cancelScan() {
|
||||||
await cancel();
|
await cancel()
|
||||||
scanning = false;
|
scanning = false
|
||||||
onMessage("cancelled");
|
onMessage('cancelled')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -59,11 +65,12 @@
|
|||||||
<div class="barcode-scanner--area--container">
|
<div class="barcode-scanner--area--container">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<p>Aim your camera at a QR code</p>
|
<p>Aim your camera at a QR code</p>
|
||||||
<button class="btn" type="button" on:click={cancelScan}>Cancel</button>
|
<button class="btn" type="button" on:click={cancelScan}>Cancel</button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="square surround-cover">
|
<div class="square surround-cover">
|
||||||
<div class="barcode-scanner--area--outer surround-cover">
|
<div class="barcode-scanner--area--outer surround-cover">
|
||||||
<div class="barcode-scanner--area--inner" />
|
<div class="barcode-scanner--area--inner"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,7 +118,7 @@
|
|||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
}
|
}
|
||||||
.square:after {
|
.square:after {
|
||||||
content: "";
|
content: '';
|
||||||
top: 0;
|
top: 0;
|
||||||
display: block;
|
display: block;
|
||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
@@ -141,7 +148,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
border: 2px solid #fff;
|
border: 2px solid #fff;
|
||||||
box-shadow: 0px 0px 2px 1px rgb(0 0 0 / 0.5),
|
box-shadow:
|
||||||
|
0px 0px 2px 1px rgb(0 0 0 / 0.5),
|
||||||
inset 0px 0px 2px 1px rgb(0 0 0 / 0.5);
|
inset 0px 0px 2px 1px rgb(0 0 0 / 0.5);
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,56 @@
|
|||||||
<script>
|
<script>
|
||||||
import { check } from "@tauri-apps/plugin-updater";
|
import { check } from '@tauri-apps/plugin-updater'
|
||||||
import { relaunch } from "@tauri-apps/plugin-process";
|
import { relaunch } from '@tauri-apps/plugin-process'
|
||||||
|
|
||||||
export let onMessage;
|
export let onMessage
|
||||||
|
|
||||||
let isChecking, isInstalling, newUpdate;
|
let isChecking, isInstalling, newUpdate
|
||||||
let totalSize = 0,
|
let totalSize = 0,
|
||||||
downloadedSize = 0;
|
downloadedSize = 0
|
||||||
|
|
||||||
async function checkUpdate() {
|
async function checkUpdate() {
|
||||||
isChecking = true;
|
isChecking = true
|
||||||
try {
|
try {
|
||||||
const update = await check();
|
const update = await check()
|
||||||
onMessage(`Should update: ${update.available}`);
|
onMessage(`Should update: ${update.available}`)
|
||||||
onMessage(update);
|
onMessage(update)
|
||||||
|
|
||||||
newUpdate = update;
|
newUpdate = update
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
onMessage(e);
|
onMessage(e)
|
||||||
} finally {
|
} finally {
|
||||||
isChecking = false;
|
isChecking = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
isInstalling = true;
|
isInstalling = true
|
||||||
downloadedSize = 0;
|
downloadedSize = 0
|
||||||
try {
|
try {
|
||||||
await newUpdate.downloadAndInstall((downloadProgress) => {
|
await newUpdate.downloadAndInstall((downloadProgress) => {
|
||||||
switch (downloadProgress.event) {
|
switch (downloadProgress.event) {
|
||||||
case "Started":
|
case 'Started':
|
||||||
totalSize = downloadProgress.data.contentLength;
|
totalSize = downloadProgress.data.contentLength
|
||||||
break;
|
break
|
||||||
case "Progress":
|
case 'Progress':
|
||||||
downloadedSize += downloadProgress.data.chunkLength;
|
downloadedSize += downloadProgress.data.chunkLength
|
||||||
break;
|
break
|
||||||
case "Finished":
|
case 'Finished':
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
onMessage("Installation complete, restarting...");
|
onMessage('Installation complete, restarting...')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
await relaunch();
|
await relaunch()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e)
|
||||||
onMessage(e);
|
onMessage(e)
|
||||||
} finally {
|
} finally {
|
||||||
isInstalling = false;
|
isInstalling = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0;
|
$: progress = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex children:grow children:h10">
|
<div class="flex children:grow children:h10">
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<span>{progress}%</span>
|
<span>{progress}%</span>
|
||||||
<div class="progress-bar" style="width: {progress}%" />
|
<div class="progress-bar" style="width: {progress}%"></div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+7
-6
@@ -7,23 +7,24 @@
|
|||||||
"build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
|
"build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check ."
|
"format:check": "prettier --check .",
|
||||||
|
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.13.0",
|
"@eslint/js": "9.14.0",
|
||||||
"@rollup/plugin-node-resolve": "15.3.0",
|
"@rollup/plugin-node-resolve": "15.3.0",
|
||||||
"@rollup/plugin-terser": "0.4.4",
|
"@rollup/plugin-terser": "0.4.4",
|
||||||
"@rollup/plugin-typescript": "11.1.6",
|
"@rollup/plugin-typescript": "11.1.6",
|
||||||
"@types/eslint__js": "8.42.3",
|
"@types/eslint__js": "8.42.3",
|
||||||
"covector": "^0.12.3",
|
"covector": "^0.12.3",
|
||||||
"eslint": "9.13.0",
|
"eslint": "9.14.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-security": "3.0.1",
|
"eslint-plugin-security": "3.0.1",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"rollup": "4.22.4",
|
"rollup": "4.27.0",
|
||||||
"tslib": "2.7.0",
|
"tslib": "2.8.1",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"typescript-eslint": "8.10.0"
|
"typescript-eslint": "8.14.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"semver": ">=7.5.2",
|
"semver": ">=7.5.2",
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/autostart)
|
|
||||||
//!
|
|
||||||
//! Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux.
|
//! Automatically launch your application at startup. Supports Windows, Mac (via AppleScript or Launch Agent), and Linux.
|
||||||
|
|
||||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/cli)
|
|
||||||
//!
|
|
||||||
//! Parse arguments from your Command Line Interface.
|
//! Parse arguments from your Command Line Interface.
|
||||||
//!
|
//!
|
||||||
//! - Supported platforms: Windows, Linux and macOS.
|
//! - Supported platforms: Windows, Linux and macOS.
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.2]
|
||||||
|
|
||||||
|
- [`d57df4de`](https://github.com/tauri-apps/plugins-workspace/commit/d57df4debe7c75cfbd6d6558fff1beb07dbee54c) ([#1986](https://github.com/tauri-apps/plugins-workspace/pull/1986) by [@RikaKagurasaka](https://github.com/tauri-apps/plugins-workspace/../../RikaKagurasaka)) Fix that `read_image` wrongly set the image rgba data with binary PNG data.
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-clipboard-manager"
|
name = "tauri-plugin-clipboard-manager"
|
||||||
version = "2.0.1"
|
version = "2.0.2"
|
||||||
description = "Read and write to the system clipboard."
|
description = "Read and write to the system clipboard."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -37,4 +37,3 @@ tauri = { workspace = true, features = ["wry"] }
|
|||||||
|
|
||||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||||
arboard = "3"
|
arboard = "3"
|
||||||
image = "0.25"
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use arboard::ImageData;
|
use arboard::ImageData;
|
||||||
use image::ImageEncoder;
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tauri::{image::Image, plugin::PluginApi, AppHandle, Runtime};
|
use tauri::{image::Image, plugin::PluginApi, AppHandle, Runtime};
|
||||||
|
|
||||||
@@ -85,16 +84,11 @@ impl<R: Runtime> Clipboard<R> {
|
|||||||
match &self.clipboard {
|
match &self.clipboard {
|
||||||
Ok(clipboard) => {
|
Ok(clipboard) => {
|
||||||
let image = clipboard.lock().unwrap().get_image()?;
|
let image = clipboard.lock().unwrap().get_image()?;
|
||||||
|
let image = Image::new_owned(
|
||||||
let mut buffer: Vec<u8> = Vec::new();
|
image.bytes.to_vec(),
|
||||||
image::codecs::png::PngEncoder::new(&mut buffer).write_image(
|
|
||||||
&image.bytes,
|
|
||||||
image.width as u32,
|
image.width as u32,
|
||||||
image.height as u32,
|
image.height as u32,
|
||||||
image::ExtendedColorType::Rgba8,
|
);
|
||||||
)?;
|
|
||||||
|
|
||||||
let image = Image::new_owned(buffer, image.width as u32, image.height as u32);
|
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
Err(e) => Err(crate::Error::Clipboard(e.to_string())),
|
Err(e) => Err(crate::Error::Clipboard(e.to_string())),
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ pub enum Error {
|
|||||||
Clipboard(String),
|
Clipboard(String),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Tauri(#[from] tauri::Error),
|
Tauri(#[from] tauri::Error),
|
||||||
#[cfg(desktop)]
|
|
||||||
#[error("invalid image: {0}")]
|
|
||||||
Image(#[from] image::ImageError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for Error {
|
impl Serialize for Error {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/clipboard-manager)
|
|
||||||
//!
|
|
||||||
//! Read and write to the system clipboard.
|
//! Read and write to the system clipboard.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -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 a={kind:"Any"};return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function a(){return await r("plugin:deep-link|get_current")}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=a,e.isRegistered=async function(e){return await r("plugin:deep-link|is_registered",{protocol:e})},e.onOpenUrl=async function(e){const n=await a();return n&&e(n),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 a={kind:"Any"};return r("plugin:event|listen",{event:e,target:a,handler:n(t)}).then((n=>async()=>async function(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__})}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.0.2",
|
"@tauri-apps/api": "2.1.1",
|
||||||
"@tauri-apps/plugin-deep-link": "2.0.0"
|
"@tauri-apps/plugin-deep-link": "2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.0.3",
|
"@tauri-apps/cli": "2.1.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.4.7"
|
"vite": "^5.4.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,11 +99,6 @@ export async function isRegistered(protocol: string): Promise<boolean> {
|
|||||||
export async function onOpenUrl(
|
export async function onOpenUrl(
|
||||||
handler: (urls: string[]) => void
|
handler: (urls: string[]) => void
|
||||||
): Promise<UnlistenFn> {
|
): Promise<UnlistenFn> {
|
||||||
const current = await getCurrent()
|
|
||||||
if (current) {
|
|
||||||
handler(current)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await listen<string[]>('deep-link://new-url', (event) => {
|
return await listen<string[]>('deep-link://new-url', (event) => {
|
||||||
handler(event.payload)
|
handler(event.payload)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use tauri::{
|
use tauri::{
|
||||||
plugin::{Builder, PluginApi, TauriPlugin},
|
plugin::{Builder, PluginApi, TauriPlugin},
|
||||||
AppHandle, EventId, Listener, Manager, Runtime,
|
AppHandle, EventId, Listener, Manager, Runtime,
|
||||||
@@ -478,13 +476,10 @@ impl OpenUrlEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Runtime> DeepLink<R> {
|
impl<R: Runtime> DeepLink<R> {
|
||||||
/// Handle a new deep link being triggered to open the app.
|
/// Helper function for the `deep-link://new-url` event to run a function each time the protocol is triggered while the app is running.
|
||||||
///
|
///
|
||||||
/// To avoid race conditions, if the app was started with a deep link,
|
/// Use `get_current` on app load to check whether your app was started via a deep link.
|
||||||
/// the closure gets immediately called with the deep link URL.
|
|
||||||
pub fn on_open_url<F: Fn(OpenUrlEvent) + Send + Sync + 'static>(&self, f: F) -> EventId {
|
pub fn on_open_url<F: Fn(OpenUrlEvent) + Send + Sync + 'static>(&self, f: F) -> EventId {
|
||||||
let f = Arc::new(f);
|
|
||||||
let f_ = f.clone();
|
|
||||||
let event_id = self.app.listen("deep-link://new-url", move |event| {
|
let event_id = self.app.listen("deep-link://new-url", move |event| {
|
||||||
if let Ok(urls) = serde_json::from_str(event.payload()) {
|
if let Ok(urls) = serde_json::from_str(event.payload()) {
|
||||||
f(OpenUrlEvent {
|
f(OpenUrlEvent {
|
||||||
@@ -494,13 +489,6 @@ impl<R: Runtime> DeepLink<R> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(Some(current)) = self.get_current() {
|
|
||||||
f_(OpenUrlEvent {
|
|
||||||
id: event_id,
|
|
||||||
urls: current,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
event_id
|
event_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.3]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs@2.0.3`
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`2302c2db`](https://github.com/tauri-apps/plugins-workspace/commit/2302c2db1c49673e61dcbda8cdb01b2c57e9ba6f) ([#1910](https://github.com/tauri-apps/plugins-workspace/pull/1910) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `ask` and `confirm` not using system button texts
|
- [`2302c2db`](https://github.com/tauri-apps/plugins-workspace/commit/2302c2db1c49673e61dcbda8cdb01b2c57e9ba6f) ([#1910](https://github.com/tauri-apps/plugins-workspace/pull/1910) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `ask` and `confirm` not using system button texts
|
||||||
@@ -293,5 +299,5 @@
|
|||||||
pull/371)) First v2 alpha release!
|
pull/371)) First v2 alpha release!
|
||||||
lpha release!
|
lpha release!
|
||||||
pull/371)) First v2 alpha release!
|
pull/371)) First v2 alpha release!
|
||||||
lease!
|
lease!
|
||||||
pull/371)) First v2 alpha release!
|
pull/371)) First v2 alpha release!
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.0.2"
|
version = "2.0.3"
|
||||||
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
|
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -34,7 +34,7 @@ tauri = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
tauri-plugin-fs = { path = "../fs", version = "2.0.2" }
|
tauri-plugin-fs = { path = "../fs", version = "2.0.3" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
tauri = { workspace = true, features = ["wry"] }
|
tauri = { workspace = true, features = ["wry"] }
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/dialog)
|
|
||||||
//!
|
|
||||||
//! Native system dialogs for opening and saving files along with message dialogs.
|
//! Native system dialogs for opening and saving files along with message dialogs.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.2]
|
||||||
|
|
||||||
|
- [`77149dc4`](https://github.com/tauri-apps/plugins-workspace/commit/77149dc4320d26b413e4a6bbe82c654367c51b32) ([#1965](https://github.com/tauri-apps/plugins-workspace/pull/1965) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Fix `writeTextFile` converting UTF-8 characters (for example `äöü`) in the given path into replacement character (`�`)
|
||||||
|
|
||||||
|
## \[2.0.3]
|
||||||
|
|
||||||
|
- [`14cee64c`](https://github.com/tauri-apps/plugins-workspace/commit/14cee64c82a72655ae6a4ac0892736a2959dbda5) ([#1958](https://github.com/tauri-apps/plugins-workspace/pull/1958) by [@bWanShiTong](https://github.com/tauri-apps/plugins-workspace/../../bWanShiTong)) Fix compilation on targets with pointer width of `16` or `32`
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`ae802456`](https://github.com/tauri-apps/plugins-workspace/commit/ae8024565f074f313084777c8b10d1b5e3bbe220) ([#1950](https://github.com/tauri-apps/plugins-workspace/pull/1950) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Improve performance of the `FileHandle.read` and `writeTextFile` APIs.
|
- [`ae802456`](https://github.com/tauri-apps/plugins-workspace/commit/ae8024565f074f313084777c8b10d1b5e3bbe220) ([#1950](https://github.com/tauri-apps/plugins-workspace/pull/1950) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Improve performance of the `FileHandle.read` and `writeTextFile` APIs.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.0.2"
|
version = "2.0.3"
|
||||||
description = "Access the file system."
|
description = "Access the file system."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -35,8 +35,12 @@ url = { workspace = true }
|
|||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
notify = { version = "6", optional = true, features = ["serde"] }
|
# TODO: Remove `serialization-compat-6` in v3
|
||||||
notify-debouncer-full = { version = "0.3", optional = true }
|
notify = { version = "7", optional = true, features = [
|
||||||
|
"serde",
|
||||||
|
"serialization-compat-6",
|
||||||
|
] }
|
||||||
|
notify-debouncer-full = { version = "0.4", optional = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -14,16 +14,29 @@
|
|||||||
*
|
*
|
||||||
* The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns.
|
* The API has a scope configuration that forces you to restrict the paths that can be accessed using glob patterns.
|
||||||
*
|
*
|
||||||
* The scope configuration is an array of glob patterns describing folder paths that are allowed.
|
* The scope configuration is an array of glob patterns describing file/directory paths that are allowed.
|
||||||
* For instance, this scope configuration only allows accessing files on the
|
* For instance, this scope configuration allows **all** enabled `fs` APIs to (only) access files in the
|
||||||
* *databases* folder of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}:
|
* *databases* directory of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}:
|
||||||
* ```json
|
* ```json
|
||||||
* {
|
* {
|
||||||
* "plugins": {
|
* "permissions": [
|
||||||
* "fs": {
|
* {
|
||||||
* "scope": ["$APPDATA/databases/*"]
|
* "identifier": "fs:scope",
|
||||||
|
* "allow": [{ "path": "$APPDATA/databases/*" }]
|
||||||
* }
|
* }
|
||||||
* }
|
* ]
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Scopes can also be applied to specific `fs` APIs by using the API's identifier instead of `fs:scope`:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "permissions": [
|
||||||
|
* {
|
||||||
|
* "identifier": "fs:allow-exists",
|
||||||
|
* "allow": [{ "path": "$APPDATA/databases/*" }]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@@ -56,8 +69,6 @@
|
|||||||
*
|
*
|
||||||
* Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
|
* Trying to execute any API with a URL not configured on the scope results in a promise rejection due to denied access.
|
||||||
*
|
*
|
||||||
* Note that this scope applies to **all** APIs on this module.
|
|
||||||
*
|
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -1072,7 +1083,7 @@ async function writeTextFile(
|
|||||||
|
|
||||||
await invoke('plugin:fs|write_text_file', encoder.encode(data), {
|
await invoke('plugin:fs|write_text_file', encoder.encode(data), {
|
||||||
headers: {
|
headers: {
|
||||||
path: path instanceof URL ? path.toString() : path,
|
path: encodeURIComponent(path instanceof URL ? path.toString() : path),
|
||||||
options: JSON.stringify(options)
|
options: JSON.stringify(options)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-fs",
|
"name": "@tauri-apps/plugin-fs",
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"description": "Access the file system.",
|
"description": "Access the file system.",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ This default permission set prevents access to critical components
|
|||||||
of the Tauri application by default.
|
of the Tauri application by default.
|
||||||
On Windows the webview data folder access is denied.
|
On Windows the webview data folder access is denied.
|
||||||
|
|
||||||
|
#### Included permissions within this default permission set:
|
||||||
|
|
||||||
|
|
||||||
- `create-app-specific-dirs`
|
- `create-app-specific-dirs`
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ This default permission set prevents access to critical components
|
|||||||
of the Tauri application by default.
|
of the Tauri application by default.
|
||||||
On Windows the webview data folder access is denied.
|
On Windows the webview data folder access is denied.
|
||||||
|
|
||||||
|
#### Included permissions within this default permission set:
|
||||||
"""
|
"""
|
||||||
permissions = [
|
permissions = [
|
||||||
"create-app-specific-dirs",
|
"create-app-specific-dirs",
|
||||||
|
|||||||
@@ -1665,7 +1665,7 @@
|
|||||||
"const": "create-app-specific-dirs"
|
"const": "create-app-specific-dirs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n",
|
"description": "This set of permissions describes the what kind of\nfile system access the `fs` plugin has enabled or denied by default.\n\n#### Granted Permissions\n\nThis default permission set enables read access to the\napplication specific directories (AppConfig, AppData, AppLocalData, AppCache,\nAppLog) and all files and sub directories created in it.\nThe location of these directories depends on the operating system,\nwhere the application is run.\n\nIn general these directories need to be manually created\nby the application at runtime, before accessing files or folders\nin it is possible.\n\nTherefore, it is also allowed to create all of these folders via\nthe `mkdir` command.\n\n#### Denied Permissions\n\nThis default permission set prevents access to critical components\nof the Tauri application by default.\nOn Windows the webview data folder access is denied.\n\n#### Included permissions within this default permission set:\n",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "default"
|
"const": "default"
|
||||||
},
|
},
|
||||||
|
|||||||
+34
-29
@@ -16,7 +16,7 @@ use std::{
|
|||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufReader, Lines, Read, Write},
|
io::{BufReader, Lines, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
@@ -245,32 +245,12 @@ pub fn mkdir<R: Runtime>(
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct DirEntry {
|
pub struct DirEntry {
|
||||||
pub name: Option<String>,
|
pub name: String,
|
||||||
pub is_directory: bool,
|
pub is_directory: bool,
|
||||||
pub is_file: bool,
|
pub is_file: bool,
|
||||||
pub is_symlink: bool,
|
pub is_symlink: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dir_inner<P: AsRef<Path>>(path: P) -> crate::Result<Vec<DirEntry>> {
|
|
||||||
let mut files_and_dirs: Vec<DirEntry> = vec![];
|
|
||||||
for entry in std::fs::read_dir(path)? {
|
|
||||||
let path = entry?.path();
|
|
||||||
let file_type = path.metadata()?.file_type();
|
|
||||||
files_and_dirs.push(DirEntry {
|
|
||||||
is_directory: file_type.is_dir(),
|
|
||||||
is_file: file_type.is_file(),
|
|
||||||
is_symlink: std::fs::symlink_metadata(&path)
|
|
||||||
.map(|md| md.file_type().is_symlink())
|
|
||||||
.unwrap_or(false),
|
|
||||||
name: path
|
|
||||||
.file_name()
|
|
||||||
.map(|name| name.to_string_lossy())
|
|
||||||
.map(|name| name.to_string()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Result::Ok(files_and_dirs)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn read_dir<R: Runtime>(
|
pub async fn read_dir<R: Runtime>(
|
||||||
webview: Webview<R>,
|
webview: Webview<R>,
|
||||||
@@ -287,14 +267,37 @@ pub async fn read_dir<R: Runtime>(
|
|||||||
options.as_ref().and_then(|o| o.base_dir),
|
options.as_ref().and_then(|o| o.base_dir),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
read_dir_inner(&resolved_path)
|
let entries = std::fs::read_dir(&resolved_path).map_err(|e| {
|
||||||
.map_err(|e| {
|
format!(
|
||||||
format!(
|
"failed to read directory at path: {} with error: {e}",
|
||||||
"failed to read directory at path: {} with error: {e}",
|
resolved_path.display()
|
||||||
resolved_path.display()
|
)
|
||||||
)
|
})?;
|
||||||
|
|
||||||
|
let entries = entries
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let entry = entry.ok()?;
|
||||||
|
let name = entry.file_name().into_string().ok()?;
|
||||||
|
let metadata = entry.file_type();
|
||||||
|
macro_rules! method_or_false {
|
||||||
|
($method:ident) => {
|
||||||
|
if let Ok(metadata) = &metadata {
|
||||||
|
metadata.$method()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(DirEntry {
|
||||||
|
name,
|
||||||
|
is_file: method_or_false!(is_file),
|
||||||
|
is_directory: method_or_false!(is_dir),
|
||||||
|
is_symlink: method_or_false!(is_symlink),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.map_err(Into::into)
|
.collect();
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -316,12 +319,14 @@ pub async fn read<R: Runtime>(
|
|||||||
let nread = nread.to_be_bytes();
|
let nread = nread.to_be_bytes();
|
||||||
let mut out = [0; 8];
|
let mut out = [0; 8];
|
||||||
out[6..].copy_from_slice(&nread);
|
out[6..].copy_from_slice(&nread);
|
||||||
|
out
|
||||||
};
|
};
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
let nread = {
|
let nread = {
|
||||||
let nread = nread.to_be_bytes();
|
let nread = nread.to_be_bytes();
|
||||||
let mut out = [0; 8];
|
let mut out = [0; 8];
|
||||||
out[4..].copy_from_slice(&nread);
|
out[4..].copy_from_slice(&nread);
|
||||||
|
out
|
||||||
};
|
};
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
let nread = nread.to_be_bytes();
|
let nread = nread.to_be_bytes();
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/fs)
|
|
||||||
//!
|
|
||||||
//! Access the file system.
|
//! Access the file system.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
|
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, RecommendedCache};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tauri::{
|
use tauri::{
|
||||||
ipc::{Channel, CommandScope, GlobalScope},
|
ipc::{Channel, CommandScope, GlobalScope},
|
||||||
@@ -47,7 +47,7 @@ impl WatcherResource {
|
|||||||
impl Resource for WatcherResource {}
|
impl Resource for WatcherResource {}
|
||||||
|
|
||||||
enum WatcherKind {
|
enum WatcherKind {
|
||||||
Debouncer(Debouncer<RecommendedWatcher, FileIdMap>),
|
Debouncer(Debouncer<RecommendedWatcher, RecommendedCache>),
|
||||||
Watcher(RecommendedWatcher),
|
Watcher(RecommendedWatcher),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +111,7 @@ pub async fn watch<R: Runtime>(
|
|||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?;
|
let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?;
|
||||||
for path in &resolved_paths {
|
for path in &resolved_paths {
|
||||||
debouncer.watcher().watch(path.as_ref(), recursive_mode)?;
|
debouncer.watch(path, recursive_mode)?;
|
||||||
debouncer.cache().add_root(path, recursive_mode);
|
|
||||||
}
|
}
|
||||||
watch_debounced(on_event, rx);
|
watch_debounced(on_event, rx);
|
||||||
WatcherKind::Debouncer(debouncer)
|
WatcherKind::Debouncer(debouncer)
|
||||||
@@ -120,7 +119,7 @@ pub async fn watch<R: Runtime>(
|
|||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
|
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
|
||||||
for path in &resolved_paths {
|
for path in &resolved_paths {
|
||||||
watcher.watch(path.as_ref(), recursive_mode)?;
|
watcher.watch(path, recursive_mode)?;
|
||||||
}
|
}
|
||||||
watch_raw(on_event, rx);
|
watch_raw(on_event, rx);
|
||||||
WatcherKind::Watcher(watcher)
|
WatcherKind::Watcher(watcher)
|
||||||
@@ -140,14 +139,14 @@ pub async fn unwatch<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> Comman
|
|||||||
match &mut watcher.kind {
|
match &mut watcher.kind {
|
||||||
WatcherKind::Debouncer(ref mut debouncer) => {
|
WatcherKind::Debouncer(ref mut debouncer) => {
|
||||||
for path in &watcher.paths {
|
for path in &watcher.paths {
|
||||||
debouncer.watcher().unwatch(path.as_ref()).map_err(|e| {
|
debouncer.unwatch(path).map_err(|e| {
|
||||||
format!("failed to unwatch path: {} with error: {e}", path.display())
|
format!("failed to unwatch path: {} with error: {e}", path.display())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WatcherKind::Watcher(ref mut w) => {
|
WatcherKind::Watcher(ref mut w) => {
|
||||||
for path in &watcher.paths {
|
for path in &watcher.paths {
|
||||||
w.unwatch(path.as_ref()).map_err(|e| {
|
w.unwatch(path).map_err(|e| {
|
||||||
format!("failed to unwatch path: {} with error: {e}", path.display())
|
format!("failed to unwatch path: {} with error: {e}", path.display())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,20 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then, for instance, grant the plugin the permission to check or request permissions from the user and to read the device position
|
||||||
|
|
||||||
|
`src-tauri/capabilities/default.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
"permissions": [
|
||||||
|
"core:default",
|
||||||
|
"geolocation:allow-check-permissions",
|
||||||
|
"geolocation:allow-request-permissions",
|
||||||
|
"geolocation:allow-get-current-position",
|
||||||
|
"geolocation:allow-watch-position",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
|
Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -100,7 +114,7 @@ import {
|
|||||||
requestPermissions,
|
requestPermissions,
|
||||||
getCurrentPosition,
|
getCurrentPosition,
|
||||||
watchPosition
|
watchPosition
|
||||||
} from '@tauri-apps/plugin-log'
|
} from '@tauri-apps/plugin-geolocation'
|
||||||
|
|
||||||
let permissions = await checkPermissions()
|
let permissions = await checkPermissions()
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,s;"function"==typeof SuppressedError&&SuppressedError;class r{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),s.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:r})=>{if(r===e(this,o,"f")){n(this,o,r+1),e(this,i,"f").call(this,t);const a=Object.keys(e(this,s,"f"));if(a.length>0){let t=r+1;for(const n of a.sort()){if(parseInt(n)!==t)break;{const o=e(this,s,"f")[n];delete e(this,s,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,s,"f")[r.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}return i=new WeakMap,o=new WeakMap,s=new WeakMap,t.checkPermissions=async function(){return await async function(t){return a(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await a("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await a("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await a("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,e){const n=new r;return n.onmessage=t=>{"string"==typeof t?e(null,t):e(t)},await a("plugin:geolocation|watch_position",{options:t,channel:n}),n.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_GEOLOCATION__=function(t){"use strict";function e(t,e,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,s;"function"==typeof SuppressedError&&SuppressedError;const r="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),o.set(this,0),s.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:r})=>{if(r===e(this,o,"f")){n(this,o,r+1),e(this,i,"f").call(this,t);const a=Object.keys(e(this,s,"f"));if(a.length>0){let t=r+1;for(const n of a.sort()){if(parseInt(n)!==t)break;{const o=e(this,s,"f")[n];delete e(this,s,"f")[n],e(this,i,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,s,"f")[r.toString()]=t}))}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,s=new WeakMap,r)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[r]()}}async function c(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}return t.checkPermissions=async function(){return await async function(t){return c(`plugin:${t}|check_permissions`)}("geolocation")},t.clearWatch=async function(t){await c("plugin:geolocation|clear_watch",{channelId:t})},t.getCurrentPosition=async function(t){return await c("plugin:geolocation|get_current_position",{options:t})},t.requestPermissions=async function(t){return await c("plugin:geolocation|request_permissions",{permissions:t})},t.watchPosition=async function(t,e){const n=new a;return n.onmessage=t=>{"string"==typeof t?e(null,t):e(t)},await c("plugin:geolocation|watch_position",{options:t,channel:n}),n.id},t}({});Object.defineProperty(window.__TAURI__,"geolocation",{value:__TAURI_PLUGIN_GEOLOCATION__})}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBAL_SHORTCUT__=function(t){"use strict";function e(t,e,r,s){if("a"===r&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?s:"a"===r?s.call(t):s?s.value:e.get(t)}function r(t,e,r,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,r),r}var s,n,i;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),i.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:o})=>{if(o===e(this,n,"f")){r(this,n,o+1),e(this,s,"f").call(this,t);const a=Object.keys(e(this,i,"f"));if(a.length>0){let t=o+1;for(const r of a.sort()){if(parseInt(r)!==t)break;{const n=e(this,i,"f")[r];delete e(this,i,"f")[r],e(this,s,"f").call(this,n),t+=1}}r(this,n,t)}}else e(this,i,"f")[o.toString()]=t}))}set onmessage(t){r(this,s,t)}get onmessage(){return e(this,s,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}return s=new WeakMap,n=new WeakMap,i=new WeakMap,t.isRegistered=async function(t){return await a("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const r=new o;return r.onmessage=e,await a("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:r})},t.unregister=async function(t){return await a("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await a("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBAL_SHORTCUT__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_GLOBAL_SHORTCUT__=function(t){"use strict";function e(t,e,r,s){if("a"===r&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===r?s:"a"===r?s.call(t):s?s.value:e.get(t)}function r(t,e,r,s,n){if("function"==typeof e?t!==e||!n:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,r),r}var s,n,i;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),i.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:o})=>{if(o===e(this,n,"f")){r(this,n,o+1),e(this,s,"f").call(this,t);const a=Object.keys(e(this,i,"f"));if(a.length>0){let t=o+1;for(const r of a.sort()){if(parseInt(r)!==t)break;{const n=e(this,i,"f")[r];delete e(this,i,"f")[r],e(this,s,"f").call(this,n),t+=1}}r(this,n,t)}}else e(this,i,"f")[o.toString()]=t}))}set onmessage(t){r(this,s,t)}get onmessage(){return e(this,s,"f")}[(s=new WeakMap,n=new WeakMap,i=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function _(t,e={},r){return window.__TAURI_INTERNALS__.invoke(t,e,r)}return t.isRegistered=async function(t){return await _("plugin:global-shortcut|is_registered",{shortcut:t})},t.register=async function(t,e){const r=new a;return r.onmessage=e,await _("plugin:global-shortcut|register",{shortcuts:Array.isArray(t)?t:[t],handler:r})},t.unregister=async function(t){return await _("plugin:global-shortcut|unregister",{shortcuts:Array.isArray(t)?t:[t]})},t.unregisterAll=async function(){return await _("plugin:global-shortcut|unregister_all",{})},t}({});Object.defineProperty(window.__TAURI__,"globalShortcut",{value:__TAURI_PLUGIN_GLOBAL_SHORTCUT__})}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/global-shortcut)
|
|
||||||
//!
|
|
||||||
//! Register global shortcuts.
|
//! Register global shortcuts.
|
||||||
//!
|
//!
|
||||||
//! - Supported platforms: Windows, Linux and macOS.
|
//! - Supported platforms: Windows, Linux and macOS.
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.3]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs@2.0.3`
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`cfd48b3b`](https://github.com/tauri-apps/plugins-workspace/commit/cfd48b3b2ec0fccfc162197518694ed59ceda22c) ([#1941](https://github.com/tauri-apps/plugins-workspace/pull/1941) by [@Nipsuli](https://github.com/tauri-apps/plugins-workspace/../../Nipsuli)) Allow skipping sending `Origin` header in HTTP requests by setting `Origin` header to an empty string when calling `fetch`.
|
- [`cfd48b3b`](https://github.com/tauri-apps/plugins-workspace/commit/cfd48b3b2ec0fccfc162197518694ed59ceda22c) ([#1941](https://github.com/tauri-apps/plugins-workspace/pull/1941) by [@Nipsuli](https://github.com/tauri-apps/plugins-workspace/../../Nipsuli)) Allow skipping sending `Origin` header in HTTP requests by setting `Origin` header to an empty string when calling `fetch`.
|
||||||
@@ -291,6 +297,6 @@
|
|||||||
ha release!
|
ha release!
|
||||||
!
|
!
|
||||||
371\)) First v2 alpha release!
|
371\)) First v2 alpha release!
|
||||||
lease!
|
lease!
|
||||||
!
|
!
|
||||||
371\)) First v2 alpha release!
|
371\)) First v2 alpha release!
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-http"
|
name = "tauri-plugin-http"
|
||||||
version = "2.0.2"
|
version = "2.0.3"
|
||||||
description = "Access an HTTP client written in Rust."
|
description = "Access an HTTP client written in Rust."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -34,7 +34,7 @@ serde_json = { workspace = true }
|
|||||||
tauri = { workspace = true }
|
tauri = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { version = "1", features = ["sync", "macros"] }
|
tokio = { version = "1", features = ["sync", "macros"] }
|
||||||
tauri-plugin-fs = { path = "../fs", version = "2.0.2" }
|
tauri-plugin-fs = { path = "../fs", version = "2.0.3" }
|
||||||
urlpattern = "0.3"
|
urlpattern = "0.3"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
http = "1"
|
http = "1"
|
||||||
|
|||||||
@@ -191,6 +191,11 @@ pub async fn fetch<R: Runtime>(
|
|||||||
let name = HeaderName::from_str(&h)?;
|
let name = HeaderName::from_str(&h)?;
|
||||||
#[cfg(not(feature = "unsafe-headers"))]
|
#[cfg(not(feature = "unsafe-headers"))]
|
||||||
if is_unsafe_header(&name) {
|
if is_unsafe_header(&name) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
eprintln!("[\x1b[33mWARNING\x1b[0m] Skipping {name} header as it is a forbidden header per fetch spec https://fetch.spec.whatwg.org/#terminology-headers");
|
||||||
|
eprintln!("[\x1b[33mWARNING\x1b[0m] if keeping the header is a desired behavior, you can enable `unsafe-headers` feature flag in your Cargo.toml");
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/http)
|
|
||||||
//!
|
|
||||||
//! Access the HTTP client written in Rust.
|
//! Access the HTTP client written in Rust.
|
||||||
|
|
||||||
pub use reqwest;
|
pub use reqwest;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/localhost)
|
|
||||||
//!
|
|
||||||
//! Expose your apps assets through a localhost server instead of the default custom protocol.
|
//! Expose your apps assets through a localhost server instead of the default custom protocol.
|
||||||
//!
|
//!
|
||||||
//! **Note: This plugins brings considerable security risks and you should only use it if you know what your are doing. If in doubt, use the default custom protocol implementation.**
|
//! **Note: This plugins brings considerable security risks and you should only use it if you know what your are doing. If in doubt, use the default custom protocol implementation.**
|
||||||
@@ -46,6 +44,7 @@ type OnRequest = Option<Box<dyn Fn(&Request, &mut Response) + Send + Sync>>;
|
|||||||
|
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
port: u16,
|
port: u16,
|
||||||
|
host: Option<String>,
|
||||||
on_request: OnRequest,
|
on_request: OnRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +52,17 @@ impl Builder {
|
|||||||
pub fn new(port: u16) -> Self {
|
pub fn new(port: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
port,
|
port,
|
||||||
|
host: None,
|
||||||
on_request: None,
|
on_request: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the host the plugin binds to. Defaults to `localhost`.
|
||||||
|
pub fn host<H: Into<String>>(mut self, host: H) -> Self {
|
||||||
|
self.host = Some(host.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_request<F: Fn(&Request, &mut Response) + Send + Sync + 'static>(
|
pub fn on_request<F: Fn(&Request, &mut Response) + Send + Sync + 'static>(
|
||||||
mut self,
|
mut self,
|
||||||
f: F,
|
f: F,
|
||||||
@@ -67,6 +73,7 @@ impl Builder {
|
|||||||
|
|
||||||
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
|
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
|
||||||
let port = self.port;
|
let port = self.port;
|
||||||
|
let host = self.host.unwrap_or("localhost".to_string());
|
||||||
let on_request = self.on_request.take();
|
let on_request = self.on_request.take();
|
||||||
|
|
||||||
PluginBuilder::new("localhost")
|
PluginBuilder::new("localhost")
|
||||||
@@ -74,7 +81,7 @@ impl Builder {
|
|||||||
let asset_resolver = app.asset_resolver();
|
let asset_resolver = app.asset_resolver();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let server =
|
let server =
|
||||||
Server::http(format!("localhost:{port}")).expect("Unable to spawn server");
|
Server::http(format!("{host}:{port}")).expect("Unable to spawn server");
|
||||||
for req in server.incoming_requests() {
|
for req in server.incoming_requests() {
|
||||||
let path = req
|
let path = req
|
||||||
.url()
|
.url()
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.2]
|
||||||
|
|
||||||
|
- [`606fa08d`](https://github.com/tauri-apps/plugins-workspace/commit/606fa08dae1acd074b961fb360623f4c86f13ee8) ([#1997](https://github.com/tauri-apps/plugins-workspace/pull/1997) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) **Potentially breaking:** Updated `fern` from 0.6 to 0.7. This is technically a breaking change because `fern` is re-exported in `tauri-plugin-log`.
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-log"
|
name = "tauri-plugin-log"
|
||||||
version = "2.0.1"
|
version = "2.0.2"
|
||||||
description = "Configurable logging for your Tauri app."
|
description = "Configurable logging for your Tauri app."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -27,12 +27,12 @@ tauri-plugin = { workspace = true, features = ["build"] }
|
|||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tauri = { workspace = true }
|
tauri = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
byte-unit = "5"
|
byte-unit = "5"
|
||||||
log = { workspace = true, features = ["kv_unstable"] }
|
log = { workspace = true, features = ["kv_unstable"] }
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
fern = "0.6"
|
fern = "0.7"
|
||||||
thiserror = "1"
|
|
||||||
|
|
||||||
[target."cfg(target_os = \"android\")".dependencies]
|
[target."cfg(target_os = \"android\")".dependencies]
|
||||||
android_logger = "0.14"
|
android_logger = "0.14"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_LOG__=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 a,t;async function o(e,a,t){const o={kind:"Any"};return r("plugin:event|listen",{event:e,target:o,handler:n(a)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(e,n,a){const t=(new Error).stack?.split("\n").map((e=>e.split("@"))),o=t?.filter((([e,n])=>e.length>0&&"[native code]"!==n)),{file:i,line:c,keyValues:u}=a??{};let l=o?.[0]?.filter((e=>e.length>0)).join("@");"Error"===l&&(l="webview::unknown"),await r("plugin:log|log",{level:e,message:n,location:l,file:i,line:c,keyValues:u})}async function c(e){return await o("log://log",(n=>{const{level:r}=n.payload;let{message:a}=n.payload;a=a.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),e({message:a,level:r})}))}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"}(a||(a={})),function(e){e[e.Trace=1]="Trace",e[e.Debug=2]="Debug",e[e.Info=3]="Info",e[e.Warn=4]="Warn",e[e.Error=5]="Error"}(t||(t={})),e.attachConsole=async function(){return await c((({level:e,message:n})=>{switch(e){case t.Trace:console.log(n);break;case t.Debug:console.debug(n);break;case t.Info:console.info(n);break;case t.Warn:console.warn(n);break;case t.Error:console.error(n);break;default:throw new Error(`unknown log level ${e}`)}}))},e.attachLogger=c,e.debug=async function(e,n){await i(t.Debug,e,n)},e.error=async function(e,n){await i(t.Error,e,n)},e.info=async function(e,n){await i(t.Info,e,n)},e.trace=async function(e,n){await i(t.Trace,e,n)},e.warn=async function(e,n){await i(t.Warn,e,n)},e}({});Object.defineProperty(window.__TAURI__,"log",{value:__TAURI_PLUGIN_LOG__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_LOG__=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 a,t;async function o(e,a,t){const o={kind:"Any"};return r("plugin:event|listen",{event:e,target:o,handler:n(a)}).then((n=>async()=>async function(e,n){await r("plugin:event|unlisten",{event:e,eventId:n})}(e,n)))}async function i(e,n,a){const t=function(e){if(e){if(!e.startsWith("Error"))return e.split("\n").map((e=>e.split("@"))).filter((([e,n])=>e.length>0&&"[native code]"!==n))[2].filter((e=>e.length>0)).join("@");{const n=e.split("\n")[3].trim(),r=/at\s+(?<functionName>.*?)\s+\((?<fileName>.*?):(?<lineNumber>\d+):(?<columnNumber>\d+)\)/,a=n.match(r);if(a){const{functionName:e,fileName:n,lineNumber:r,columnNumber:t}=a.groups;return`${e}@${n}:${r}:${t}`}{const e=/at\s+(?<fileName>.*?):(?<lineNumber>\d+):(?<columnNumber>\d+)/,r=n.match(e);if(r){const{fileName:e,lineNumber:n,columnNumber:a}=r.groups;return`<anonymous>@${e}:${n}:${a}`}}}}}((new Error).stack),{file:o,line:i,keyValues:u}=a??{};await r("plugin:log|log",{level:e,message:n,location:t,file:o,line:i,keyValues:u})}async function u(e){return await o("log://log",(n=>{const{level:r}=n.payload;let{message:a}=n.payload;a=a.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),e({message:a,level:r})}))}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"}(a||(a={})),function(e){e[e.Trace=1]="Trace",e[e.Debug=2]="Debug",e[e.Info=3]="Info",e[e.Warn=4]="Warn",e[e.Error=5]="Error"}(t||(t={})),e.attachConsole=async function(){return await u((({level:e,message:n})=>{switch(e){case t.Trace:console.log(n);break;case t.Debug:console.debug(n);break;case t.Info:console.info(n);break;case t.Warn:console.warn(n);break;case t.Error:console.error(n);break;default:throw new Error(`unknown log level ${e}`)}}))},e.attachLogger=u,e.debug=async function(e,n){await i(t.Debug,e,n)},e.error=async function(e,n){await i(t.Error,e,n)},e.info=async function(e,n){await i(t.Info,e,n)},e.trace=async function(e,n){await i(t.Trace,e,n)},e.warn=async function(e,n){await i(t.Warn,e,n)},e}({});Object.defineProperty(window.__TAURI__,"log",{value:__TAURI_PLUGIN_LOG__})}
|
||||||
|
|||||||
@@ -44,24 +44,78 @@ enum LogLevel {
|
|||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCallerLocation(stack?: string) {
|
||||||
|
if (!stack) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.startsWith('Error')) {
|
||||||
|
// Assume it's Chromium V8
|
||||||
|
//
|
||||||
|
// Error
|
||||||
|
// at baz (filename.js:10:15)
|
||||||
|
// at bar (filename.js:6:3)
|
||||||
|
// at foo (filename.js:2:3)
|
||||||
|
// at filename.js:13:1
|
||||||
|
|
||||||
|
const lines = stack.split('\n')
|
||||||
|
// Find the third line (caller's caller of the current location)
|
||||||
|
const callerLine = lines[3].trim()
|
||||||
|
|
||||||
|
const regex =
|
||||||
|
/at\s+(?<functionName>.*?)\s+\((?<fileName>.*?):(?<lineNumber>\d+):(?<columnNumber>\d+)\)/
|
||||||
|
const match = callerLine.match(regex)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const { functionName, fileName, lineNumber, columnNumber } =
|
||||||
|
match.groups as {
|
||||||
|
functionName: string
|
||||||
|
fileName: string
|
||||||
|
lineNumber: string
|
||||||
|
columnNumber: string
|
||||||
|
}
|
||||||
|
return `${functionName}@${fileName}:${lineNumber}:${columnNumber}`
|
||||||
|
} else {
|
||||||
|
// Handle cases where the regex does not match (e.g., last line without function name)
|
||||||
|
const regexNoFunction =
|
||||||
|
/at\s+(?<fileName>.*?):(?<lineNumber>\d+):(?<columnNumber>\d+)/
|
||||||
|
const matchNoFunction = callerLine.match(regexNoFunction)
|
||||||
|
if (matchNoFunction) {
|
||||||
|
const { fileName, lineNumber, columnNumber } =
|
||||||
|
matchNoFunction.groups as {
|
||||||
|
fileName: string
|
||||||
|
lineNumber: string
|
||||||
|
columnNumber: string
|
||||||
|
}
|
||||||
|
return `<anonymous>@${fileName}:${lineNumber}:${columnNumber}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Assume it's Webkit JavaScriptCore, example:
|
||||||
|
//
|
||||||
|
// baz@filename.js:10:24
|
||||||
|
// bar@filename.js:6:6
|
||||||
|
// foo@filename.js:2:6
|
||||||
|
// global code@filename.js:13:4
|
||||||
|
|
||||||
|
const traces = stack.split('\n').map((line) => line.split('@'))
|
||||||
|
const filtered = traces.filter(([name, location]) => {
|
||||||
|
return name.length > 0 && location !== '[native code]'
|
||||||
|
})
|
||||||
|
// Find the third line (caller's caller of the current location)
|
||||||
|
return filtered[2].filter((v) => v.length > 0).join('@')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function log(
|
async function log(
|
||||||
level: LogLevel,
|
level: LogLevel,
|
||||||
message: string,
|
message: string,
|
||||||
options?: LogOptions
|
options?: LogOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const traces = new Error().stack?.split('\n').map((line) => line.split('@'))
|
const location = getCallerLocation(new Error().stack)
|
||||||
|
|
||||||
const filtered = traces?.filter(([name, location]) => {
|
|
||||||
return name.length > 0 && location !== '[native code]'
|
|
||||||
})
|
|
||||||
|
|
||||||
const { file, line, keyValues } = options ?? {}
|
const { file, line, keyValues } = options ?? {}
|
||||||
|
|
||||||
let location = filtered?.[0]?.filter((v) => v.length > 0).join('@')
|
|
||||||
if (location === 'Error') {
|
|
||||||
location = 'webview::unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
await invoke('plugin:log|log', {
|
await invoke('plugin:log|log', {
|
||||||
level,
|
level,
|
||||||
message,
|
message,
|
||||||
|
|||||||
+9
-17
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/log)
|
|
||||||
//!
|
|
||||||
//! Logging for Tauri applications.
|
//! Logging for Tauri applications.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
@@ -33,7 +31,7 @@ use tauri::{AppHandle, Emitter};
|
|||||||
pub use fern;
|
pub use fern;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
pub const WEBVIEW_TARGET: &str = "Webview";
|
pub const WEBVIEW_TARGET: &str = "webview";
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
mod ios {
|
mod ios {
|
||||||
@@ -230,22 +228,16 @@ fn log(
|
|||||||
line: Option<u32>,
|
line: Option<u32>,
|
||||||
key_values: Option<HashMap<String, String>>,
|
key_values: Option<HashMap<String, String>>,
|
||||||
) {
|
) {
|
||||||
let location = location.unwrap_or("webview");
|
|
||||||
|
|
||||||
let level = log::Level::from(level);
|
let level = log::Level::from(level);
|
||||||
|
|
||||||
let metadata = log::MetadataBuilder::new()
|
let target = if let Some(location) = location {
|
||||||
.level(level)
|
format!("{WEBVIEW_TARGET}:{location}")
|
||||||
.target(WEBVIEW_TARGET)
|
} else {
|
||||||
.build();
|
WEBVIEW_TARGET.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
let mut builder = RecordBuilder::new();
|
let mut builder = RecordBuilder::new();
|
||||||
builder
|
builder.level(level).target(&target).file(file).line(line);
|
||||||
.level(level)
|
|
||||||
.metadata(metadata)
|
|
||||||
.target(location)
|
|
||||||
.file(file)
|
|
||||||
.line(line);
|
|
||||||
|
|
||||||
let key_values = key_values.unwrap_or_default();
|
let key_values = key_values.unwrap_or_default();
|
||||||
let mut kv = HashMap::new();
|
let mut kv = HashMap::new();
|
||||||
@@ -380,8 +372,8 @@ impl Builder {
|
|||||||
/// .clear_targets()
|
/// .clear_targets()
|
||||||
/// .targets([
|
/// .targets([
|
||||||
/// Target::new(TargetKind::Webview),
|
/// Target::new(TargetKind::Webview),
|
||||||
/// Target::new(TargetKind::LogDir { file_name: Some("webview".into()) }).filter(|metadata| metadata.target() == WEBVIEW_TARGET),
|
/// Target::new(TargetKind::LogDir { file_name: Some("webview".into()) }).filter(|metadata| metadata.target().starts_with(WEBVIEW_TARGET)),
|
||||||
/// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| metadata.target() != WEBVIEW_TARGET),
|
/// Target::new(TargetKind::LogDir { file_name: Some("rust".into()) }).filter(|metadata| !metadata.target().starts_with(WEBVIEW_TARGET)),
|
||||||
/// ]);
|
/// ]);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn targets(mut self, targets: impl IntoIterator<Item = Target>) -> Self {
|
pub fn targets(mut self, targets: impl IntoIterator<Item = Target>) -> Self {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_NOTIFICATION__=function(i){"use strict";function t(i,t,n,e){if("a"===n&&!e)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?i!==t||!e:!t.has(i))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?e:"a"===n?e.call(i):e?e.value:t.get(i)}function n(i,t,n,e,o){if("function"==typeof t?i!==t||!o:!t.has(i))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(i,n),n}var e,o,a,r,c,s;"function"==typeof SuppressedError&&SuppressedError;class l{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,e.set(this,(()=>{})),o.set(this,0),a.set(this,{}),this.id=function(i,t=!1){return window.__TAURI_INTERNALS__.transformCallback(i,t)}((({message:i,id:r})=>{if(r===t(this,o,"f")){n(this,o,r+1),t(this,e,"f").call(this,i);const c=Object.keys(t(this,a,"f"));if(c.length>0){let i=r+1;for(const n of c.sort()){if(parseInt(n)!==i)break;{const o=t(this,a,"f")[n];delete t(this,a,"f")[n],t(this,e,"f").call(this,o),i+=1}}n(this,o,i)}}else t(this,a,"f")[r.toString()]=i}))}set onmessage(i){n(this,e,i)}get onmessage(){return t(this,e,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}e=new WeakMap,o=new WeakMap,a=new WeakMap;class u{constructor(i,t,n){this.plugin=i,this.event=t,this.channelId=n}async unregister(){return d(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function f(i,t,n){const e=new l;return e.onmessage=n,d(`plugin:${i}|register_listener`,{event:t,handler:e}).then((()=>new u(i,t,e.id)))}async function d(i,t={},n){return window.__TAURI_INTERNALS__.invoke(i,t,n)}i.ScheduleEvery=void 0,(r=i.ScheduleEvery||(i.ScheduleEvery={})).Year="year",r.Month="month",r.TwoWeeks="twoWeeks",r.Week="week",r.Day="day",r.Hour="hour",r.Minute="minute",r.Second="second";return i.Importance=void 0,(c=i.Importance||(i.Importance={}))[c.None=0]="None",c[c.Min=1]="Min",c[c.Low=2]="Low",c[c.Default=3]="Default",c[c.High=4]="High",i.Visibility=void 0,(s=i.Visibility||(i.Visibility={}))[s.Secret=-1]="Secret",s[s.Private=0]="Private",s[s.Public=1]="Public",i.Schedule=class{static at(i,t=!1,n=!1){return{at:{date:i,repeating:t,allowWhileIdle:n},interval:void 0,every:void 0}}static interval(i,t=!1){return{at:void 0,interval:{interval:i,allowWhileIdle:t},every:void 0}}static every(i,t,n=!1){return{at:void 0,interval:void 0,every:{interval:i,count:t,allowWhileIdle:n}}}},i.active=async function(){return await d("plugin:notification|get_active")},i.cancel=async function(i){await d("plugin:notification|cancel",{notifications:i})},i.cancelAll=async function(){await d("plugin:notification|cancel")},i.channels=async function(){return await d("plugin:notification|listChannels")},i.createChannel=async function(i){await d("plugin:notification|create_channel",{...i})},i.isPermissionGranted=async function(){return"default"!==window.Notification.permission?await Promise.resolve("granted"===window.Notification.permission):await d("plugin:notification|is_permission_granted")},i.onAction=async function(i){return await f("notification","actionPerformed",i)},i.onNotificationReceived=async function(i){return await f("notification","notification",i)},i.pending=async function(){return await d("plugin:notification|get_pending")},i.registerActionTypes=async function(i){await d("plugin:notification|register_action_types",{types:i})},i.removeActive=async function(i){await d("plugin:notification|remove_active",{notifications:i})},i.removeAllActive=async function(){await d("plugin:notification|remove_active")},i.removeChannel=async function(i){await d("plugin:notification|delete_channel",{id:i})},i.requestPermission=async function(){return await window.Notification.requestPermission()},i.sendNotification=function(i){"string"==typeof i?new window.Notification(i):new window.Notification(i.title,i)},i}({});Object.defineProperty(window.__TAURI__,"notification",{value:__TAURI_PLUGIN_NOTIFICATION__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_NOTIFICATION__=function(i){"use strict";function t(i,t,n,e){if("a"===n&&!e)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?i!==t||!e:!t.has(i))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?e:"a"===n?e.call(i):e?e.value:t.get(i)}function n(i,t,n,e,o){if("function"==typeof t?i!==t||!o:!t.has(i))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(i,n),n}var e,o,a;"function"==typeof SuppressedError&&SuppressedError;const r="__TAURI_TO_IPC_KEY__";class c{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,e.set(this,(()=>{})),o.set(this,0),a.set(this,{}),this.id=function(i,t=!1){return window.__TAURI_INTERNALS__.transformCallback(i,t)}((({message:i,id:r})=>{if(r===t(this,o,"f")){n(this,o,r+1),t(this,e,"f").call(this,i);const c=Object.keys(t(this,a,"f"));if(c.length>0){let i=r+1;for(const n of c.sort()){if(parseInt(n)!==i)break;{const o=t(this,a,"f")[n];delete t(this,a,"f")[n],t(this,e,"f").call(this,o),i+=1}}n(this,o,i)}}else t(this,a,"f")[r.toString()]=i}))}set onmessage(i){n(this,e,i)}get onmessage(){return t(this,e,"f")}[(e=new WeakMap,o=new WeakMap,a=new WeakMap,r)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[r]()}}class s{constructor(i,t,n){this.plugin=i,this.event=t,this.channelId=n}async unregister(){return u(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function l(i,t,n){const e=new c;return e.onmessage=n,u(`plugin:${i}|registerListener`,{event:t,handler:e}).then((()=>new s(i,t,e.id)))}async function u(i,t={},n){return window.__TAURI_INTERNALS__.invoke(i,t,n)}var f,d,w;i.ScheduleEvery=void 0,(f=i.ScheduleEvery||(i.ScheduleEvery={})).Year="year",f.Month="month",f.TwoWeeks="twoWeeks",f.Week="week",f.Day="day",f.Hour="hour",f.Minute="minute",f.Second="second";return i.Importance=void 0,(d=i.Importance||(i.Importance={}))[d.None=0]="None",d[d.Min=1]="Min",d[d.Low=2]="Low",d[d.Default=3]="Default",d[d.High=4]="High",i.Visibility=void 0,(w=i.Visibility||(i.Visibility={}))[w.Secret=-1]="Secret",w[w.Private=0]="Private",w[w.Public=1]="Public",i.Schedule=class{static at(i,t=!1,n=!1){return{at:{date:i,repeating:t,allowWhileIdle:n},interval:void 0,every:void 0}}static interval(i,t=!1){return{at:void 0,interval:{interval:i,allowWhileIdle:t},every:void 0}}static every(i,t,n=!1){return{at:void 0,interval:void 0,every:{interval:i,count:t,allowWhileIdle:n}}}},i.active=async function(){return await u("plugin:notification|get_active")},i.cancel=async function(i){await u("plugin:notification|cancel",{notifications:i})},i.cancelAll=async function(){await u("plugin:notification|cancel")},i.channels=async function(){return await u("plugin:notification|listChannels")},i.createChannel=async function(i){await u("plugin:notification|create_channel",{...i})},i.isPermissionGranted=async function(){return"default"!==window.Notification.permission?await Promise.resolve("granted"===window.Notification.permission):await u("plugin:notification|is_permission_granted")},i.onAction=async function(i){return await l("notification","actionPerformed",i)},i.onNotificationReceived=async function(i){return await l("notification","notification",i)},i.pending=async function(){return await u("plugin:notification|get_pending")},i.registerActionTypes=async function(i){await u("plugin:notification|register_action_types",{types:i})},i.removeActive=async function(i){await u("plugin:notification|remove_active",{notifications:i})},i.removeAllActive=async function(){await u("plugin:notification|remove_active")},i.removeChannel=async function(i){await u("plugin:notification|delete_channel",{id:i})},i.requestPermission=async function(){return await window.Notification.requestPermission()},i.sendNotification=function(i){"string"==typeof i?new window.Notification(i):new window.Notification(i.title,i)},i}({});Object.defineProperty(window.__TAURI__,"notification",{value:__TAURI_PLUGIN_NOTIFICATION__})}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/notification)
|
|
||||||
//!
|
|
||||||
//! Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API.
|
//! Send message notifications (brief auto-expiring OS window element) to your user. Can also be used with the Notification Web API.
|
||||||
|
|
||||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/os)
|
|
||||||
//!
|
|
||||||
//! Read information about the operating system.
|
//! Read information about the operating system.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.3]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs@2.0.3`
|
||||||
|
|
||||||
## \[2.0.2]
|
## \[2.0.2]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-persisted-scope"
|
name = "tauri-plugin-persisted-scope"
|
||||||
version = "2.0.2"
|
version = "2.0.3"
|
||||||
description = "Save filesystem and asset scopes and restore them when the app is reopened."
|
description = "Save filesystem and asset scopes and restore them when the app is reopened."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -27,7 +27,7 @@ log = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
aho-corasick = "1"
|
aho-corasick = "1"
|
||||||
bincode = "1"
|
bincode = "1"
|
||||||
tauri-plugin-fs = { path = "../fs", version = "2.0.2" }
|
tauri-plugin-fs = { path = "../fs", version = "2.0.3" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
protocol-asset = ["tauri/protocol-asset"]
|
protocol-asset = ["tauri/protocol-asset"]
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/persisted-scope)
|
|
||||||
//!
|
|
||||||
//! Save filesystem and asset scopes and restore them when the app is reopened.
|
//! Save filesystem and asset scopes and restore them when the app is reopened.
|
||||||
|
|
||||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/positioner)
|
|
||||||
//!
|
|
||||||
//! A plugin for Tauri that helps position your windows at well-known locations.
|
//! A plugin for Tauri that helps position your windows at well-known locations.
|
||||||
//!
|
//!
|
||||||
//! # Cargo features
|
//! # Cargo features
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/process)
|
|
||||||
//!
|
|
||||||
//! This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin.
|
//! This plugin provides APIs to access the current process. To spawn child processes, see the [`shell`](https://github.com/tauri-apps/tauri-plugin-shell) plugin.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_SHELL__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:o})=>{if(o===t(this,i,"f")){s(this,i,o+1),t(this,n,"f").call(this,e);const a=Object.keys(t(this,r,"f"));if(a.length>0){let e=o+1;for(const s of a.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[o.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,r=new WeakMap;class h{constructor(){this.eventListeners=Object.create(null)}addListener(e,t){return this.on(e,t)}removeListener(e,t){return this.off(e,t)}on(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}once(e,t){const s=n=>{this.removeListener(e,s),t(n)};return this.addListener(e,s)}off(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter((e=>e!==t))),this}removeAllListeners(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}emit(e,t){if(e in this.eventListeners){const s=this.eventListeners[e];for(const e of s)e(t);return!0}return!1}listenerCount(e){return e in this.eventListeners?this.eventListeners[e].length:0}prependListener(e,t){return e in this.eventListeners?this.eventListeners[e].unshift(t):this.eventListeners[e]=[t],this}prependOnceListener(e,t){const s=n=>{this.removeListener(e,s),t(n)};return this.prependListener(e,s)}}class c{constructor(e){this.pid=e}async write(e){await a("plugin:shell|stdin_write",{pid:this.pid,buffer:e})}async kill(){await a("plugin:shell|kill",{cmd:"killChild",pid:this.pid})}}class l extends h{constructor(e,t=[],s){super(),this.stdout=new h,this.stderr=new h,this.program=e,this.args="string"==typeof t?[t]:t,this.options=s??{}}static create(e,t=[],s){return new l(e,t,s)}static sidecar(e,t=[],s){const n=new l(e,t,s);return n.options.sidecar=!0,n}async spawn(){const e=this.program,t=this.args,s=this.options;"object"==typeof t&&Object.freeze(t);const n=new o;return n.onmessage=e=>{switch(e.event){case"Error":this.emit("error",e.payload);break;case"Terminated":this.emit("close",e.payload);break;case"Stdout":this.stdout.emit("data",e.payload);break;case"Stderr":this.stderr.emit("data",e.payload)}},await a("plugin:shell|spawn",{program:e,args:t,options:s,onEvent:n}).then((e=>new c(e)))}async execute(){const e=this.program,t=this.args,s=this.options;return"object"==typeof t&&Object.freeze(t),await a("plugin:shell|execute",{program:e,args:t,options:s})}}return e.Child=c,e.Command=l,e.EventEmitter=h,e.open=async function(e,t){await a("plugin:shell|open",{path:e,with:t})},e}({});Object.defineProperty(window.__TAURI__,"shell",{value:__TAURI_PLUGIN_SHELL__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_SHELL__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:o})=>{if(o===t(this,i,"f")){s(this,i,o+1),t(this,n,"f").call(this,e);const a=Object.keys(t(this,r,"f"));if(a.length>0){let e=o+1;for(const s of a.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[o.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,i=new WeakMap,r=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function h(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}class c{constructor(){this.eventListeners=Object.create(null)}addListener(e,t){return this.on(e,t)}removeListener(e,t){return this.off(e,t)}on(e,t){return e in this.eventListeners?this.eventListeners[e].push(t):this.eventListeners[e]=[t],this}once(e,t){const s=n=>{this.removeListener(e,s),t(n)};return this.addListener(e,s)}off(e,t){return e in this.eventListeners&&(this.eventListeners[e]=this.eventListeners[e].filter((e=>e!==t))),this}removeAllListeners(e){return e?delete this.eventListeners[e]:this.eventListeners=Object.create(null),this}emit(e,t){if(e in this.eventListeners){const s=this.eventListeners[e];for(const e of s)e(t);return!0}return!1}listenerCount(e){return e in this.eventListeners?this.eventListeners[e].length:0}prependListener(e,t){return e in this.eventListeners?this.eventListeners[e].unshift(t):this.eventListeners[e]=[t],this}prependOnceListener(e,t){const s=n=>{this.removeListener(e,s),t(n)};return this.prependListener(e,s)}}class l{constructor(e){this.pid=e}async write(e){await h("plugin:shell|stdin_write",{pid:this.pid,buffer:e})}async kill(){await h("plugin:shell|kill",{cmd:"killChild",pid:this.pid})}}class u extends c{constructor(e,t=[],s){super(),this.stdout=new c,this.stderr=new c,this.program=e,this.args="string"==typeof t?[t]:t,this.options=s??{}}static create(e,t=[],s){return new u(e,t,s)}static sidecar(e,t=[],s){const n=new u(e,t,s);return n.options.sidecar=!0,n}async spawn(){const e=this.program,t=this.args,s=this.options;"object"==typeof t&&Object.freeze(t);const n=new a;return n.onmessage=e=>{switch(e.event){case"Error":this.emit("error",e.payload);break;case"Terminated":this.emit("close",e.payload);break;case"Stdout":this.stdout.emit("data",e.payload);break;case"Stderr":this.stderr.emit("data",e.payload)}},await h("plugin:shell|spawn",{program:e,args:t,options:s,onEvent:n}).then((e=>new l(e)))}async execute(){const e=this.program,t=this.args,s=this.options;return"object"==typeof t&&Object.freeze(t),await h("plugin:shell|execute",{program:e,args:t,options:s})}}return e.Child=l,e.Command=u,e.EventEmitter=c,e.open=async function(e,t){await h("plugin:shell|open",{path:e,with:t})},e}({});Object.defineProperty(window.__TAURI__,"shell",{value:__TAURI_PLUGIN_SHELL__})}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/shell)
|
|
||||||
//!
|
|
||||||
//! Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application.
|
//! Access the system shell. Allows you to spawn child processes and manage files and URLs using their default application.
|
||||||
|
|
||||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/single-instance)
|
|
||||||
//!
|
|
||||||
//! Ensure a single instance of your tauri app is running.
|
//! Ensure a single instance of your tauri app is running.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
|
- [`0ca4cc91`](https://github.com/tauri-apps/plugins-workspace/commit/0ca4cc914c5ea995c98f9e60a2ab49827c219350) ([#1963](https://github.com/tauri-apps/plugins-workspace/pull/1963) by [@yoggys](https://github.com/tauri-apps/plugins-workspace/../../yoggys)) Fixed incorrect documentation of the select method in the Database class.
|
||||||
|
|
||||||
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
||||||
|
|
||||||
## \[2.0.0]
|
## \[2.0.0]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-sql"
|
name = "tauri-plugin-sql"
|
||||||
version = "2.0.1"
|
version = "2.0.2"
|
||||||
description = "Interface with SQL databases."
|
description = "Interface with SQL databases."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|||||||
@@ -129,12 +129,12 @@ export default class Database {
|
|||||||
* ```ts
|
* ```ts
|
||||||
* // for sqlite & postgres
|
* // for sqlite & postgres
|
||||||
* const result = await db.select(
|
* const result = await db.select(
|
||||||
* "SELECT * from todos WHERE id = $1", id
|
* "SELECT * from todos WHERE id = $1", [ id ]
|
||||||
* );
|
* );
|
||||||
*
|
*
|
||||||
* // for mysql
|
* // for mysql
|
||||||
* const result = await db.select(
|
* const result = await db.select(
|
||||||
* "SELECT * from todos WHERE id = ?", id
|
* "SELECT * from todos WHERE id = ?", [ id ]
|
||||||
* );
|
* );
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-sql",
|
"name": "@tauri-apps/plugin-sql",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "Interface with SQL databases",
|
"description": "Interface with SQL databases",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub(crate) async fn load<R: Runtime>(
|
|||||||
pool.migrate(&migrator).await?;
|
pool.migrate(&migrator).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
db_instances.0.lock().await.insert(db.clone(), pool);
|
db_instances.0.write().await.insert(db.clone(), pool);
|
||||||
|
|
||||||
Ok(db)
|
Ok(db)
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ pub(crate) async fn close(
|
|||||||
db_instances: State<'_, DbInstances>,
|
db_instances: State<'_, DbInstances>,
|
||||||
db: Option<String>,
|
db: Option<String>,
|
||||||
) -> Result<bool, crate::Error> {
|
) -> Result<bool, crate::Error> {
|
||||||
let mut instances = db_instances.0.lock().await;
|
let instances = db_instances.0.read().await;
|
||||||
|
|
||||||
let pools = if let Some(db) = db {
|
let pools = if let Some(db) = db {
|
||||||
vec![db]
|
vec![db]
|
||||||
@@ -45,9 +45,7 @@ pub(crate) async fn close(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for pool in pools {
|
for pool in pools {
|
||||||
let db = instances
|
let db = instances.get(&pool).ok_or(Error::DatabaseNotLoaded(pool))?;
|
||||||
.get_mut(&pool)
|
|
||||||
.ok_or(Error::DatabaseNotLoaded(pool))?;
|
|
||||||
db.close().await;
|
db.close().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +60,9 @@ pub(crate) async fn execute(
|
|||||||
query: String,
|
query: String,
|
||||||
values: Vec<JsonValue>,
|
values: Vec<JsonValue>,
|
||||||
) -> Result<(u64, LastInsertId), crate::Error> {
|
) -> Result<(u64, LastInsertId), crate::Error> {
|
||||||
let mut instances = db_instances.0.lock().await;
|
let instances = db_instances.0.read().await;
|
||||||
|
|
||||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||||
db.execute(query, values).await
|
db.execute(query, values).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,8 +73,8 @@ pub(crate) async fn select(
|
|||||||
query: String,
|
query: String,
|
||||||
values: Vec<JsonValue>,
|
values: Vec<JsonValue>,
|
||||||
) -> Result<Vec<IndexMap<String, JsonValue>>, crate::Error> {
|
) -> Result<Vec<IndexMap<String, JsonValue>>, crate::Error> {
|
||||||
let mut instances = db_instances.0.lock().await;
|
let instances = db_instances.0.read().await;
|
||||||
|
|
||||||
let db = instances.get_mut(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
let db = instances.get(&db).ok_or(Error::DatabaseNotLoaded(db))?;
|
||||||
db.select(query, values).await
|
db.select(query, values).await
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-8
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/sql)
|
|
||||||
//!
|
|
||||||
//! Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx). It supports the `sqlite`, `mysql` and `postgres` drivers, enabled by a Cargo feature.
|
//! Interface with SQL databases through [sqlx](https://github.com/launchbadge/sqlx). It supports the `sqlite`, `mysql` and `postgres` drivers, enabled by a Cargo feature.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
@@ -29,12 +27,12 @@ use tauri::{
|
|||||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||||
Manager, RunEvent, Runtime,
|
Manager, RunEvent, Runtime,
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct DbInstances(pub Mutex<HashMap<String, DbPool>>);
|
pub struct DbInstances(pub RwLock<HashMap<String, DbPool>>);
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -104,6 +102,15 @@ impl MigrationSource<'static> for MigrationList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allows blocking on async code without creating a nested runtime.
|
||||||
|
fn run_async_command<F: std::future::Future>(cmd: F) -> F::Output {
|
||||||
|
if tokio::runtime::Handle::try_current().is_ok() {
|
||||||
|
tokio::task::block_in_place(|| tokio::runtime::Handle::current().block_on(cmd))
|
||||||
|
} else {
|
||||||
|
tauri::async_runtime::block_on(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Tauri SQL plugin builder.
|
/// Tauri SQL plugin builder.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
@@ -138,9 +145,9 @@ impl Builder {
|
|||||||
.setup(|app, api| {
|
.setup(|app, api| {
|
||||||
let config = api.config().clone().unwrap_or_default();
|
let config = api.config().clone().unwrap_or_default();
|
||||||
|
|
||||||
tauri::async_runtime::block_on(async move {
|
run_async_command(async move {
|
||||||
let instances = DbInstances::default();
|
let instances = DbInstances::default();
|
||||||
let mut lock = instances.0.lock().await;
|
let mut lock = instances.0.write().await;
|
||||||
|
|
||||||
for db in config.preload {
|
for db in config.preload {
|
||||||
let pool = DbPool::connect(&db, app).await?;
|
let pool = DbPool::connect(&db, app).await?;
|
||||||
@@ -166,9 +173,9 @@ impl Builder {
|
|||||||
})
|
})
|
||||||
.on_event(|app, event| {
|
.on_event(|app, event| {
|
||||||
if let RunEvent::Exit = event {
|
if let RunEvent::Exit = event {
|
||||||
tauri::async_runtime::block_on(async move {
|
run_async_command(async move {
|
||||||
let instances = &*app.state::<DbInstances>();
|
let instances = &*app.state::<DbInstances>();
|
||||||
let instances = instances.0.lock().await;
|
let instances = instances.0.read().await;
|
||||||
for value in instances.values() {
|
for value in instances.values() {
|
||||||
value.close().await;
|
value.close().await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,11 @@ impl DbPool {
|
|||||||
}
|
}
|
||||||
Ok(Self::Postgres(Pool::connect(conn_url).await?))
|
Ok(Self::Postgres(Pool::connect(conn_url).await?))
|
||||||
}
|
}
|
||||||
|
#[cfg(not(any(feature = "sqlite", feature = "postgres", feature = "mysql")))]
|
||||||
|
_ => Err(crate::Error::InvalidDbUrl(format!(
|
||||||
|
"{conn_url} - No database driver enabled!"
|
||||||
|
))),
|
||||||
|
#[cfg(any(feature = "sqlite", feature = "postgres", feature = "mysql"))]
|
||||||
_ => Err(crate::Error::InvalidDbUrl(conn_url.to_string())),
|
_ => Err(crate::Error::InvalidDbUrl(conn_url.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.0.3",
|
"@tauri-apps/cli": "2.1.0",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
"typescript": "^5.4.7"
|
"typescript": "^5.4.7"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/store)
|
|
||||||
//!
|
|
||||||
//! Simple, persistent key-value store.
|
//! Simple, persistent key-value store.
|
||||||
|
|
||||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/stronghold)
|
|
||||||
//!
|
|
||||||
//! Store secrets and keys using the [IOTA Stronghold](https://github.com/iotaledger/stronghold.rs) encrypted database and secure runtime.
|
//! Store secrets and keys using the [IOTA Stronghold](https://github.com/iotaledger/stronghold.rs) encrypted database and secure runtime.
|
||||||
|
|
||||||
#![doc(
|
#![doc(
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ flate2 = { version = "1", optional = true }
|
|||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dunce = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["rustls-tls", "zip"]
|
default = ["rustls-tls", "zip"]
|
||||||
zip = ["dep:zip", "dep:tar", "dep:flate2"]
|
zip = ["dep:zip", "dep:tar", "dep:flate2"]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r,a;"function"==typeof SuppressedError&&SuppressedError;class o{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:a})=>{if(a===t(this,i,"f")){s(this,i,a+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,r,"f"));if(o.length>0){let e=a+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[a.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function d(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}n=new WeakMap,i=new WeakMap,r=new WeakMap;class l{get rid(){return t(this,a,"f")}constructor(e){a.set(this,void 0),s(this,a,e)}async close(){return d("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;class c extends l{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e,t){const s=new o;e&&(s.onmessage=e);const n=await d("plugin:updater|download",{onEvent:s,rid:this.rid,...t});this.downloadedBytes=new l(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await d("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e,t){const s=new o;e&&(s.onmessage=e),await d("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...t})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=c,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await d("plugin:updater|check",{...e}).then((e=>e.available?new c(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_UPDATER__=function(e){"use strict";function t(e,t,s,n){if("a"===s&&!n)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!n:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===s?n:"a"===s?n.call(e):n?n.value:t.get(e)}function s(e,t,s,n,i){if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,s),s}var n,i,r,a;"function"==typeof SuppressedError&&SuppressedError;const o="__TAURI_TO_IPC_KEY__";class d{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,n.set(this,(()=>{})),i.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:a})=>{if(a===t(this,i,"f")){s(this,i,a+1),t(this,n,"f").call(this,e);const o=Object.keys(t(this,r,"f"));if(o.length>0){let e=a+1;for(const s of o.sort()){if(parseInt(s)!==e)break;{const i=t(this,r,"f")[s];delete t(this,r,"f")[s],t(this,n,"f").call(this,i),e+=1}}s(this,i,e)}}else t(this,r,"f")[a.toString()]=e}))}set onmessage(e){s(this,n,e)}get onmessage(){return t(this,n,"f")}[(n=new WeakMap,i=new WeakMap,r=new WeakMap,o)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[o]()}}async function l(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}class c{get rid(){return t(this,a,"f")}constructor(e){a.set(this,void 0),s(this,a,e)}async close(){return l("plugin:resources|close",{rid:this.rid})}}a=new WeakMap;class h extends c{constructor(e){super(e.rid),this.available=e.available,this.currentVersion=e.currentVersion,this.version=e.version,this.date=e.date,this.body=e.body}async download(e,t){const s=new d;e&&(s.onmessage=e);const n=await l("plugin:updater|download",{onEvent:s,rid:this.rid,...t});this.downloadedBytes=new c(n)}async install(){if(!this.downloadedBytes)throw new Error("Update.install called before Update.download");await l("plugin:updater|install",{updateRid:this.rid,bytesRid:this.downloadedBytes.rid}),this.downloadedBytes=void 0}async downloadAndInstall(e,t){const s=new d;e&&(s.onmessage=e),await l("plugin:updater|download_and_install",{onEvent:s,rid:this.rid,...t})}async close(){await(this.downloadedBytes?.close()),await super.close()}}return e.Update=h,e.check=async function(e){return e?.headers&&(e.headers=Array.from(new Headers(e.headers).entries())),await l("plugin:updater|check",{...e}).then((e=>e.available?new h(e):null))},e}({});Object.defineProperty(window.__TAURI__,"updater",{value:__TAURI_PLUGIN_UPDATER__})}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/updater)
|
|
||||||
//!
|
|
||||||
//! In-app updates for Tauri applications.
|
//! In-app updates for Tauri applications.
|
||||||
//!
|
//!
|
||||||
//! - Supported platforms: Windows, Linux and macOS.crypted database and secure runtime.
|
//! - Supported platforms: Windows, Linux and macOS.crypted database and secure runtime.
|
||||||
|
|||||||
@@ -616,12 +616,16 @@ impl Update {
|
|||||||
.chain(self.installer_args())
|
.chain(self.installer_args())
|
||||||
.collect(),
|
.collect(),
|
||||||
WindowsUpdaterType::Msi { path, .. } => {
|
WindowsUpdaterType::Msi { path, .. } => {
|
||||||
let escaped_args = current_args
|
msi_args = if !current_args.is_empty() {
|
||||||
.iter()
|
let escaped_args = current_args
|
||||||
.map(escape_msi_property_arg)
|
.iter()
|
||||||
.collect::<Vec<_>>()
|
.map(escape_msi_property_arg)
|
||||||
.join(" ");
|
.collect::<Vec<_>>()
|
||||||
msi_args = OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""));
|
.join(" ");
|
||||||
|
OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""))
|
||||||
|
} else {
|
||||||
|
OsString::new()
|
||||||
|
};
|
||||||
|
|
||||||
[OsStr::new("/i"), path.as_os_str()]
|
[OsStr::new("/i"), path.as_os_str()]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -629,7 +633,7 @@ impl Update {
|
|||||||
.chain(once(OsStr::new("/promptrestart")))
|
.chain(once(OsStr::new("/promptrestart")))
|
||||||
.chain(self.installer_args())
|
.chain(self.installer_args())
|
||||||
.chain(once(OsStr::new("AUTOLAUNCHAPP=True")))
|
.chain(once(OsStr::new("AUTOLAUNCHAPP=True")))
|
||||||
.chain(once(msi_args.as_os_str()))
|
.chain(once(msi_args.as_os_str()).filter(|a| !a.is_empty()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[target.'cfg(windows)']
|
||||||
|
runner = "powershell -Command Start-Process -Verb runAs -FilePath"
|
||||||
|
|
||||||
|
[target.x86_64-unknown-linux-gnu]
|
||||||
|
runner = 'sudo -E'
|
||||||
@@ -14,5 +14,8 @@ tauri-plugin-updater = { path = "../.." }
|
|||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
time = { version = "0.3", features = ["formatting"] }
|
time = { version = "0.3", features = ["formatting"] }
|
||||||
|
|
||||||
|
[target."cfg(windows)".dependencies]
|
||||||
|
dunce = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
prod = ["tauri/custom-protocol"]
|
prod = ["tauri/custom-protocol"]
|
||||||
|
|||||||
@@ -14,29 +14,31 @@ fn main() {
|
|||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
let handle = app.handle().clone();
|
let handle = app.handle().clone();
|
||||||
|
eprintln!("app version: {}", app.package_info().version);
|
||||||
|
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut builder = handle.updater_builder();
|
let mut builder = handle.updater_builder();
|
||||||
if std::env::var("TARGET").unwrap_or_default() == "nsis" {
|
|
||||||
// /D sets the default installation directory ($INSTDIR),
|
// Overriding installation directory for integration tests on Windows
|
||||||
// overriding InstallDir and InstallDirRegKey.
|
#[cfg(windows)]
|
||||||
// It must be the last parameter used in the command line and must not contain any quotes, even if the path contains spaces.
|
{
|
||||||
// Only absolute paths are supported.
|
let target = std::env::var("TARGET").unwrap_or_default();
|
||||||
// NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder
|
let exe = tauri::utils::platform::current_exe().unwrap();
|
||||||
builder = builder.installer_args(vec![format!(
|
let dir = dunce::simplified(exe.parent().unwrap()).display();
|
||||||
"/D={}",
|
if target == "nsis" {
|
||||||
tauri::utils::platform::current_exe()
|
builder = builder.installer_arg(format!("/D=\"{dir}\"",));
|
||||||
.unwrap()
|
} else if target == "msi" {
|
||||||
.parent()
|
builder = builder.installer_arg(format!("INSTALLDIR=\"{dir}\""));
|
||||||
.unwrap()
|
}
|
||||||
.display()
|
|
||||||
)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let updater = builder.build().unwrap();
|
let updater = builder.build().unwrap();
|
||||||
|
|
||||||
match updater.check().await {
|
match updater.check().await {
|
||||||
Ok(Some(update)) => {
|
Ok(Some(update)) => {
|
||||||
if let Err(e) = update.download_and_install(|_, _| {}, || {}).await {
|
if let Err(e) = update.download_and_install(|_, _| {}, || {}).await {
|
||||||
println!("{e}");
|
eprintln!("{e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
@@ -45,8 +47,8 @@ fn main() {
|
|||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("{e}");
|
eprintln!("{e}");
|
||||||
std::process::exit(1);
|
std::process::exit(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"endpoints": ["http://localhost:3007"],
|
"endpoints": ["http://localhost:3007"],
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
|
||||||
"windows": {
|
"windows": {
|
||||||
"installMode": "quiet",
|
"installMode": "quiet"
|
||||||
"installerArgs": ["/NS"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,55 +19,65 @@ const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZ
|
|||||||
const UPDATED_EXIT_CODE: i32 = 0;
|
const UPDATED_EXIT_CODE: i32 = 0;
|
||||||
const UP_TO_DATE_EXIT_CODE: i32 = 2;
|
const UP_TO_DATE_EXIT_CODE: i32 = 2;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
struct Config {
|
struct Config {
|
||||||
version: &'static str,
|
version: &'static str,
|
||||||
bundle: BundleConfig,
|
bundle: BundleConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct BundleConfig {
|
struct BundleConfig {
|
||||||
create_updater_artifacts: Updater,
|
create_updater_artifacts: Updater,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct PlatformUpdate {
|
|
||||||
signature: String,
|
|
||||||
url: &'static str,
|
|
||||||
with_elevated_task: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Update {
|
struct Update {
|
||||||
version: &'static str,
|
version: &'static str,
|
||||||
date: String,
|
date: String,
|
||||||
platforms: HashMap<String, PlatformUpdate>,
|
signature: String,
|
||||||
|
url: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTarget) {
|
fn setup_test() -> (PathBuf, PathBuf, Config, Config) {
|
||||||
|
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
|
let target_dir = std::env::var("CARGO_TARGET_DIR")
|
||||||
|
.or_else(|_| std::env::var("CARGO_BUILD_TARGET_DIR"))
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.unwrap_or_else(|_| manifest_dir.join("../../../../target"));
|
||||||
|
|
||||||
|
let base_config = Config {
|
||||||
|
version: "0.1.0",
|
||||||
|
bundle: BundleConfig {
|
||||||
|
create_updater_artifacts: Updater::Bool(true),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
version: "1.0.0",
|
||||||
|
bundle: BundleConfig {
|
||||||
|
create_updater_artifacts: Updater::Bool(true),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
(manifest_dir, target_dir, base_config, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: &str) {
|
||||||
let mut command = Command::new("cargo");
|
let mut command = Command::new("cargo");
|
||||||
command
|
command
|
||||||
.args(["tauri", "build", "--debug", "--verbose"])
|
.args(["tauri", "build", "--debug"])
|
||||||
.arg("--config")
|
.arg("--config")
|
||||||
.arg(serde_json::to_string(config).unwrap())
|
.arg(serde_json::to_string(config).unwrap())
|
||||||
.env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
|
.env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
|
||||||
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
|
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
|
||||||
.current_dir(cwd);
|
.current_dir(cwd);
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
command.args(["--bundles", target.name()]);
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
command.args(["--bundles", target.name()]);
|
|
||||||
|
|
||||||
if bundle_updater {
|
if bundle_updater {
|
||||||
#[cfg(windows)]
|
command.args(["--bundles", target, "updater"]);
|
||||||
command.args(["--bundles", "msi", "nsis"]);
|
|
||||||
|
|
||||||
command.args(["--bundles", "updater"]);
|
|
||||||
} else {
|
} else {
|
||||||
#[cfg(windows)]
|
command.arg("--no-bundle");
|
||||||
command.args(["--bundles", target.name()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = command
|
let status = command
|
||||||
@@ -79,268 +89,209 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
fn start_server(update_bundle: PathBuf, signature: PathBuf) -> Arc<tiny_http::Server> {
|
||||||
enum BundleTarget {
|
let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
|
||||||
AppImage,
|
let server = Arc::new(server);
|
||||||
|
let server_ = server.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
for request in server_.incoming_requests() {
|
||||||
|
match request.url() {
|
||||||
|
"/" => {
|
||||||
|
let signature =
|
||||||
|
std::fs::read_to_string(&signature).expect("failed to read signature");
|
||||||
|
|
||||||
App,
|
let now = time::OffsetDateTime::now_utc()
|
||||||
|
.format(&time::format_description::well_known::Rfc3339)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
Msi,
|
let body = serde_json::to_vec(&Update {
|
||||||
Nsis,
|
version: "1.0.0",
|
||||||
|
date: now,
|
||||||
|
signature,
|
||||||
|
url: "http://localhost:3007/download",
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let len = body.len();
|
||||||
|
|
||||||
|
let response = tiny_http::Response::new(
|
||||||
|
tiny_http::StatusCode(200),
|
||||||
|
Vec::new(),
|
||||||
|
std::io::Cursor::new(body),
|
||||||
|
Some(len),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = request.respond(response);
|
||||||
|
}
|
||||||
|
"/download" => {
|
||||||
|
let file = File::open(&update_bundle).unwrap_or_else(|_| {
|
||||||
|
panic!("failed to open updater bundle {}", update_bundle.display())
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = request.respond(tiny_http::Response::from_file(file));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BundleTarget {
|
fn test_update(app: &Path, update_bundle: PathBuf, signature: PathBuf, target: &str) {
|
||||||
fn name(self) -> &'static str {
|
// start the updater server
|
||||||
match self {
|
let server = start_server(update_bundle, signature);
|
||||||
Self::AppImage => "appimage",
|
|
||||||
Self::App => "app",
|
// run app
|
||||||
Self::Msi => "msi",
|
let mut app_cmd = Command::new(app);
|
||||||
Self::Nsis => "nsis",
|
#[cfg(target_os = "linux")]
|
||||||
}
|
if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
||||||
|
app_cmd = Command::new("xvfb-run");
|
||||||
|
app_cmd.arg("--auto-servernum").arg(app);
|
||||||
|
};
|
||||||
|
app_cmd.env("TARGET", target);
|
||||||
|
let output = app_cmd.output().expect("failed to run app");
|
||||||
|
|
||||||
|
// check if updated, or failed during update
|
||||||
|
let code = output.status.code().unwrap_or(-1);
|
||||||
|
if code != UPDATED_EXIT_CODE {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
panic!("app failed while updating, expected exit code {UPDATED_EXIT_CODE}, got {code}\n{stderr}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the update to finish
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(20));
|
||||||
|
|
||||||
|
// run again
|
||||||
|
let status = app_cmd.status().expect("failed to run new app");
|
||||||
|
|
||||||
|
// check if new version is up to date
|
||||||
|
let code = status.code().unwrap_or(-1);
|
||||||
|
if code != UP_TO_DATE_EXIT_CODE {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
panic!(
|
||||||
|
"app failed to update, expected exit code {UP_TO_DATE_EXIT_CODE}, got {code}\n{stderr}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown the server
|
||||||
|
server.unblock();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn nsis() {
|
||||||
|
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||||
|
|
||||||
|
// build update bundles
|
||||||
|
build_app(&manifest_dir, &config, true, "nsis");
|
||||||
|
|
||||||
|
// bundle base app
|
||||||
|
build_app(&manifest_dir, &base_config, false, "nsis");
|
||||||
|
|
||||||
|
let app = target_dir.join("debug/app-updater.exe");
|
||||||
|
|
||||||
|
// test nsis installer updates
|
||||||
|
let update_bundle = target_dir.join(format!(
|
||||||
|
"debug/bundle/nsis/app-updater_{}_x64-setup.exe",
|
||||||
|
config.version,
|
||||||
|
));
|
||||||
|
let signature = update_bundle.with_extension("exe.sig");
|
||||||
|
test_update(&app, update_bundle, signature, "nsis");
|
||||||
|
|
||||||
|
// cleanup the installed application
|
||||||
|
if !std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
||||||
|
let _ = Command::new(target_dir.join("debug/uninstall.exe"))
|
||||||
|
.arg("/S")
|
||||||
|
.status()
|
||||||
|
.expect("failed to run nsis uninstaller");
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BundleTarget {
|
#[cfg(windows)]
|
||||||
fn default() -> Self {
|
fn msi() {
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||||
return Self::App;
|
|
||||||
#[cfg(target_os = "linux")]
|
// build update bundles
|
||||||
return Self::AppImage;
|
build_app(&manifest_dir, &config, true, "msi");
|
||||||
#[cfg(windows)]
|
|
||||||
return Self::Nsis;
|
// bundle base app
|
||||||
|
build_app(&manifest_dir, &base_config, false, "msi");
|
||||||
|
|
||||||
|
let app = target_dir.join("debug/app-updater.exe");
|
||||||
|
|
||||||
|
// test msi installer updates
|
||||||
|
let update_bundle = target_dir.join(format!(
|
||||||
|
"debug/bundle/msi/app-updater_{}_x64_en-US.msi",
|
||||||
|
config.version,
|
||||||
|
));
|
||||||
|
let signature = update_bundle.with_extension("msi.sig");
|
||||||
|
test_update(&app, update_bundle, signature, "msi");
|
||||||
|
|
||||||
|
// cleanup the installed application
|
||||||
|
if !std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
||||||
|
let uninstall = target_dir.join("debug/Uninstall app-updater.lnk");
|
||||||
|
let _ = Command::new("cmd")
|
||||||
|
.arg("/c")
|
||||||
|
.arg(&uninstall)
|
||||||
|
.arg("/quiet")
|
||||||
|
.status()
|
||||||
|
.expect("failed to run msi uninstaller");
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
fn appimage() {
|
||||||
vec![(
|
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||||
BundleTarget::AppImage,
|
|
||||||
root_dir.join(format!(
|
// build update bundles
|
||||||
"target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage"
|
build_app(&manifest_dir, &config, true, "appimage");
|
||||||
)),
|
|
||||||
)]
|
let update_bundle = target_dir.join(format!(
|
||||||
|
"debug/bundle/appimage/app-updater_{}_amd64.AppImage",
|
||||||
|
config.version,
|
||||||
|
));
|
||||||
|
let signature = update_bundle.with_extension("AppImage.sig");
|
||||||
|
|
||||||
|
// backup update bundles files because next build will override them
|
||||||
|
let appimage_backup = target_dir.join("debug/bundle/test-appimage.AppImage");
|
||||||
|
let signature_backup = target_dir.join("debug/bundle/test-appimage.AppImage.sig");
|
||||||
|
std::fs::rename(&update_bundle, &appimage_backup);
|
||||||
|
std::fs::rename(&signature, &signature_backup);
|
||||||
|
|
||||||
|
// bundle base app
|
||||||
|
build_app(&manifest_dir, &base_config, true, "appimage");
|
||||||
|
|
||||||
|
// restore backup
|
||||||
|
std::fs::rename(&appimage_backup, &update_bundle);
|
||||||
|
std::fs::rename(&signature_backup, &signature);
|
||||||
|
|
||||||
|
let app = target_dir.join(format!(
|
||||||
|
"debug/bundle/appimage/app-updater_{}_amd64.AppImage",
|
||||||
|
base_config.version
|
||||||
|
));
|
||||||
|
|
||||||
|
// test appimage updates
|
||||||
|
test_update(&app, update_bundle, signature, "appimage");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
fn app() {
|
||||||
vec![(
|
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||||
BundleTarget::App,
|
|
||||||
root_dir.join("target/debug/bundle/macos/app-updater.app"),
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
|
||||||
vec![(
|
|
||||||
BundleTarget::App,
|
|
||||||
root_dir.join("target/debug/bundle/ios/app-updater.ipa"),
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
fn bundle_path(root_dir: &Path, _version: &str) -> PathBuf {
|
|
||||||
root_dir.join("target/debug/bundle/android/app-updater.apk")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
BundleTarget::Nsis,
|
|
||||||
root_dir.join(format!(
|
|
||||||
"target/debug/bundle/nsis/app-updater_{version}_x64-setup.exe"
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
BundleTarget::Msi,
|
|
||||||
root_dir.join(format!(
|
|
||||||
"target/debug/bundle/msi/app-updater_{version}_x64_en-US.msi"
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn update_app() {
|
fn it_updates() {
|
||||||
let target =
|
#[cfg(windows)]
|
||||||
tauri_plugin_updater::target().expect("running updater test in an unsupported platform");
|
nsis();
|
||||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
// MSI test should be the last one
|
||||||
let root_dir = manifest_dir.join("../../../..");
|
#[cfg(windows)]
|
||||||
|
msi();
|
||||||
for mut config in [
|
#[cfg(target_os = "linux")]
|
||||||
Config {
|
appimage();
|
||||||
version: "1.0.0",
|
#[cfg(target_os = "macos")]
|
||||||
bundle: BundleConfig {
|
app();
|
||||||
create_updater_artifacts: Updater::Bool(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Config {
|
|
||||||
version: "1.0.0",
|
|
||||||
bundle: BundleConfig {
|
|
||||||
create_updater_artifacts: Updater::String(V1Compatible::V1Compatible),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] {
|
|
||||||
let v1_compatible = matches!(
|
|
||||||
config.bundle.create_updater_artifacts,
|
|
||||||
Updater::String(V1Compatible::V1Compatible)
|
|
||||||
);
|
|
||||||
|
|
||||||
// bundle app update
|
|
||||||
build_app(&manifest_dir, &config, true, Default::default());
|
|
||||||
|
|
||||||
let updater_zip_ext = if v1_compatible {
|
|
||||||
if cfg!(windows) {
|
|
||||||
Some("zip")
|
|
||||||
} else {
|
|
||||||
Some("tar.gz")
|
|
||||||
}
|
|
||||||
} else if cfg!(target_os = "macos") {
|
|
||||||
Some("tar.gz")
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
for (bundle_target, out_bundle_path) in bundle_paths(&root_dir, "1.0.0") {
|
|
||||||
let bundle_updater_ext = if v1_compatible {
|
|
||||||
out_bundle_path
|
|
||||||
.extension()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.replace("exe", "nsis")
|
|
||||||
} else {
|
|
||||||
out_bundle_path
|
|
||||||
.extension()
|
|
||||||
.unwrap()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
};
|
|
||||||
let updater_extension = if let Some(updater_zip_ext) = updater_zip_ext {
|
|
||||||
format!("{bundle_updater_ext}.{updater_zip_ext}")
|
|
||||||
} else {
|
|
||||||
bundle_updater_ext
|
|
||||||
};
|
|
||||||
let signature_extension = format!("{updater_extension}.sig");
|
|
||||||
let signature_path = out_bundle_path.with_extension(signature_extension);
|
|
||||||
let signature = std::fs::read_to_string(&signature_path).unwrap_or_else(|_| {
|
|
||||||
panic!("failed to read signature file {}", signature_path.display())
|
|
||||||
});
|
|
||||||
let out_updater_path = out_bundle_path.with_extension(updater_extension);
|
|
||||||
let updater_path = root_dir.join(format!(
|
|
||||||
"target/debug/{}",
|
|
||||||
out_updater_path.file_name().unwrap().to_str().unwrap()
|
|
||||||
));
|
|
||||||
std::fs::rename(&out_updater_path, &updater_path).expect("failed to rename bundle");
|
|
||||||
|
|
||||||
let target = target.clone();
|
|
||||||
|
|
||||||
// start the updater server
|
|
||||||
let server = Arc::new(
|
|
||||||
tiny_http::Server::http("localhost:3007").expect("failed to start updater server"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let server_ = server.clone();
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
for request in server_.incoming_requests() {
|
|
||||||
match request.url() {
|
|
||||||
"/" => {
|
|
||||||
let mut platforms = HashMap::new();
|
|
||||||
|
|
||||||
platforms.insert(
|
|
||||||
target.clone(),
|
|
||||||
PlatformUpdate {
|
|
||||||
signature: signature.clone(),
|
|
||||||
url: "http://localhost:3007/download",
|
|
||||||
with_elevated_task: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let body = serde_json::to_vec(&Update {
|
|
||||||
version: "1.0.0",
|
|
||||||
date: time::OffsetDateTime::now_utc()
|
|
||||||
.format(&time::format_description::well_known::Rfc3339)
|
|
||||||
.unwrap(),
|
|
||||||
platforms,
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
let len = body.len();
|
|
||||||
let response = tiny_http::Response::new(
|
|
||||||
tiny_http::StatusCode(200),
|
|
||||||
Vec::new(),
|
|
||||||
std::io::Cursor::new(body),
|
|
||||||
Some(len),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let _ = request.respond(response);
|
|
||||||
}
|
|
||||||
"/download" => {
|
|
||||||
let _ = request.respond(tiny_http::Response::from_file(
|
|
||||||
File::open(&updater_path).unwrap_or_else(|_| {
|
|
||||||
panic!(
|
|
||||||
"failed to open updater bundle {}",
|
|
||||||
updater_path.display()
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
config.version = "0.1.0";
|
|
||||||
|
|
||||||
// bundle initial app version
|
|
||||||
build_app(&manifest_dir, &config, false, bundle_target);
|
|
||||||
|
|
||||||
let status_checks = if matches!(bundle_target, BundleTarget::Msi) {
|
|
||||||
// for msi we can't really check if the app was updated, because we can't change the install path
|
|
||||||
vec![UPDATED_EXIT_CODE]
|
|
||||||
} else {
|
|
||||||
vec![UPDATED_EXIT_CODE, UP_TO_DATE_EXIT_CODE]
|
|
||||||
};
|
|
||||||
|
|
||||||
for expected_exit_code in status_checks {
|
|
||||||
let mut binary_cmd = if cfg!(windows) {
|
|
||||||
Command::new(root_dir.join("target/debug/app-updater.exe"))
|
|
||||||
} else if cfg!(target_os = "macos") {
|
|
||||||
Command::new(
|
|
||||||
bundle_paths(&root_dir, "0.1.0")
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.1
|
|
||||||
.join("Contents/MacOS/app-updater"),
|
|
||||||
)
|
|
||||||
} else if std::env::var("CI").map(|v| v == "true").unwrap_or_default() {
|
|
||||||
let mut c = Command::new("xvfb-run");
|
|
||||||
c.arg("--auto-servernum")
|
|
||||||
.arg(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1);
|
|
||||||
c
|
|
||||||
} else {
|
|
||||||
Command::new(&bundle_paths(&root_dir, "0.1.0").first().unwrap().1)
|
|
||||||
};
|
|
||||||
|
|
||||||
binary_cmd.env("TARGET", bundle_target.name());
|
|
||||||
|
|
||||||
let status = binary_cmd.status().expect("failed to run app");
|
|
||||||
let code = status.code().unwrap_or(-1);
|
|
||||||
|
|
||||||
if code != expected_exit_code {
|
|
||||||
panic!(
|
|
||||||
"failed to run app, expected exit code {expected_exit_code}, got {code}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
#[cfg(windows)]
|
|
||||||
if code == UPDATED_EXIT_CODE {
|
|
||||||
// wait for the update to finish
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// graceful shutdown
|
|
||||||
server.unblock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.1.0]
|
||||||
|
|
||||||
|
- [`87cc5852`](https://github.com/tauri-apps/plugins-workspace/commit/87cc58527d769960427a2f46bb10532f5dcf7ace) ([#1797](https://github.com/tauri-apps/plugins-workspace/pull/1797) by [@VirtualPirate](https://github.com/tauri-apps/plugins-workspace/../../VirtualPirate)) Added feature for calculating `transfer_speed` during file uploads and downloads
|
||||||
|
|
||||||
## \[2.0.1]
|
## \[2.0.1]
|
||||||
|
|
||||||
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
- [`a1a82208`](https://github.com/tauri-apps/plugins-workspace/commit/a1a82208ed4ab87f83310be0dc95428aec9ab241) ([#1873](https://github.com/tauri-apps/plugins-workspace/pull/1873) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Downgrade MSRV to 1.77.2 to support Windows 7.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-upload"
|
name = "tauri-plugin-upload"
|
||||||
version = "2.0.1"
|
version = "2.1.0"
|
||||||
description = "Upload files from disk to a remote server over HTTP."
|
description = "Upload files from disk to a remote server over HTTP."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_UPLOAD__=function(e){"use strict";function t(e,t,n,o){if("a"===n&&!o)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!o:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?o:"a"===n?o.call(e):o?o.value:t.get(e)}function n(e,t,n,o,s){if("function"==typeof t?e!==t||!s:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return t.set(e,n),n}var o,s,r;"function"==typeof SuppressedError&&SuppressedError;class i{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,o.set(this,(()=>{})),s.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:e,id:i})=>{if(i===t(this,s,"f")){n(this,s,i+1),t(this,o,"f").call(this,e);const a=Object.keys(t(this,r,"f"));if(a.length>0){let e=i+1;for(const n of a.sort()){if(parseInt(n)!==e)break;{const s=t(this,r,"f")[n];delete t(this,r,"f")[n],t(this,o,"f").call(this,s),e+=1}}n(this,s,e)}}else t(this,r,"f")[i.toString()]=e}))}set onmessage(e){n(this,o,e)}get onmessage(){return t(this,o,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(e,t={},n){return window.__TAURI_INTERNALS__.invoke(e,t,n)}return o=new WeakMap,s=new WeakMap,r=new WeakMap,e.download=async function(e,t,n,o){const s=new Uint32Array(1);window.crypto.getRandomValues(s);const r=s[0],c=new i;n&&(c.onmessage=n),await a("plugin:upload|download",{id:r,url:e,filePath:t,headers:o??{},onProgress:c})},e.upload=async function(e,t,n,o){const s=new Uint32Array(1);window.crypto.getRandomValues(s);const r=s[0],c=new i;return n&&(c.onmessage=n),await a("plugin:upload|upload",{id:r,url:e,filePath:t,headers:o??{},onProgress:c})},e}({});Object.defineProperty(window.__TAURI__,"upload",{value:__TAURI_PLUGIN_UPLOAD__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_UPLOAD__=function(t){"use strict";function e(t,e,n,s){if("a"===n&&!s)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof e?t!==e||!s:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?s:"a"===n?s.call(t):s?s.value:e.get(t)}function n(t,e,n,s,o){if("function"==typeof e?t!==e||!o:!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var s,o,r;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),o.set(this,0),r.set(this,{}),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((({message:t,id:i})=>{if(i===e(this,o,"f")){n(this,o,i+1),e(this,s,"f").call(this,t);const a=Object.keys(e(this,r,"f"));if(a.length>0){let t=i+1;for(const n of a.sort()){if(parseInt(n)!==t)break;{const o=e(this,r,"f")[n];delete e(this,r,"f")[n],e(this,s,"f").call(this,o),t+=1}}n(this,o,t)}}else e(this,r,"f")[i.toString()]=t}))}set onmessage(t){n(this,s,t)}get onmessage(){return e(this,s,"f")}[(s=new WeakMap,o=new WeakMap,r=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function _(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}return t.download=async function(t,e,n,s){const o=new Uint32Array(1);window.crypto.getRandomValues(o);const r=o[0],i=new a;n&&(i.onmessage=n),await _("plugin:upload|download",{id:r,url:t,filePath:e,headers:s??{},onProgress:i})},t.upload=async function(t,e,n,s){const o=new Uint32Array(1);window.crypto.getRandomValues(o);const r=o[0],i=new a;return n&&(i.onmessage=n),await _("plugin:upload|upload",{id:r,url:t,filePath:e,headers:s??{},onProgress:i})},t}({});Object.defineProperty(window.__TAURI__,"upload",{value:__TAURI_PLUGIN_UPLOAD__})}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import { invoke, Channel } from '@tauri-apps/api/core'
|
|||||||
|
|
||||||
interface ProgressPayload {
|
interface ProgressPayload {
|
||||||
progress: number
|
progress: number
|
||||||
|
progressTotal: number
|
||||||
total: number
|
total: number
|
||||||
|
transferSpeed: number
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgressHandler = (progress: ProgressPayload) => void
|
type ProgressHandler = (progress: ProgressPayload) => void
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-upload",
|
"name": "@tauri-apps/plugin-upload",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "Upload files from disk to a remote server over HTTP.",
|
"description": "Upload files from disk to a remote server over HTTP.",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/upload)
|
|
||||||
//!
|
|
||||||
//! Upload files from disk to a remote server over HTTP.
|
//! Upload files from disk to a remote server over HTTP.
|
||||||
//!
|
//!
|
||||||
//! Download files from a remote HTTP server to disk.
|
//! Download files from a remote HTTP server to disk.
|
||||||
@@ -13,6 +11,9 @@
|
|||||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
mod transfer_stats;
|
||||||
|
use transfer_stats::TransferStats;
|
||||||
|
|
||||||
use futures_util::TryStreamExt;
|
use futures_util::TryStreamExt;
|
||||||
use serde::{ser::Serializer, Serialize};
|
use serde::{ser::Serializer, Serialize};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
@@ -55,9 +56,12 @@ impl Serialize for Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ProgressPayload {
|
struct ProgressPayload {
|
||||||
progress: u64,
|
progress: u64,
|
||||||
|
progress_total: u64,
|
||||||
total: u64,
|
total: u64,
|
||||||
|
transfer_speed: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
@@ -88,11 +92,15 @@ async fn download(
|
|||||||
let mut file = BufWriter::new(File::create(file_path).await?);
|
let mut file = BufWriter::new(File::create(file_path).await?);
|
||||||
let mut stream = response.bytes_stream();
|
let mut stream = response.bytes_stream();
|
||||||
|
|
||||||
|
let mut stats = TransferStats::default();
|
||||||
while let Some(chunk) = stream.try_next().await? {
|
while let Some(chunk) = stream.try_next().await? {
|
||||||
file.write_all(&chunk).await?;
|
file.write_all(&chunk).await?;
|
||||||
|
stats.record_chunk_transfer(chunk.len());
|
||||||
let _ = on_progress.send(ProgressPayload {
|
let _ = on_progress.send(ProgressPayload {
|
||||||
progress: chunk.len() as u64,
|
progress: chunk.len() as u64,
|
||||||
|
progress_total: stats.total_transferred,
|
||||||
total,
|
total,
|
||||||
|
transfer_speed: stats.transfer_speed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
file.flush().await?;
|
file.flush().await?;
|
||||||
@@ -138,10 +146,17 @@ async fn upload(
|
|||||||
fn file_to_body(channel: Channel<ProgressPayload>, file: File) -> reqwest::Body {
|
fn file_to_body(channel: Channel<ProgressPayload>, file: File) -> reqwest::Body {
|
||||||
let stream = FramedRead::new(file, BytesCodec::new()).map_ok(|r| r.freeze());
|
let stream = FramedRead::new(file, BytesCodec::new()).map_ok(|r| r.freeze());
|
||||||
|
|
||||||
|
let mut stats = TransferStats::default();
|
||||||
reqwest::Body::wrap_stream(ReadProgressStream::new(
|
reqwest::Body::wrap_stream(ReadProgressStream::new(
|
||||||
stream,
|
stream,
|
||||||
Box::new(move |progress, total| {
|
Box::new(move |progress, total| {
|
||||||
let _ = channel.send(ProgressPayload { progress, total });
|
stats.record_chunk_transfer(progress as usize);
|
||||||
|
let _ = channel.send(ProgressPayload {
|
||||||
|
progress,
|
||||||
|
progress_total: stats.total_transferred,
|
||||||
|
total,
|
||||||
|
transfer_speed: stats.transfer_speed,
|
||||||
|
});
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user