mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-05-03 12:15:11 +02:00
Compare commits
33 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 |
@@ -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
+155
-151
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -11,12 +11,12 @@ resolver = "2"
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
log = "0.4"
|
||||
tauri = { version = "2.0.4", default-features = false }
|
||||
tauri-build = "2.0.1"
|
||||
tauri-plugin = "2.0.1"
|
||||
tauri-utils = "2.0.1"
|
||||
tauri = { version = "2", default-features = false }
|
||||
tauri-build = "2"
|
||||
tauri-plugin = "2"
|
||||
tauri-utils = "2"
|
||||
serde_json = "1"
|
||||
thiserror = "1"
|
||||
thiserror = "2"
|
||||
url = "2"
|
||||
schemars = "0.8"
|
||||
dunce = "1"
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"name": "api",
|
||||
"private": true,
|
||||
"version": "2.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --clearScreen false",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
"serve": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.0.3",
|
||||
"@tauri-apps/api": "2.1.1",
|
||||
"@tauri-apps/plugin-barcode-scanner": "2.0.0",
|
||||
"@tauri-apps/plugin-biometric": "2.0.0",
|
||||
"@tauri-apps/plugin-cli": "2.0.0",
|
||||
@@ -33,7 +34,7 @@
|
||||
"@iconify-json/codicon": "^1.1.37",
|
||||
"@iconify-json/ph": "^1.1.8",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@tauri-apps/cli": "2.0.4",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"@unocss/extractor-svelte": "^0.64.0",
|
||||
"svelte": "^5.0.0",
|
||||
"unocss": "^0.64.0",
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"geolocation:allow-check-permissions",
|
||||
"geolocation:allow-request-permissions",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import Scanner from './views/Scanner.svelte'
|
||||
import Biometric from './views/Biometric.svelte'
|
||||
import Geolocation from './views/Geolocation.svelte'
|
||||
import Haptics from './views/Haptics.svelte'
|
||||
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { ask } from '@tauri-apps/plugin-dialog'
|
||||
@@ -130,6 +131,11 @@
|
||||
label: 'Geolocation',
|
||||
component: Geolocation,
|
||||
icon: 'i-ph-map-pin'
|
||||
},
|
||||
isMobile && {
|
||||
label: 'Haptics',
|
||||
component: Haptics,
|
||||
icon: 'i-ph-vibrate'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -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>
|
||||
+4
-3
@@ -7,7 +7,8 @@
|
||||
"build": "pnpm run -r --parallel --filter !plugins-workspace --filter !\"./plugins/*/examples/**\" --filter !\"./examples/*\" build",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
"format:check": "prettier --check .",
|
||||
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.14.0",
|
||||
@@ -20,10 +21,10 @@
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-plugin-security": "3.0.1",
|
||||
"prettier": "3.3.3",
|
||||
"rollup": "4.24.4",
|
||||
"rollup": "4.27.0",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.6.3",
|
||||
"typescript-eslint": "8.13.0"
|
||||
"typescript-eslint": "8.14.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"semver": ">=7.5.2",
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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.
|
||||
|
||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/cli)
|
||||
//!
|
||||
//! Parse arguments from your Command Line Interface.
|
||||
//!
|
||||
//! - Supported platforms: Windows, Linux and macOS.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/clipboard-manager)
|
||||
//!
|
||||
//! Read and write to the system clipboard.
|
||||
|
||||
#![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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.0.3",
|
||||
"@tauri-apps/api": "2.1.1",
|
||||
"@tauri-apps/plugin-deep-link": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.4",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.4.7"
|
||||
}
|
||||
|
||||
@@ -99,11 +99,6 @@ export async function isRegistered(protocol: string): Promise<boolean> {
|
||||
export async function onOpenUrl(
|
||||
handler: (urls: string[]) => void
|
||||
): Promise<UnlistenFn> {
|
||||
const current = await getCurrent()
|
||||
if (current) {
|
||||
handler(current)
|
||||
}
|
||||
|
||||
return await listen<string[]>('deep-link://new-url', (event) => {
|
||||
handler(event.payload)
|
||||
})
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tauri::{
|
||||
plugin::{Builder, PluginApi, TauriPlugin},
|
||||
AppHandle, EventId, Listener, Manager, Runtime,
|
||||
@@ -478,13 +476,10 @@ impl OpenUrlEvent {
|
||||
}
|
||||
|
||||
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,
|
||||
/// the closure gets immediately called with the deep link URL.
|
||||
/// Use `get_current` on app load to check whether your app was started via a deep link.
|
||||
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| {
|
||||
if let Ok(urls) = serde_json::from_str(event.payload()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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.
|
||||
|
||||
#![doc(
|
||||
|
||||
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 scope configuration is an array of glob patterns describing folder paths that are allowed.
|
||||
* For instance, this scope configuration only allows accessing files on the
|
||||
* *databases* folder of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}:
|
||||
* The scope configuration is an array of glob patterns describing file/directory paths that are allowed.
|
||||
* For instance, this scope configuration allows **all** enabled `fs` APIs to (only) access files in the
|
||||
* *databases* directory of the {@link https://v2.tauri.app/reference/javascript/api/namespacepath/#appdatadir | `$APPDATA` directory}:
|
||||
* ```json
|
||||
* {
|
||||
* "plugins": {
|
||||
* "fs": {
|
||||
* "scope": ["$APPDATA/databases/*"]
|
||||
* "permissions": [
|
||||
* {
|
||||
* "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.
|
||||
*
|
||||
* Note that this scope applies to **all** APIs on this module.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
|
||||
+32
-29
@@ -16,7 +16,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
fs::File,
|
||||
io::{BufReader, Lines, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
sync::Mutex,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
@@ -245,32 +245,12 @@ pub fn mkdir<R: Runtime>(
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[non_exhaustive]
|
||||
pub struct DirEntry {
|
||||
pub name: Option<String>,
|
||||
pub name: String,
|
||||
pub is_directory: bool,
|
||||
pub is_file: 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]
|
||||
pub async fn read_dir<R: Runtime>(
|
||||
webview: Webview<R>,
|
||||
@@ -287,14 +267,37 @@ pub async fn read_dir<R: Runtime>(
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
|
||||
read_dir_inner(&resolved_path)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"failed to read directory at path: {} with error: {e}",
|
||||
resolved_path.display()
|
||||
)
|
||||
let entries = std::fs::read_dir(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
"failed to read directory at path: {} with error: {e}",
|
||||
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]
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/fs)
|
||||
//!
|
||||
//! Access the file system.
|
||||
|
||||
#![doc(
|
||||
|
||||
@@ -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:
|
||||
|
||||
```javascript
|
||||
@@ -100,7 +114,7 @@ import {
|
||||
requestPermissions,
|
||||
getCurrentPosition,
|
||||
watchPosition
|
||||
} from '@tauri-apps/plugin-log'
|
||||
} from '@tauri-apps/plugin-geolocation'
|
||||
|
||||
let permissions = await checkPermissions()
|
||||
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: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/global-shortcut)
|
||||
//!
|
||||
//! Register global shortcuts.
|
||||
//!
|
||||
//! - Supported platforms: Windows, Linux and macOS.
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/http)
|
||||
//!
|
||||
//! Access the HTTP client written in Rust.
|
||||
|
||||
pub use reqwest;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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.
|
||||
//!
|
||||
//! **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 {
|
||||
port: u16,
|
||||
host: Option<String>,
|
||||
on_request: OnRequest,
|
||||
}
|
||||
|
||||
@@ -53,10 +52,17 @@ impl Builder {
|
||||
pub fn new(port: u16) -> Self {
|
||||
Self {
|
||||
port,
|
||||
host: 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>(
|
||||
mut self,
|
||||
f: F,
|
||||
@@ -67,6 +73,7 @@ impl Builder {
|
||||
|
||||
pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
|
||||
let port = self.port;
|
||||
let host = self.host.unwrap_or("localhost".to_string());
|
||||
let on_request = self.on_request.take();
|
||||
|
||||
PluginBuilder::new("localhost")
|
||||
@@ -74,7 +81,7 @@ impl Builder {
|
||||
let asset_resolver = app.asset_resolver();
|
||||
std::thread::spawn(move || {
|
||||
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() {
|
||||
let path = req
|
||||
.url()
|
||||
|
||||
@@ -27,12 +27,12 @@ tauri-plugin = { workspace = true, features = ["build"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde_repr = "0.1"
|
||||
byte-unit = "5"
|
||||
log = { workspace = true, features = ["kv_unstable"] }
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
fern = "0.7"
|
||||
thiserror = "1"
|
||||
|
||||
[target."cfg(target_os = \"android\")".dependencies]
|
||||
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
|
||||
}
|
||||
|
||||
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(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
options?: LogOptions
|
||||
): Promise<void> {
|
||||
const traces = new Error().stack?.split('\n').map((line) => line.split('@'))
|
||||
|
||||
const filtered = traces?.filter(([name, location]) => {
|
||||
return name.length > 0 && location !== '[native code]'
|
||||
})
|
||||
const location = getCallerLocation(new Error().stack)
|
||||
|
||||
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', {
|
||||
level,
|
||||
message,
|
||||
|
||||
+9
-17
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/log)
|
||||
//!
|
||||
//! Logging for Tauri applications.
|
||||
|
||||
#![doc(
|
||||
@@ -33,7 +31,7 @@ use tauri::{AppHandle, Emitter};
|
||||
pub use fern;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub const WEBVIEW_TARGET: &str = "Webview";
|
||||
pub const WEBVIEW_TARGET: &str = "webview";
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
mod ios {
|
||||
@@ -230,22 +228,16 @@ fn log(
|
||||
line: Option<u32>,
|
||||
key_values: Option<HashMap<String, String>>,
|
||||
) {
|
||||
let location = location.unwrap_or("webview");
|
||||
|
||||
let level = log::Level::from(level);
|
||||
|
||||
let metadata = log::MetadataBuilder::new()
|
||||
.level(level)
|
||||
.target(WEBVIEW_TARGET)
|
||||
.build();
|
||||
let target = if let Some(location) = location {
|
||||
format!("{WEBVIEW_TARGET}:{location}")
|
||||
} else {
|
||||
WEBVIEW_TARGET.to_string()
|
||||
};
|
||||
|
||||
let mut builder = RecordBuilder::new();
|
||||
builder
|
||||
.level(level)
|
||||
.metadata(metadata)
|
||||
.target(location)
|
||||
.file(file)
|
||||
.line(line);
|
||||
builder.level(level).target(&target).file(file).line(line);
|
||||
|
||||
let key_values = key_values.unwrap_or_default();
|
||||
let mut kv = HashMap::new();
|
||||
@@ -380,8 +372,8 @@ impl Builder {
|
||||
/// .clear_targets()
|
||||
/// .targets([
|
||||
/// 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("rust".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().starts_with(WEBVIEW_TARGET)),
|
||||
/// ]);
|
||||
/// ```
|
||||
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}|registerListener`,{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: 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.
|
||||
|
||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/os)
|
||||
//!
|
||||
//! Read information about the operating system.
|
||||
|
||||
#](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.
|
||||
|
||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/positioner)
|
||||
//!
|
||||
//! A plugin for Tauri that helps position your windows at well-known locations.
|
||||
//!
|
||||
//! # Cargo features
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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.
|
||||
|
||||
#![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: 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.
|
||||
|
||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/single-instance)
|
||||
//!
|
||||
//! Ensure a single instance of your tauri app is running.
|
||||
|
||||
#](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.
|
||||
|
||||
#![doc(
|
||||
@@ -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.
|
||||
#[derive(Default)]
|
||||
pub struct Builder {
|
||||
@@ -138,7 +145,7 @@ impl Builder {
|
||||
.setup(|app, api| {
|
||||
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 mut lock = instances.0.write().await;
|
||||
|
||||
@@ -166,7 +173,7 @@ impl Builder {
|
||||
})
|
||||
.on_event(|app, 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 = instances.0.read().await;
|
||||
for value in instances.values() {
|
||||
|
||||
@@ -104,6 +104,11 @@ impl DbPool {
|
||||
}
|
||||
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())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.4",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"vite": "^5.0.12",
|
||||
"typescript": "^5.4.7"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/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.
|
||||
|
||||
#![doc(
|
||||
|
||||
@@ -63,6 +63,9 @@ flate2 = { version = "1", optional = true }
|
||||
tar = "0.4"
|
||||
flate2 = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
dunce = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["rustls-tls", "zip"]
|
||||
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: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/updater)
|
||||
//!
|
||||
//! In-app updates for Tauri applications.
|
||||
//!
|
||||
//! - Supported platforms: Windows, Linux and macOS.crypted database and secure runtime.
|
||||
|
||||
@@ -616,12 +616,16 @@ impl Update {
|
||||
.chain(self.installer_args())
|
||||
.collect(),
|
||||
WindowsUpdaterType::Msi { path, .. } => {
|
||||
let escaped_args = current_args
|
||||
.iter()
|
||||
.map(escape_msi_property_arg)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
msi_args = OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""));
|
||||
msi_args = if !current_args.is_empty() {
|
||||
let escaped_args = current_args
|
||||
.iter()
|
||||
.map(escape_msi_property_arg)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
OsString::from(format!("LAUNCHAPPARGS=\"{escaped_args}\""))
|
||||
} else {
|
||||
OsString::new()
|
||||
};
|
||||
|
||||
[OsStr::new("/i"), path.as_os_str()]
|
||||
.into_iter()
|
||||
@@ -629,7 +633,7 @@ impl Update {
|
||||
.chain(once(OsStr::new("/promptrestart")))
|
||||
.chain(self.installer_args())
|
||||
.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()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
time = { version = "0.3", features = ["formatting"] }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
dunce = { workspace = true }
|
||||
|
||||
[features]
|
||||
prod = ["tauri/custom-protocol"]
|
||||
|
||||
@@ -14,29 +14,31 @@ fn main() {
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
eprintln!("app version: {}", app.package_info().version);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = handle.updater_builder();
|
||||
if std::env::var("TARGET").unwrap_or_default() == "nsis" {
|
||||
// /D sets the default installation directory ($INSTDIR),
|
||||
// overriding InstallDir and InstallDirRegKey.
|
||||
// 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.
|
||||
// NOTE: we only need this because this is an integration test and we don't want to install the app in the programs folder
|
||||
builder = builder.installer_args(vec![format!(
|
||||
"/D={}",
|
||||
tauri::utils::platform::current_exe()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.display()
|
||||
)]);
|
||||
|
||||
// Overriding installation directory for integration tests on Windows
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let target = std::env::var("TARGET").unwrap_or_default();
|
||||
let exe = tauri::utils::platform::current_exe().unwrap();
|
||||
let dir = dunce::simplified(exe.parent().unwrap()).display();
|
||||
if target == "nsis" {
|
||||
builder = builder.installer_arg(format!("/D=\"{dir}\"",));
|
||||
} else if target == "msi" {
|
||||
builder = builder.installer_arg(format!("INSTALLDIR=\"{dir}\""));
|
||||
}
|
||||
}
|
||||
|
||||
let updater = builder.build().unwrap();
|
||||
|
||||
match updater.check().await {
|
||||
Ok(Some(update)) => {
|
||||
if let Err(e) = update.download_and_install(|_, _| {}, || {}).await {
|
||||
println!("{e}");
|
||||
eprintln!("{e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
std::process::exit(0);
|
||||
@@ -45,8 +47,8 @@ fn main() {
|
||||
std::process::exit(2);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{e}");
|
||||
std::process::exit(1);
|
||||
eprintln!("{e}");
|
||||
std::process::exit(3);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
"endpoints": ["http://localhost:3007"],
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUwNDRGMjkwRjg2MDhCRDAKUldUUWkyRDRrUEpFNEQ4SmdwcU5PaXl6R2ZRUUNvUnhIaVkwVUltV0NMaEx6VTkrWVhpT0ZqeEEK",
|
||||
"windows": {
|
||||
"installMode": "quiet",
|
||||
"installerArgs": ["/NS"]
|
||||
"installMode": "quiet"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,55 +19,65 @@ const UPDATER_PRIVATE_KEY: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZ
|
||||
const UPDATED_EXIT_CODE: i32 = 0;
|
||||
const UP_TO_DATE_EXIT_CODE: i32 = 2;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
struct Config {
|
||||
version: &'static str,
|
||||
bundle: BundleConfig,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct BundleConfig {
|
||||
create_updater_artifacts: Updater,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PlatformUpdate {
|
||||
signature: String,
|
||||
url: &'static str,
|
||||
with_elevated_task: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Update {
|
||||
version: &'static str,
|
||||
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");
|
||||
command
|
||||
.args(["tauri", "build", "--debug", "--verbose"])
|
||||
.args(["tauri", "build", "--debug"])
|
||||
.arg("--config")
|
||||
.arg(serde_json::to_string(config).unwrap())
|
||||
.env("TAURI_SIGNING_PRIVATE_KEY", UPDATER_PRIVATE_KEY)
|
||||
.env("TAURI_SIGNING_PRIVATE_KEY_PASSWORD", "")
|
||||
.current_dir(cwd);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
command.args(["--bundles", target.name()]);
|
||||
#[cfg(target_os = "macos")]
|
||||
command.args(["--bundles", target.name()]);
|
||||
|
||||
if bundle_updater {
|
||||
#[cfg(windows)]
|
||||
command.args(["--bundles", "msi", "nsis"]);
|
||||
|
||||
command.args(["--bundles", "updater"]);
|
||||
command.args(["--bundles", target, "updater"]);
|
||||
} else {
|
||||
#[cfg(windows)]
|
||||
command.args(["--bundles", target.name()]);
|
||||
command.arg("--no-bundle");
|
||||
}
|
||||
|
||||
let status = command
|
||||
@@ -79,268 +89,209 @@ fn build_app(cwd: &Path, config: &Config, bundle_updater: bool, target: BundleTa
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum BundleTarget {
|
||||
AppImage,
|
||||
fn start_server(update_bundle: PathBuf, signature: PathBuf) -> Arc<tiny_http::Server> {
|
||||
let server = tiny_http::Server::http("localhost:3007").expect("failed to start updater server");
|
||||
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,
|
||||
Nsis,
|
||||
let body = serde_json::to_vec(&Update {
|
||||
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 name(self) -> &'static str {
|
||||
match self {
|
||||
Self::AppImage => "appimage",
|
||||
Self::App => "app",
|
||||
Self::Msi => "msi",
|
||||
Self::Nsis => "nsis",
|
||||
}
|
||||
fn test_update(app: &Path, update_bundle: PathBuf, signature: PathBuf, target: &str) {
|
||||
// start the updater server
|
||||
let server = start_server(update_bundle, signature);
|
||||
|
||||
// run app
|
||||
let mut app_cmd = Command::new(app);
|
||||
#[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 {
|
||||
fn default() -> Self {
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
return Self::App;
|
||||
#[cfg(target_os = "linux")]
|
||||
return Self::AppImage;
|
||||
#[cfg(windows)]
|
||||
return Self::Nsis;
|
||||
#[cfg(windows)]
|
||||
fn msi() {
|
||||
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||
|
||||
// build update bundles
|
||||
build_app(&manifest_dir, &config, true, "msi");
|
||||
|
||||
// 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")]
|
||||
fn bundle_paths(root_dir: &Path, version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
||||
vec![(
|
||||
BundleTarget::AppImage,
|
||||
root_dir.join(format!(
|
||||
"target/debug/bundle/appimage/app-updater_{version}_amd64.AppImage"
|
||||
)),
|
||||
)]
|
||||
fn appimage() {
|
||||
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||
|
||||
// build update bundles
|
||||
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")]
|
||||
fn bundle_paths(root_dir: &Path, _version: &str) -> Vec<(BundleTarget, PathBuf)> {
|
||||
vec![(
|
||||
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"
|
||||
)),
|
||||
),
|
||||
]
|
||||
fn app() {
|
||||
let (manifest_dir, target_dir, base_config, config) = setup_test();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn update_app() {
|
||||
let target =
|
||||
tauri_plugin_updater::target().expect("running updater test in an unsupported platform");
|
||||
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
let root_dir = manifest_dir.join("../../../..");
|
||||
|
||||
for mut config in [
|
||||
Config {
|
||||
version: "1.0.0",
|
||||
bundle: BundleConfig {
|
||||
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();
|
||||
}
|
||||
}
|
||||
fn it_updates() {
|
||||
#[cfg(windows)]
|
||||
nsis();
|
||||
// MSI test should be the last one
|
||||
#[cfg(windows)]
|
||||
msi();
|
||||
#[cfg(target_os = "linux")]
|
||||
appimage();
|
||||
#[cfg(target_os = "macos")]
|
||||
app();
|
||||
}
|
||||
|
||||
@@ -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,6 +6,7 @@ import { invoke, Channel } from '@tauri-apps/api/core'
|
||||
|
||||
interface ProgressPayload {
|
||||
progress: number
|
||||
progressTotal: number
|
||||
total: number
|
||||
transferSpeed: number
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// 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.
|
||||
//!
|
||||
//! Download files from a remote HTTP server to disk.
|
||||
@@ -61,6 +59,7 @@ impl Serialize for Error {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ProgressPayload {
|
||||
progress: u64,
|
||||
progress_total: u64,
|
||||
total: u64,
|
||||
transfer_speed: u64,
|
||||
}
|
||||
@@ -99,6 +98,7 @@ async fn download(
|
||||
stats.record_chunk_transfer(chunk.len());
|
||||
let _ = on_progress.send(ProgressPayload {
|
||||
progress: chunk.len() as u64,
|
||||
progress_total: stats.total_transferred,
|
||||
total,
|
||||
transfer_speed: stats.transfer_speed,
|
||||
});
|
||||
@@ -153,6 +153,7 @@ fn file_to_body(channel: Channel<ProgressPayload>, file: File) -> reqwest::Body
|
||||
stats.record_chunk_transfer(progress as usize);
|
||||
let _ = channel.send(ProgressPayload {
|
||||
progress,
|
||||
progress_total: stats.total_transferred,
|
||||
total,
|
||||
transfer_speed: stats.transfer_speed,
|
||||
});
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
// The TransferStats struct is used to track and calculate the transfer speed of data chunks over time.
|
||||
// The TransferStats struct tracks both transfer speed and cumulative transfer progress.
|
||||
pub struct TransferStats {
|
||||
accumulated_chunk_len: usize, // Total length of chunks transferred in the current period
|
||||
accumulated_time: u128, // Total time taken for the transfers in the current period
|
||||
pub transfer_speed: u64, // Calculated transfer speed in bytes per second
|
||||
pub total_transferred: u64, // Cumulative total of all transferred data
|
||||
start_time: Instant, // Time when the current period started
|
||||
granularity: u32, // Time period (in milliseconds) over which the transfer speed is calculated
|
||||
}
|
||||
@@ -20,18 +21,20 @@ impl TransferStats {
|
||||
accumulated_chunk_len: 0,
|
||||
accumulated_time: 0,
|
||||
transfer_speed: 0,
|
||||
total_transferred: 0,
|
||||
start_time: Instant::now(),
|
||||
granularity,
|
||||
}
|
||||
}
|
||||
// Records the transfer of a data chunk and updates the transfer speed if the granularity period has elapsed.
|
||||
// Records the transfer of a data chunk and updates both transfer speed and total progress.
|
||||
pub fn record_chunk_transfer(&mut self, chunk_len: usize) {
|
||||
let now = Instant::now();
|
||||
let it_took = now.duration_since(self.start_time).as_millis();
|
||||
self.accumulated_chunk_len += chunk_len;
|
||||
self.total_transferred += chunk_len as u64;
|
||||
self.accumulated_time += it_took;
|
||||
|
||||
// If the accumulated time exceeds the granularity, calculate the transfer speed.
|
||||
// Calculate transfer speed if accumulated time exceeds granularity.
|
||||
if self.accumulated_time >= self.granularity as u128 {
|
||||
self.transfer_speed =
|
||||
(self.accumulated_chunk_len as u128 / self.accumulated_time * 1024) as u64;
|
||||
@@ -47,6 +50,6 @@ impl TransferStats {
|
||||
// Provides a default implementation for TransferStats with a granularity of 500 milliseconds.
|
||||
impl Default for TransferStats {
|
||||
fn default() -> Self {
|
||||
Self::start(500) // Default granularity is 500
|
||||
Self::start(500) // Default granularity is 500 ms
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
Expose a WebSocket server to your Tauri frontend.
|
||||
Open a WebSocket connection using a Rust client in JS.
|
||||
|
||||
| Platform | Supported |
|
||||
| -------- | --------- |
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_WEBSOCKET__=function(){"use strict";function e(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 t(e,t,s,n,r){if("function"==typeof t?e!==t||!r:!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 s,n,r;"function"==typeof SuppressedError&&SuppressedError;class i{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:i,id:a})=>{if(a===e(this,n,"f")){t(this,n,a+1),e(this,s,"f").call(this,i);const o=Object.keys(e(this,r,"f"));if(o.length>0){let i=a+1;for(const t of o.sort()){if(parseInt(t)!==i)break;{const n=e(this,r,"f")[t];delete e(this,r,"f")[t],e(this,s,"f").call(this,n),i+=1}}t(this,n,i)}}else e(this,r,"f")[a.toString()]=i}))}set onmessage(e){t(this,s,e)}get onmessage(){return e(this,s,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}async function a(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}s=new WeakMap,n=new WeakMap,r=new WeakMap;class o{constructor(e,t){this.id=e,this.listeners=t}static async connect(e,t){const s=[],n=new i;return n.onmessage=e=>{s.forEach((t=>{t(e)}))},t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries())),await a("plugin:websocket|connect",{url:e,onMessage:n,config:t}).then((e=>new o(e,s)))}addListener(e){this.listeners.push(e)}async send(e){let t;if("string"==typeof e)t={type:"Text",data:e};else if("object"==typeof e&&"type"in e)t=e;else{if(!Array.isArray(e))throw new Error("invalid `message` type, expected a `{ type: string, data: any }` object, a string or a numeric array");t={type:"Binary",data:e}}await a("plugin:websocket|send",{id:this.id,message:t})}async disconnect(){await this.send({type:"Close",data:{code:1e3,reason:"Disconnected by client"}})}}return o}();Object.defineProperty(window.__TAURI__,"websocket",{value:__TAURI_PLUGIN_WEBSOCKET__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_WEBSOCKET__=function(){"use strict";function e(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 t(e,t,s,n,r){if("function"==typeof t?e!==t||!r:!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 s,n,r;"function"==typeof SuppressedError&&SuppressedError;const i="__TAURI_TO_IPC_KEY__";class a{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,s.set(this,(()=>{})),n.set(this,0),r.set(this,{}),this.id=function(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}((({message:i,id:a})=>{if(a===e(this,n,"f")){t(this,n,a+1),e(this,s,"f").call(this,i);const o=Object.keys(e(this,r,"f"));if(o.length>0){let i=a+1;for(const t of o.sort()){if(parseInt(t)!==i)break;{const n=e(this,r,"f")[t];delete e(this,r,"f")[t],e(this,s,"f").call(this,n),i+=1}}t(this,n,i)}}else e(this,r,"f")[a.toString()]=i}))}set onmessage(e){t(this,s,e)}get onmessage(){return e(this,s,"f")}[(s=new WeakMap,n=new WeakMap,r=new WeakMap,i)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[i]()}}async function o(e,t={},s){return window.__TAURI_INTERNALS__.invoke(e,t,s)}class c{constructor(e,t){this.id=e,this.listeners=t}static async connect(e,t){const s=[],n=new a;return n.onmessage=e=>{s.forEach((t=>{t(e)}))},t?.headers&&(t.headers=Array.from(new Headers(t.headers).entries())),await o("plugin:websocket|connect",{url:e,onMessage:n,config:t}).then((e=>new c(e,s)))}addListener(e){this.listeners.push(e)}async send(e){let t;if("string"==typeof e)t={type:"Text",data:e};else if("object"==typeof e&&"type"in e)t=e;else{if(!Array.isArray(e))throw new Error("invalid `message` type, expected a `{ type: string, data: any }` object, a string or a numeric array");t={type:"Binary",data:e}}await o("plugin:websocket|send",{id:this.id,message:t})}async disconnect(){await this.send({type:"Close",data:{code:1e3,reason:"Disconnected by client"}})}}return c}();Object.defineProperty(window.__TAURI__,"websocket",{value:__TAURI_PLUGIN_WEBSOCKET__})}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.4",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.4.7"
|
||||
},
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
//! [](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/websocket)
|
||||
//!
|
||||
//! Expose a WebSocket server to your Tauri frontend.
|
||||
//! Open a WebSocket connection using a Rust client in JS.
|
||||
|
||||
#](https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/window-state)
|
||||
//!
|
||||
//! Save window positions and sizes and restore them when the app is reopened.
|
||||
|
||||
#![doc(
|
||||
|
||||
Generated
+250
-252
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user