mirror of
https://github.com/tauri-apps/tauri.git
synced 2026-04-11 10:43:31 +02:00
Compare commits
6 Commits
@tauri-app
...
1.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec484c54a | ||
|
|
1f06c2d00e | ||
|
|
6749be08be | ||
|
|
bf708a21a8 | ||
|
|
f6d81dfe08 | ||
|
|
50aabad1f6 |
48
.github/workflows/publish-hotfix.yml
vendored
Normal file
48
.github/workflows/publish-hotfix.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: version or publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '1.*'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 65
|
||||
outputs:
|
||||
change: ${{ steps.covector.outputs.change }}
|
||||
commandRan: ${{ steps.covector.outputs.commandRan }}
|
||||
successfulPublish: ${{ steps.covector.outputs.successfulPublish }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: yarn
|
||||
cache-dependency-path: tooling/*/yarn.lock
|
||||
|
||||
- name: cargo login
|
||||
run: cargo login ${{ secrets.ORG_CRATES_IO_TOKEN }}
|
||||
- name: git config
|
||||
run: |
|
||||
git config --global user.name "${{ github.event.pusher.name }}"
|
||||
git config --global user.email "${{ github.event.pusher.email }}"
|
||||
|
||||
- name: covector version or publish (publish when no change files present)
|
||||
uses: jbolda/covector/packages/action@covector-v0
|
||||
id: covector
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
|
||||
CARGO_AUDIT_OPTIONS: ${{ secrets.CARGO_AUDIT_OPTIONS }}
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
command: 'version-or-publish'
|
||||
createRelease: true
|
||||
9
Cargo.lock
generated
9
Cargo.lock
generated
@@ -4021,7 +4021,7 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.6.5"
|
||||
version = "1.6.8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
@@ -4036,6 +4036,7 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"getrandom 0.2.14",
|
||||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
@@ -4180,7 +4181,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.14.7"
|
||||
version = "0.14.8"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"cocoa",
|
||||
@@ -5515,9 +5516,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.24.8"
|
||||
version = "0.24.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a04e72739ee84a218e3dbf8625888eadc874285637003ed21ab96a1bbbb538ec"
|
||||
checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"block",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[0.14.8]
|
||||
|
||||
### Security fixes
|
||||
|
||||
- [`f6d81dfe0`](https://www.github.com/tauri-apps/tauri/commit/f6d81dfe0871e0ccd012e5190d41e3767e733608) Only process IPC commands from the main frame.
|
||||
|
||||
## \[0.14.7]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.14.7"
|
||||
version = "0.14.8"
|
||||
authors = [ "Tauri Programme within The Commons Conservancy" ]
|
||||
categories = [ "gui", "web-programming" ]
|
||||
license = "Apache-2.0 OR MIT"
|
||||
@@ -13,7 +13,7 @@ exclude = [ "CHANGELOG.md", "/target" ]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
wry = { version = "0.24.6", default-features = false, features = [ "file-drop", "protocol" ] }
|
||||
wry = { version = "0.24.10", default-features = false, features = [ "file-drop", "protocol" ] }
|
||||
tauri-runtime = { version = "0.14.3", path = "../tauri-runtime" }
|
||||
tauri-utils = { version = "1.5.4", path = "../tauri-utils" }
|
||||
uuid = { version = "1", features = [ "v4" ] }
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## \[1.6.8]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`1f06c2d00`](https://www.github.com/tauri-apps/tauri/commit/1f06c2d00eb02b17a8ce450d6f21ef4510b59d91) ([#9960](https://www.github.com/tauri-apps/tauri/pull/9960)) Added `test::INVOKE_KEY` to be used in `tauri::InvokePayload` when testing using `test::assert_ipc_response` and `test::get_ipc_response`.
|
||||
|
||||
## \[1.6.7]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [`50aabad1f`](https://www.github.com/tauri-apps/tauri/commit/50aabad1f602066417d815b0f2d2c5948848bb00)([#9818](https://www.github.com/tauri-apps/tauri/pull/9818)) On Windows, fix flashing PowerShell for updates for NSIS installer, and address possible "permission denied" problems.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- [`f6d81dfe0`](https://www.github.com/tauri-apps/tauri/commit/f6d81dfe0871e0ccd012e5190d41e3767e733608) Only process IPC commands from the main frame.
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `tauri-runtime-wry@0.14.8`
|
||||
|
||||
## \[1.6.6]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -10,7 +10,7 @@ license = "Apache-2.0 OR MIT"
|
||||
name = "tauri"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/tauri-apps/tauri"
|
||||
version = "1.6.6"
|
||||
version = "1.6.8"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
no-default-features = true
|
||||
@@ -61,7 +61,7 @@ once_cell = "1"
|
||||
tauri-runtime = { version = "0.14.3", path = "../tauri-runtime" }
|
||||
tauri-macros = { version = "1.4.4", path = "../tauri-macros" }
|
||||
tauri-utils = { version = "1.5.4", features = [ "resources" ], path = "../tauri-utils" }
|
||||
tauri-runtime-wry = { version = "0.14.7", path = "../tauri-runtime-wry", optional = true }
|
||||
tauri-runtime-wry = { version = "0.14.8", path = "../tauri-runtime-wry", optional = true }
|
||||
rand = "0.8"
|
||||
semver = { version = "1.0", features = [ "serde" ] }
|
||||
serde_repr = "0.1"
|
||||
@@ -96,6 +96,7 @@ encoding_rs = "0.8.31"
|
||||
sys-locale = { version = "0.2.3", optional = true }
|
||||
tracing = { version = "0.1", optional = true }
|
||||
indexmap = { version = "1", features = [ "std", "serde" ], optional = true }
|
||||
getrandom = { version = "0.2", features = [ "std" ] }
|
||||
|
||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||
rfd = { version = "0.10", optional = true, features = [ "gtk3", "common-controls-v6" ] }
|
||||
@@ -119,7 +120,11 @@ win7-notifications = { version = "0.4", optional = true }
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.39.0"
|
||||
features = [ "Win32_Foundation" ]
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
"Win32_UI_Shell"
|
||||
]
|
||||
|
||||
[build-dependencies]
|
||||
heck = "0.5"
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
; (function () {
|
||||
;(function() {
|
||||
__RAW_freeze_prototype__
|
||||
|
||||
; (function () {
|
||||
__RAW_hotkeys__
|
||||
})()
|
||||
;(function() {
|
||||
__RAW_hotkeys__
|
||||
})()
|
||||
|
||||
__RAW_pattern_script__
|
||||
|
||||
__RAW_ipc_script__
|
||||
; (function () {
|
||||
__RAW_bundle_script__
|
||||
})()
|
||||
;(function() {
|
||||
__RAW_bundle_script__
|
||||
})()
|
||||
|
||||
__RAW_listen_function__
|
||||
|
||||
@@ -22,11 +22,5 @@
|
||||
|
||||
__RAW_event_initialization_script__
|
||||
|
||||
if (window.ipc) {
|
||||
window.__TAURI_INVOKE__('__initialized', { url: window.location.href })
|
||||
} else {
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
window.__TAURI_INVOKE__('__initialized', { url: window.location.href })
|
||||
})
|
||||
}
|
||||
window.__TAURI_INVOKE__('__initialized', { url: window.location.href })
|
||||
})()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
;
|
||||
(function () {
|
||||
(function() {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
@@ -18,6 +18,15 @@
|
||||
*/
|
||||
const isolationOrigin = __TEMPLATE_isolation_origin__
|
||||
|
||||
/**
|
||||
* A runtime generated key to ensure an IPC call comes from an initialized frame.
|
||||
*
|
||||
* This is declared outside the `window.__TAURI_INVOKE__` definition to prevent
|
||||
* the key from being leaked by `window.__TAURI_INVOKE__.toString()`.
|
||||
* @var {string} __TEMPLATE_invoke_key__
|
||||
*/
|
||||
const __TAURI_INVOKE_KEY__ = __TEMPLATE_invoke_key__
|
||||
|
||||
/**
|
||||
* @type {{queue: object[], ready: boolean, frame: HTMLElement | null}}
|
||||
*/
|
||||
@@ -85,6 +94,7 @@
|
||||
Object.defineProperty(window, '__TAURI_IPC__', {
|
||||
// todo: JSDoc this function
|
||||
value: Object.freeze((message) => {
|
||||
message.__TAURI_INVOKE_KEY__ = __TAURI_INVOKE_KEY__
|
||||
switch (pattern) {
|
||||
case 'brownfield':
|
||||
window.__TAURI_POST_MESSAGE__(message)
|
||||
|
||||
@@ -989,6 +989,9 @@ pub struct Builder<R: Runtime> {
|
||||
/// The script that initializes the `window.__TAURI_POST_MESSAGE__` function.
|
||||
invoke_initialization_script: String,
|
||||
|
||||
/// Invoke key. Used to secure IPC calls.
|
||||
pub(crate) invoke_key: String,
|
||||
|
||||
/// The setup hook.
|
||||
setup: SetupHook<R>,
|
||||
|
||||
@@ -1047,6 +1050,7 @@ impl<R: Runtime> Builder<R> {
|
||||
invoke_responder: Arc::new(window_invoke_responder),
|
||||
invoke_initialization_script:
|
||||
format!("Object.defineProperty(window, '__TAURI_POST_MESSAGE__', {{ value: (message) => window.ipc.postMessage({}(message)) }})", crate::manager::STRINGIFY_IPC_MESSAGE_FN),
|
||||
invoke_key: crate::generate_invoke_key().expect("failed to generate invoke key"),
|
||||
on_page_load: Box::new(|_, _| ()),
|
||||
pending_windows: Default::default(),
|
||||
plugins: PluginStore::default(),
|
||||
@@ -1569,6 +1573,7 @@ impl<R: Runtime> Builder<R> {
|
||||
self.window_event_listeners,
|
||||
(self.menu, self.menu_event_listeners),
|
||||
(self.invoke_responder, self.invoke_initialization_script),
|
||||
self.invoke_key,
|
||||
);
|
||||
|
||||
let http_scheme = manager.config().tauri.security.dangerous_use_http_scheme;
|
||||
|
||||
@@ -131,6 +131,12 @@ pub enum Error {
|
||||
/// The Window's raw handle is invalid for the platform.
|
||||
#[error("Unexpected `raw_window_handle` for the current platform")]
|
||||
InvalidWindowHandle,
|
||||
/// Something went wrong with the CSPRNG.
|
||||
#[error("unable to generate random bytes from the operating system: {0}")]
|
||||
Csprng(#[from] getrandom::Error),
|
||||
/// Bad `__TAURI_INVOKE_KEY__` value received in ipc message.
|
||||
#[error("bad __TAURI_INVOKE_KEY__ value received in ipc message")]
|
||||
InvokeKey,
|
||||
}
|
||||
|
||||
pub(crate) fn into_anyhow<T: std::fmt::Display>(err: T) -> anyhow::Error {
|
||||
|
||||
@@ -35,6 +35,7 @@ pub type OnPageLoad<R> = dyn Fn(Window<R>, PageLoadPayload) + Send + Sync + 'sta
|
||||
#[default_template("../scripts/ipc.js")]
|
||||
pub(crate) struct IpcJavascript<'a> {
|
||||
pub(crate) isolation_origin: &'a str,
|
||||
pub(crate) invoke_key: &'a str,
|
||||
}
|
||||
|
||||
#[cfg(feature = "isolation")]
|
||||
@@ -66,6 +67,10 @@ pub struct InvokePayload {
|
||||
#[serde(rename = "__tauriModule")]
|
||||
#[doc(hidden)]
|
||||
pub tauri_module: Option<String>,
|
||||
/// A secret key that only Tauri initialized frames have.
|
||||
#[serde(rename = "__TAURI_INVOKE_KEY__")]
|
||||
#[doc(hidden)]
|
||||
pub invoke_key: Option<String>,
|
||||
/// The success callback.
|
||||
pub callback: CallbackFn,
|
||||
/// The error callback.
|
||||
|
||||
@@ -1091,3 +1091,52 @@ mod test_utils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple dependency-free string encoder using [Z85].
|
||||
mod z85 {
|
||||
const TABLE: &[u8; 85] =
|
||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
|
||||
|
||||
/// Encode bytes with [Z85].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the input bytes are not a multiple of 4.
|
||||
pub fn encode(bytes: &[u8]) -> String {
|
||||
assert_eq!(bytes.len() % 4, 0);
|
||||
|
||||
let mut buf = String::with_capacity(bytes.len() * 5 / 4);
|
||||
for chunk in bytes.chunks_exact(4) {
|
||||
let mut chars = [0u8; 5];
|
||||
let mut chunk = u32::from_be_bytes(chunk.try_into().unwrap()) as usize;
|
||||
for byte in chars.iter_mut().rev() {
|
||||
*byte = TABLE[chunk % 85];
|
||||
chunk /= 85;
|
||||
}
|
||||
|
||||
buf.push_str(std::str::from_utf8(&chars).unwrap());
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn encode() {
|
||||
assert_eq!(
|
||||
super::encode(&[0x86, 0x4F, 0xD2, 0x6F, 0xB5, 0x59, 0xF7, 0x5B]),
|
||||
"HelloWorld"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random 128-bit [Z85] encoded [`String`].
|
||||
///
|
||||
/// [Z85]: https://rfc.zeromq.org/spec/32/
|
||||
pub(crate) fn generate_invoke_key() -> Result<String> {
|
||||
let mut bytes = [0u8; 16];
|
||||
getrandom::getrandom(&mut bytes)?;
|
||||
Ok(z85::encode(&bytes))
|
||||
}
|
||||
|
||||
@@ -238,6 +238,8 @@ pub struct InnerWindowManager<R: Runtime> {
|
||||
invoke_initialization_script: String,
|
||||
/// Application pattern.
|
||||
pub(crate) pattern: Pattern,
|
||||
/// A runtime generated key to ensure an IPC call comes from an initialized frame.
|
||||
invoke_key: String,
|
||||
}
|
||||
|
||||
impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
|
||||
@@ -252,6 +254,7 @@ impl<R: Runtime> fmt::Debug for InnerWindowManager<R> {
|
||||
.field("package_info", &self.package_info)
|
||||
.field("menu", &self.menu)
|
||||
.field("pattern", &self.pattern)
|
||||
.field("invoke_key", &self.invoke_key)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -303,6 +306,7 @@ impl<R: Runtime> WindowManager<R> {
|
||||
window_event_listeners: Vec<GlobalWindowEventListener<R>>,
|
||||
(menu, menu_event_listeners): (Option<Menu>, Vec<GlobalMenuEventListener<R>>),
|
||||
(invoke_responder, invoke_initialization_script): (Arc<InvokeResponder<R>>, String),
|
||||
invoke_key: String,
|
||||
) -> Self {
|
||||
// generate a random isolation key at runtime
|
||||
#[cfg(feature = "isolation")]
|
||||
@@ -333,6 +337,7 @@ impl<R: Runtime> WindowManager<R> {
|
||||
window_event_listeners: Arc::new(window_event_listeners),
|
||||
invoke_responder,
|
||||
invoke_initialization_script,
|
||||
invoke_key,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -449,6 +454,7 @@ impl<R: Runtime> WindowManager<R> {
|
||||
}
|
||||
_ => "".to_string(),
|
||||
},
|
||||
invoke_key: self.invoke_key(),
|
||||
}
|
||||
.render_default(&Default::default())?;
|
||||
|
||||
@@ -896,6 +902,10 @@ impl<R: Runtime> WindowManager<R> {
|
||||
listeners = self.event_listeners_object_name()
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn invoke_key(&self) -> &str {
|
||||
&self.inner.invoke_key
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -917,6 +927,7 @@ mod test {
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
(std::sync::Arc::new(|_, _, _, _| ()), "".into()),
|
||||
crate::generate_invoke_key().unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(custom_protocol)]
|
||||
|
||||
@@ -171,6 +171,7 @@ impl Scope {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RemoteDomainAccessScope;
|
||||
use crate::sealed::ManagerBase;
|
||||
use crate::{
|
||||
api::ipc::CallbackFn,
|
||||
test::{assert_ipc_response, mock_app, MockRuntime},
|
||||
@@ -190,7 +191,7 @@ mod tests {
|
||||
(app, window)
|
||||
}
|
||||
|
||||
fn app_version_payload() -> InvokePayload {
|
||||
fn app_version_payload(invoke_key: &str) -> InvokePayload {
|
||||
let callback = CallbackFn(0);
|
||||
let error = CallbackFn(1);
|
||||
|
||||
@@ -208,10 +209,11 @@ mod tests {
|
||||
callback,
|
||||
error,
|
||||
inner: serde_json::Value::Object(payload),
|
||||
invoke_key: Some(invoke_key.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_test_payload() -> InvokePayload {
|
||||
fn plugin_test_payload(invoke_key: &str) -> InvokePayload {
|
||||
let callback = CallbackFn(0);
|
||||
let error = CallbackFn(1);
|
||||
|
||||
@@ -221,19 +223,25 @@ mod tests {
|
||||
callback,
|
||||
error,
|
||||
inner: Default::default(),
|
||||
invoke_key: Some(invoke_key.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke_key(app: &App<MockRuntime>) -> &str {
|
||||
app.manager().invoke_key()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_not_defined() {
|
||||
let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app")
|
||||
let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("app.tauri.app")
|
||||
.add_window("other")
|
||||
.enable_tauri_api()]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(&crate::window::ipc_scope_not_found_error_message(
|
||||
"main",
|
||||
"https://tauri.app/",
|
||||
@@ -243,28 +251,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn scope_not_defined_for_window() {
|
||||
let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app")
|
||||
let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app")
|
||||
.add_window("second")
|
||||
.enable_tauri_api()]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(&crate::window::ipc_scope_window_error_message("main")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_not_defined_for_url() {
|
||||
let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com")
|
||||
let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("github.com")
|
||||
.add_window("main")
|
||||
.enable_tauri_api()]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(&crate::window::ipc_scope_domain_error_message(
|
||||
"https://tauri.app/",
|
||||
)),
|
||||
@@ -281,18 +291,19 @@ mod tests {
|
||||
.add_window("main")
|
||||
.enable_tauri_api(),
|
||||
]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Ok(app.package_info().version.to_string().as_str()),
|
||||
);
|
||||
|
||||
window.navigate("https://blog.tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(&crate::window::ipc_scope_domain_error_message(
|
||||
"https://blog.tauri.app/",
|
||||
)),
|
||||
@@ -301,7 +312,7 @@ mod tests {
|
||||
window.navigate("https://sub.tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Ok(app.package_info().version.to_string().as_str()),
|
||||
);
|
||||
|
||||
@@ -309,7 +320,7 @@ mod tests {
|
||||
window.navigate("https://dev.tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(&crate::window::ipc_scope_not_found_error_message(
|
||||
"test",
|
||||
"https://dev.tauri.app/",
|
||||
@@ -322,53 +333,57 @@ mod tests {
|
||||
let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app")
|
||||
.add_window("main")
|
||||
.enable_tauri_api()]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app/inner/path".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Ok(app.package_info().version.to_string().as_str()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tauri_api_not_allowed() {
|
||||
let (_app, mut window) = test_context(vec![
|
||||
let (app, mut window) = test_context(vec![
|
||||
RemoteDomainAccessScope::new("tauri.app").add_window("main")
|
||||
]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
app_version_payload(),
|
||||
app_version_payload(invoke_key),
|
||||
Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_allowed() {
|
||||
let (_app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app")
|
||||
let (app, mut window) = test_context(vec![RemoteDomainAccessScope::new("tauri.app")
|
||||
.add_window("main")
|
||||
.add_plugin(PLUGIN_NAME)]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
plugin_test_payload(),
|
||||
plugin_test_payload(invoke_key),
|
||||
Err(&format!("plugin {PLUGIN_NAME} not found")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_not_allowed() {
|
||||
let (_app, mut window) = test_context(vec![
|
||||
let (app, mut window) = test_context(vec![
|
||||
RemoteDomainAccessScope::new("tauri.app").add_window("main")
|
||||
]);
|
||||
let invoke_key = invoke_key(&app);
|
||||
|
||||
window.navigate("https://tauri.app".parse().unwrap());
|
||||
assert_ipc_response(
|
||||
&window,
|
||||
plugin_test_payload(),
|
||||
plugin_test_payload(invoke_key),
|
||||
Err(crate::window::IPC_SCOPE_DOES_NOT_ALLOW),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
//! callback: tauri::api::ipc::CallbackFn(0),
|
||||
//! error: tauri::api::ipc::CallbackFn(1),
|
||||
//! inner: serde_json::Value::Null,
|
||||
//! invoke_key: Some(tauri::test::INVOKE_KEY.into()),
|
||||
//! },
|
||||
//! Ok(())
|
||||
//! );
|
||||
@@ -85,6 +86,9 @@ use tauri_utils::{
|
||||
config::{CliConfig, Config, PatternKind, TauriConfig},
|
||||
};
|
||||
|
||||
/// The invoke key used for tests.
|
||||
pub const INVOKE_KEY: &str = "__invoke-key__";
|
||||
|
||||
/// A key for an [`Ipc`] call.
|
||||
#[derive(Eq, PartialEq)]
|
||||
struct IpcKey {
|
||||
@@ -197,6 +201,8 @@ pub fn mock_context<A: Assets>(assets: A) -> crate::Context<A> {
|
||||
pub fn mock_builder() -> Builder<MockRuntime> {
|
||||
let mut builder = Builder::<MockRuntime>::new().manage(Ipc(Default::default()));
|
||||
|
||||
builder.invoke_key = INVOKE_KEY.to_string();
|
||||
|
||||
builder.invoke_responder = Arc::new(|window, response, callback, error| {
|
||||
let window_ = window.clone();
|
||||
let ipc = window_.state::<Ipc>();
|
||||
@@ -250,6 +256,7 @@ pub fn mock_app() -> App<MockRuntime> {
|
||||
/// callback: tauri::api::ipc::CallbackFn(0),
|
||||
/// error: tauri::api::ipc::CallbackFn(1),
|
||||
/// inner: serde_json::Value::Null,
|
||||
/// invoke_key: Some(tauri::test::INVOKE_KEY.into()),
|
||||
/// },
|
||||
/// // the expected response is a success with the "pong" payload
|
||||
/// // we could also use Err("error message") here to ensure the command failed
|
||||
@@ -303,6 +310,7 @@ pub fn assert_ipc_response<T: Serialize + Debug>(
|
||||
/// callback: tauri::api::ipc::CallbackFn(0),
|
||||
/// error: tauri::api::ipc::CallbackFn(1),
|
||||
/// inner: serde_json::Value::Null,
|
||||
/// invoke_key: Some(tauri::test::INVOKE_KEY.into()),
|
||||
/// });
|
||||
/// assert_eq!(res, Ok("pong".into()))
|
||||
/// ```
|
||||
|
||||
@@ -815,6 +815,15 @@ fn copy_files_and_run<R: Read + Seek>(
|
||||
// shouldn't drop but we should be able to pass the reference so we can drop it once the installation
|
||||
// is done, otherwise we have a huge memory leak.
|
||||
|
||||
use windows::{
|
||||
core::{HSTRING, PCWSTR},
|
||||
w,
|
||||
Win32::{
|
||||
Foundation::HWND,
|
||||
UI::{Shell::ShellExecuteW, WindowsAndMessaging::SW_SHOW},
|
||||
},
|
||||
};
|
||||
|
||||
let tmp_dir = tempfile::Builder::new().tempdir()?.into_path();
|
||||
|
||||
// extract the buffer to the tmp_dir
|
||||
@@ -832,20 +841,14 @@ fn copy_files_and_run<R: Read + Seek>(
|
||||
|p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
|
||||
);
|
||||
|
||||
let current_exe_args = env.args.clone();
|
||||
let mut installer_args_shellexecute;
|
||||
|
||||
for path in paths {
|
||||
let found_path = path?.path();
|
||||
// we support 2 type of files exe & msi for now
|
||||
// If it's an `exe` we expect an installer not a runtime.
|
||||
// If it's an `exe` we expect an NSIS installer.
|
||||
if found_path.extension() == Some(OsStr::new("exe")) {
|
||||
// we need to wrap the installer path in quotes for Start-Process
|
||||
let mut installer_path = std::ffi::OsString::new();
|
||||
installer_path.push("\"");
|
||||
installer_path.push(&found_path);
|
||||
installer_path.push("\"");
|
||||
|
||||
let installer_args = [
|
||||
installer_args_shellexecute = [
|
||||
config
|
||||
.tauri
|
||||
.updater
|
||||
@@ -853,34 +856,13 @@ fn copy_files_and_run<R: Read + Seek>(
|
||||
.install_mode
|
||||
.nsis_args()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.map(|a| a.to_string())
|
||||
.collect(),
|
||||
vec!["/ARGS".to_string()],
|
||||
current_exe_args,
|
||||
config
|
||||
.tauri
|
||||
.updater
|
||||
.windows
|
||||
.installer_args
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>(),
|
||||
env.args.clone(),
|
||||
config.tauri.updater.windows.installer_args.clone(),
|
||||
]
|
||||
.concat();
|
||||
|
||||
// Run the EXE
|
||||
let mut cmd = Command::new(powershell_path);
|
||||
cmd
|
||||
.args(["-NoProfile", "-WindowStyle", "Hidden", "Start-Process"])
|
||||
.arg(installer_path);
|
||||
if !installer_args.is_empty() {
|
||||
cmd.arg("-ArgumentList").arg(installer_args.join(", "));
|
||||
}
|
||||
cmd
|
||||
.spawn()
|
||||
.expect("Running NSIS installer from powershell has failed to start");
|
||||
|
||||
exit(0);
|
||||
} else if found_path.extension() == Some(OsStr::new("msi")) {
|
||||
if with_elevated_task {
|
||||
if let Some(bin_name) = current_exe()
|
||||
@@ -932,7 +914,7 @@ fn copy_files_and_run<R: Read + Seek>(
|
||||
msi_path.push(&found_path);
|
||||
msi_path.push("\"\"\"");
|
||||
|
||||
let installer_args = [
|
||||
let msi_installer_args = [
|
||||
config.tauri.updater.windows.install_mode.msiexec_args(),
|
||||
config
|
||||
.tauri
|
||||
@@ -960,34 +942,57 @@ fn copy_files_and_run<R: Read + Seek>(
|
||||
])
|
||||
.arg("/i,")
|
||||
.arg(&msi_path)
|
||||
.arg(format!(", {}, /promptrestart;", installer_args.join(", ")))
|
||||
.arg(format!(
|
||||
", {}, /promptrestart;",
|
||||
msi_installer_args.join(", ")
|
||||
))
|
||||
.arg("Start-Process")
|
||||
.arg(current_executable);
|
||||
|
||||
if !current_exe_args.is_empty() {
|
||||
powershell_cmd
|
||||
.arg("-ArgumentList")
|
||||
.arg(current_exe_args.join(", "));
|
||||
if !env.args.is_empty() {
|
||||
powershell_cmd.arg("-ArgumentList").arg(env.args.join(", "));
|
||||
}
|
||||
|
||||
let powershell_install_res = powershell_cmd.spawn();
|
||||
if powershell_install_res.is_err() {
|
||||
// fallback to running msiexec directly - relaunch won't be available
|
||||
// we use this here in case powershell fails in an older machine somehow
|
||||
let msiexec_path = system_root.as_ref().map_or_else(
|
||||
|_| "msiexec.exe".to_string(),
|
||||
|p| format!("{p}\\System32\\msiexec.exe"),
|
||||
);
|
||||
let _ = Command::new(msiexec_path)
|
||||
.arg("/i")
|
||||
.arg(msi_path)
|
||||
.args(installer_args)
|
||||
.arg("/promptrestart")
|
||||
.spawn();
|
||||
installer_args_shellexecute = [
|
||||
config
|
||||
.tauri
|
||||
.updater
|
||||
.windows
|
||||
.install_mode
|
||||
.msiexec_args()
|
||||
.iter()
|
||||
.map(|a| a.to_string())
|
||||
.collect(),
|
||||
config.tauri.updater.windows.installer_args.clone(),
|
||||
]
|
||||
.concat();
|
||||
installer_args_shellexecute.push("/promptrestart".to_string());
|
||||
} else {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file = HSTRING::from(found_path.as_os_str());
|
||||
let parameters = HSTRING::from(installer_args_shellexecute.join(" "));
|
||||
let ret = unsafe {
|
||||
ShellExecuteW(
|
||||
HWND(0),
|
||||
w!("open"),
|
||||
&file,
|
||||
¶meters,
|
||||
PCWSTR::null(),
|
||||
SW_SHOW.0 as _,
|
||||
)
|
||||
};
|
||||
if ret.0 <= 32 {
|
||||
return Err(Error::Io(std::io::Error::last_os_error()));
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -31,8 +31,8 @@ use crate::{
|
||||
sealed::ManagerBase,
|
||||
sealed::RuntimeOrDispatch,
|
||||
utils::config::{WindowConfig, WindowUrl},
|
||||
CursorIcon, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver, Manager,
|
||||
PageLoadPayload, Runtime, Theme, WindowEvent,
|
||||
CursorIcon, Error, EventLoopMessage, Icon, Invoke, InvokeError, InvokeMessage, InvokeResolver,
|
||||
Manager, Runtime, Theme, WindowEvent,
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
@@ -1550,9 +1550,35 @@ impl<R: Runtime> Window<R> {
|
||||
self.current_url = url;
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument("window::on_message"))]
|
||||
/// Handles this window receiving an [`InvokeMessage`].
|
||||
pub fn on_message(self, payload: InvokePayload) -> crate::Result<()> {
|
||||
let manager = self.manager.clone();
|
||||
|
||||
// ensure the passed key matches what our manager should have injected
|
||||
let expected = manager.invoke_key();
|
||||
match payload.invoke_key.as_deref() {
|
||||
Some(sent) if sent == expected => { /* good */ }
|
||||
Some(sent) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!("__TAURI_INVOKE_KEY__ expected {expected} but received {sent}");
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
eprintln!("__TAURI_INVOKE_KEY__ expected {expected} but received {sent}");
|
||||
|
||||
return Err(Error::InvokeKey);
|
||||
}
|
||||
None => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!("received ipc message without a __TAURI_INVOKE_KEY__");
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
eprintln!("received ipc message without a __TAURI_INVOKE_KEY__");
|
||||
|
||||
return Err(Error::InvokeKey);
|
||||
}
|
||||
}
|
||||
|
||||
let current_url = self.url();
|
||||
let config_url = manager.get_url();
|
||||
let is_local = config_url.make_relative(¤t_url).is_some();
|
||||
@@ -1574,54 +1600,53 @@ impl<R: Runtime> Window<R> {
|
||||
}
|
||||
}
|
||||
};
|
||||
match payload.cmd.as_str() {
|
||||
"__initialized" => {
|
||||
let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
|
||||
manager.run_on_page_load(self, payload);
|
||||
}
|
||||
_ => {
|
||||
let message = InvokeMessage::new(
|
||||
self.clone(),
|
||||
manager.state(),
|
||||
payload.cmd.to_string(),
|
||||
payload.inner,
|
||||
);
|
||||
let resolver = InvokeResolver::new(self, payload.callback, payload.error);
|
||||
let invoke = Invoke { message, resolver };
|
||||
|
||||
if !is_local && scope.is_none() {
|
||||
invoke.resolver.reject(scope_not_found_error_message);
|
||||
if "__initialized" == &payload.cmd {
|
||||
let payload = serde_json::from_value(payload.inner)?;
|
||||
manager.run_on_page_load(self, payload);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let message = InvokeMessage::new(
|
||||
self.clone(),
|
||||
manager.state(),
|
||||
payload.cmd.to_string(),
|
||||
payload.inner,
|
||||
);
|
||||
let resolver = InvokeResolver::new(self, payload.callback, payload.error);
|
||||
let invoke = Invoke { message, resolver };
|
||||
|
||||
if !is_local && scope.is_none() {
|
||||
invoke.resolver.reject(scope_not_found_error_message);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(module) = &payload.tauri_module {
|
||||
if !is_local && scope.map(|s| !s.enables_tauri_api()).unwrap_or_default() {
|
||||
invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW);
|
||||
return Ok(());
|
||||
}
|
||||
crate::endpoints::handle(
|
||||
module.to_string(),
|
||||
invoke,
|
||||
manager.config(),
|
||||
manager.package_info(),
|
||||
);
|
||||
} else if payload.cmd.starts_with("plugin:") {
|
||||
if !is_local {
|
||||
let command = invoke.message.command.replace("plugin:", "");
|
||||
let plugin_name = command.split('|').next().unwrap().to_string();
|
||||
if !scope
|
||||
.map(|s| s.plugins().contains(&plugin_name))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(module) = &payload.tauri_module {
|
||||
if !is_local && scope.map(|s| !s.enables_tauri_api()).unwrap_or_default() {
|
||||
invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW);
|
||||
return Ok(());
|
||||
}
|
||||
crate::endpoints::handle(
|
||||
module.to_string(),
|
||||
invoke,
|
||||
manager.config(),
|
||||
manager.package_info(),
|
||||
);
|
||||
} else if payload.cmd.starts_with("plugin:") {
|
||||
if !is_local {
|
||||
let command = invoke.message.command.replace("plugin:", "");
|
||||
let plugin_name = command.split('|').next().unwrap().to_string();
|
||||
if !scope
|
||||
.map(|s| s.plugins().contains(&plugin_name))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
invoke.resolver.reject(IPC_SCOPE_DOES_NOT_ALLOW);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
manager.extend_api(invoke);
|
||||
} else {
|
||||
manager.run_invoke_handler(invoke);
|
||||
}
|
||||
}
|
||||
manager.extend_api(invoke);
|
||||
} else {
|
||||
manager.run_invoke_handler(invoke);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
33
examples/api/src-tauri/Cargo.lock
generated
33
examples/api/src-tauri/Cargo.lock
generated
@@ -1806,6 +1806,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@@ -4018,7 +4024,7 @@ checksum = "69758bda2e78f098e4ccb393021a0963bb3442eac05f135c30f61b7370bbafae"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "1.6.0"
|
||||
version = "1.6.6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
@@ -4031,10 +4037,11 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"getrandom 0.2.12",
|
||||
"glib",
|
||||
"glob",
|
||||
"gtk",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"ico 0.2.0",
|
||||
"ignore",
|
||||
@@ -4083,12 +4090,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.5.1"
|
||||
version = "1.5.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"dirs-next",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"json-patch",
|
||||
"quote",
|
||||
"semver",
|
||||
@@ -4102,7 +4109,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"brotli",
|
||||
@@ -4126,9 +4133,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "1.4.3"
|
||||
version = "1.4.4"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
@@ -4138,7 +4145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -4157,7 +4164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "0.14.4"
|
||||
version = "0.14.7"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"cocoa 0.24.1",
|
||||
@@ -4176,7 +4183,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "1.5.3"
|
||||
version = "1.5.4"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"brotli",
|
||||
@@ -4184,7 +4191,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"getrandom 0.2.12",
|
||||
"glob",
|
||||
"heck 0.4.1",
|
||||
"heck 0.5.0",
|
||||
"html5ever",
|
||||
"infer 0.13.0",
|
||||
"json-patch",
|
||||
@@ -5494,9 +5501,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.24.7"
|
||||
version = "0.24.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4"
|
||||
checksum = "00711278ed357350d44c749c286786ecac644e044e4da410d466212152383b45"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"block",
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"version": "1.5.14",
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"tauri": "1.6.6",
|
||||
"tauri": "1.6.8",
|
||||
"tauri-build": "1.5.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user