(chore): update codebase
This commit is contained in:
parent
fd15215f15
commit
4e8d46b20e
106 changed files with 29936 additions and 1023 deletions
|
|
@ -41,11 +41,11 @@
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0"
|
"react-dom": "^19.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2"
|
"@tauri-apps/cli": "^2.11.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
659
apps/tauri/src-tauri/Cargo.lock
generated
659
apps/tauri/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -15,42 +15,47 @@ name = "mobile_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2", features = [] }
|
tauri-build = { version = "2.6", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
image = { version = "0.25", default-features = false, features = ["jpeg"] }
|
||||||
tauri-plugin-opener = "2"
|
tauri-plugin-opener = "2.5.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tauri-plugin-deep-link = "2"
|
tauri-plugin-deep-link = "2.4.7"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))'.dependencies]
|
||||||
xcap = "0.4.1"
|
xcap = "0.4.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
[target.'cfg(target_os = "android")'.dependencies.tauri]
|
||||||
version = "2"
|
version = "2.11"
|
||||||
features = []
|
features = []
|
||||||
default-features = true
|
default-features = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.tauri]
|
[target.'cfg(target_os = "windows")'.dependencies.tauri]
|
||||||
version = "2"
|
version = "2.11"
|
||||||
features = ["compression", "common-controls-v6", "dynamic-acl"]
|
features = ["compression", "common-controls-v6", "dynamic-acl"]
|
||||||
default-features = true
|
default-features = true
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies.tauri]
|
[target.'cfg(target_os = "linux")'.dependencies.tauri]
|
||||||
version = "2"
|
version = "2.11"
|
||||||
features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"]
|
features = ["common-controls-v6", "cef", "compression", "dynamic-acl", "x11"]
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies.tauri]
|
[target.'cfg(target_os = "macos")'.dependencies.tauri]
|
||||||
version = "2"
|
version = "2.11"
|
||||||
features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"]
|
features = ["x11", "common-controls-v6", "cef", "compression", "dynamic-acl"]
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||||
tauri-plugin-barcode-scanner = "2"
|
tauri-plugin-barcode-scanner = "2.4.4"
|
||||||
|
|
||||||
[patch.crates-io.tauri]
|
[patch.crates-io.tauri]
|
||||||
git = "https://github.com/tauri-apps/tauri"
|
git = "https://github.com/tauri-apps/tauri"
|
||||||
branch = "feat/cef"
|
branch = "feat/cef"
|
||||||
|
|
||||||
|
[patch."https://github.com/tauri-apps/tauri"]
|
||||||
|
tauri-runtime = { path = "vendor/tauri-runtime" }
|
||||||
|
tauri-runtime-cef = { path = "vendor/tauri-runtime-cef" }
|
||||||
|
tauri-utils = { path = "vendor/tauri-utils" }
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,6 @@
|
||||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||||
<intent-filter >
|
<intent-filter >
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<!-- ChromeOS ARC++ uses a different action for deep links -->
|
|
||||||
<action android:name="org.chromium.arc.intent.action.VIEW" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="tensamin" />
|
<data android:scheme="tensamin" />
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import com.android.build.api.dsl.ApplicationExtension
|
||||||
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
|
|
@ -5,7 +8,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.11.0")
|
classpath("com.android.tools.build:gradle:8.11.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,7 +19,24 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
plugins.withId("com.android.application") {
|
||||||
|
extensions.configure<ApplicationExtension>("android") {
|
||||||
|
lint {
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins.withId("com.android.library") {
|
||||||
|
extensions.configure<LibraryExtension>("android") {
|
||||||
|
lint {
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register("clean").configure {
|
tasks.register("clean").configure {
|
||||||
delete("build")
|
delete("build")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
import org.apache.tools.ant.taskdefs.condition.Os
|
import org.apache.tools.ant.taskdefs.condition.Os
|
||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.GradleException
|
import org.gradle.api.GradleException
|
||||||
import org.gradle.api.logging.LogLevel
|
import org.gradle.api.logging.LogLevel
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.Internal
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.process.ExecOperations
|
||||||
|
|
||||||
open class BuildTask : DefaultTask() {
|
open class BuildTask : DefaultTask() {
|
||||||
@Input
|
@Input
|
||||||
|
|
@ -14,6 +17,13 @@ open class BuildTask : DefaultTask() {
|
||||||
@Input
|
@Input
|
||||||
var release: Boolean? = null
|
var release: Boolean? = null
|
||||||
|
|
||||||
|
@Internal
|
||||||
|
var baseProjectDir: File? = null
|
||||||
|
|
||||||
|
@get:Inject
|
||||||
|
protected open val execOperations: ExecOperations
|
||||||
|
get() = throw UnsupportedOperationException("Gradle injects ExecOperations")
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
fun assemble() {
|
fun assemble() {
|
||||||
val executable = """bun""";
|
val executable = """bun""";
|
||||||
|
|
@ -48,15 +58,16 @@ open class BuildTask : DefaultTask() {
|
||||||
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
|
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
|
||||||
val target = target ?: throw GradleException("target cannot be null")
|
val target = target ?: throw GradleException("target cannot be null")
|
||||||
val release = release ?: throw GradleException("release cannot be null")
|
val release = release ?: throw GradleException("release cannot be null")
|
||||||
|
val baseProjectDir = baseProjectDir ?: throw GradleException("baseProjectDir cannot be null")
|
||||||
val args = listOf("tauri", "android", "android-studio-script");
|
val args = listOf("tauri", "android", "android-studio-script");
|
||||||
|
|
||||||
project.exec {
|
execOperations.exec {
|
||||||
workingDir(File(project.projectDir, rootDirRel))
|
workingDir(File(baseProjectDir, rootDirRel))
|
||||||
executable(executable)
|
executable(executable)
|
||||||
args(args)
|
args(args)
|
||||||
if (project.logger.isEnabled(LogLevel.DEBUG)) {
|
if (logger.isEnabled(LogLevel.DEBUG)) {
|
||||||
args("-vv")
|
args("-vv")
|
||||||
} else if (project.logger.isEnabled(LogLevel.INFO)) {
|
} else if (logger.isEnabled(LogLevel.INFO)) {
|
||||||
args("-v")
|
args("-v")
|
||||||
}
|
}
|
||||||
if (release) {
|
if (release) {
|
||||||
|
|
@ -65,4 +76,4 @@ open class BuildTask : DefaultTask() {
|
||||||
args(listOf("--target", target))
|
args(listOf("--target", target))
|
||||||
}.assertNormalExitValue()
|
}.assertNormalExitValue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ open class RustPlugin : Plugin<Project> {
|
||||||
rootDirRel = config.rootDirRel
|
rootDirRel = config.rootDirRel
|
||||||
target = targetName
|
target = targetName
|
||||||
release = profile == "release"
|
release = profile == "release"
|
||||||
|
baseProjectDir = project.projectDir
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTask.dependsOn(targetBuildTask)
|
buildTask.dependsOn(targetBuildTask)
|
||||||
|
|
@ -82,4 +83,4 @@ open class RustPlugin : Plugin<Project> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,31 @@ fn get_screen_share_capabilities() -> ScreenShareCapabilities {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_app<R: tauri::Runtime>(
|
||||||
|
_app: &mut tauri::App<R>,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
#[cfg(any(target_os = "linux", windows))]
|
||||||
|
{
|
||||||
|
use tauri_plugin_deep_link::DeepLinkExt;
|
||||||
|
|
||||||
|
if let Err(error) = _app.deep_link().register_all() {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
eprintln!("Skipping deep link registration during dev: {error}");
|
||||||
|
} else {
|
||||||
|
return Err(Box::new(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
let builder = tauri::Builder::<tauri::Cef>::default();
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
let builder = tauri::Builder::default();
|
let builder = tauri::Builder::default();
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
|
@ -207,21 +230,7 @@ pub fn run() {
|
||||||
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
let builder = builder.plugin(tauri_plugin_barcode_scanner::init());
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.setup(|_app| {
|
.setup(setup_app)
|
||||||
#[cfg(any(target_os = "linux", windows))]
|
|
||||||
{
|
|
||||||
use tauri_plugin_deep_link::DeepLinkExt;
|
|
||||||
|
|
||||||
if let Err(error) = _app.deep_link().register_all() {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
eprintln!("Skipping deep link registration during dev: {error}");
|
|
||||||
} else {
|
|
||||||
return Err(error.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
list_screen_share_sources,
|
list_screen_share_sources,
|
||||||
list_audio_outputs,
|
list_audio_outputs,
|
||||||
|
|
|
||||||
12
apps/tauri/src-tauri/vendor/Cargo.toml
vendored
Normal file
12
apps/tauri/src-tauri/vendor/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["tauri-runtime", "tauri-runtime-cef", "tauri-utils"]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
authors = ["Tauri Programme within The Commons Conservancy"]
|
||||||
|
homepage = "https://tauri.app/"
|
||||||
|
repository = "https://github.com/tauri-apps/tauri"
|
||||||
|
categories = ["gui", "web-programming"]
|
||||||
|
license = "Apache-2.0 OR MIT"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.88"
|
||||||
55
apps/tauri/src-tauri/vendor/tauri-runtime-cef/Cargo.toml
vendored
Normal file
55
apps/tauri/src-tauri/vendor/tauri-runtime-cef/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
[package]
|
||||||
|
name = "tauri-runtime-cef"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dioxus-debug-cell = "0.1"
|
||||||
|
tauri-runtime = { version = "2.9.2", path = "../tauri-runtime" }
|
||||||
|
tauri-utils = { version = "2.8.0", path = "../tauri-utils", features = [
|
||||||
|
"html-manipulation",
|
||||||
|
] }
|
||||||
|
html5ever = "0.29"
|
||||||
|
raw-window-handle = "0.6"
|
||||||
|
url = "2"
|
||||||
|
http = "1"
|
||||||
|
cef = { version = "=146.4.1", default-features = false }
|
||||||
|
# Not actually used directly, just locking it.
|
||||||
|
cef-dll-sys = "=146.4.1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
kuchiki = { package = "kuchikiki", version = "0.8.8-speedreader" }
|
||||||
|
sha2 = "0.10"
|
||||||
|
base64 = "0.22"
|
||||||
|
dirs = "6"
|
||||||
|
|
||||||
|
[target."cfg(windows)".dependencies]
|
||||||
|
windows = { version = "0.61", features = [
|
||||||
|
"Win32_Graphics",
|
||||||
|
"Win32_Graphics_Gdi",
|
||||||
|
"Win32_UI_HiDpi",
|
||||||
|
"Win32_UI_Input",
|
||||||
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
|
objc2 = "0.6"
|
||||||
|
objc2-app-kit = { version = "0.3", features = [] }
|
||||||
|
objc2-foundation = { version = "0.3", features = ["NSNotification"] }
|
||||||
|
|
||||||
|
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||||
|
gtk = { version = "0.18", features = ["v3_24"] }
|
||||||
|
x11-dl = "2.21"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["sandbox"]
|
||||||
|
devtools = []
|
||||||
|
macos-private-api = ["tauri-runtime/macos-private-api"]
|
||||||
|
sandbox = ["cef/sandbox"]
|
||||||
4351
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl.rs
vendored
Normal file
4351
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
82
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/cookie.rs
vendored
Normal file
82
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/cookie.rs
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use cef::{rc::*, *};
|
||||||
|
use std::sync::{Arc, Mutex, mpsc::Sender};
|
||||||
|
|
||||||
|
cef::wrap_cookie_visitor! {
|
||||||
|
pub struct CollectUrlCookiesVisitor {
|
||||||
|
pub tx: Sender<tauri_runtime::Result<Vec<tauri_runtime::Cookie<'static>>>>,
|
||||||
|
pub collected: Arc<Mutex<Vec<tauri_runtime::Cookie<'static>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CookieVisitor {
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
cookie: Option<&cef::Cookie>,
|
||||||
|
count: ::std::os::raw::c_int,
|
||||||
|
total: ::std::os::raw::c_int,
|
||||||
|
_delete_cookie: Option<&mut ::std::os::raw::c_int>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
if let Some(c) = cookie {
|
||||||
|
let name = c.name.to_string();
|
||||||
|
let value = c.value.to_string();
|
||||||
|
let domain = c.domain.to_string();
|
||||||
|
let path = c.path.to_string();
|
||||||
|
|
||||||
|
let mut builder = tauri_runtime::Cookie::build((name, value));
|
||||||
|
if !domain.is_empty() { builder = builder.domain(domain); }
|
||||||
|
if !path.is_empty() { builder = builder.path(path); }
|
||||||
|
if c.secure == 1 { builder = builder.secure(true); }
|
||||||
|
if c.httponly == 1 { builder = builder.http_only(true); }
|
||||||
|
let ck = builder.build();
|
||||||
|
|
||||||
|
self.collected.lock().unwrap().push(ck.into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count + 1) >= total {
|
||||||
|
let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone()));
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cef::wrap_cookie_visitor! {
|
||||||
|
pub struct CollectAllCookiesVisitor {
|
||||||
|
pub tx: Sender<tauri_runtime::Result<Vec<tauri_runtime::Cookie<'static>>>>,
|
||||||
|
pub collected: Arc<Mutex<Vec<tauri_runtime::Cookie<'static>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CookieVisitor {
|
||||||
|
fn visit(
|
||||||
|
&self,
|
||||||
|
cookie: Option<&cef::Cookie>,
|
||||||
|
count: ::std::os::raw::c_int,
|
||||||
|
total: ::std::os::raw::c_int,
|
||||||
|
_delete_cookie: Option<&mut ::std::os::raw::c_int>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
if let Some(c) = cookie {
|
||||||
|
let name = c.name.to_string();
|
||||||
|
let value = c.value.to_string();
|
||||||
|
let domain = c.domain.to_string();
|
||||||
|
let path = c.path.to_string();
|
||||||
|
|
||||||
|
let mut builder = tauri_runtime::Cookie::build((name, value));
|
||||||
|
if !domain.is_empty() { builder = builder.domain(domain); }
|
||||||
|
if !path.is_empty() { builder = builder.path(path); }
|
||||||
|
if c.secure == 1 { builder = builder.secure(true); }
|
||||||
|
if c.httponly == 1 { builder = builder.http_only(true); }
|
||||||
|
let ck = builder.build();
|
||||||
|
|
||||||
|
self.collected.lock().unwrap().push(ck.into_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count + 1) >= total {
|
||||||
|
let _ = self.tx.send(Ok(self.collected.lock().unwrap().clone()));
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/drag_window.rs
vendored
Normal file
72
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/drag_window.rs
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod windows {
|
||||||
|
use cef::*;
|
||||||
|
use windows::Win32::Foundation::*;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::*;
|
||||||
|
use windows::core::{PCWSTR, w};
|
||||||
|
|
||||||
|
/// Same as [WNDPROC] but without the Option wrapper.
|
||||||
|
type WindowProc = unsafe extern "system" fn(HWND, u32, WPARAM, LPARAM) -> LRESULT;
|
||||||
|
|
||||||
|
const ORIGINAL_WND_PROP: PCWSTR = w!("TAURI_CEF_ORIGINAL_WND_PROC");
|
||||||
|
|
||||||
|
/// Subclasses the given window to handle draggable regions
|
||||||
|
/// by replacing its window procedure with `root_window_proc`
|
||||||
|
/// and storing the original procedure as a property to be called later.
|
||||||
|
pub fn subclass_window_for_dragging(window: &mut cef::Window) {
|
||||||
|
let hwnd = window.window_handle();
|
||||||
|
let hwnd = HWND(hwnd.0 as _);
|
||||||
|
subclass_window(hwnd, root_window_proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subclasses a window by replacing its window procedure with the given `proc`
|
||||||
|
/// and storing the original procedure as a property for later use.
|
||||||
|
fn subclass_window(hwnd: HWND, proc: WindowProc) {
|
||||||
|
// If already subclassed, return early
|
||||||
|
let orginial_wnd_proc = unsafe { GetPropW(hwnd, ORIGINAL_WND_PROP) };
|
||||||
|
if !orginial_wnd_proc.is_invalid() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset last error
|
||||||
|
unsafe { SetLastError(ERROR_SUCCESS) };
|
||||||
|
|
||||||
|
// Set the new window procedure and get the orginal one
|
||||||
|
let original_wnd_proc = unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, proc as isize) };
|
||||||
|
if original_wnd_proc == 0 && unsafe { GetLastError() } != ERROR_SUCCESS {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Store the original window proc as a property for later use
|
||||||
|
let _ = SetPropW(
|
||||||
|
hwnd,
|
||||||
|
ORIGINAL_WND_PROP,
|
||||||
|
Some(HANDLE(original_wnd_proc as _)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The root window procedure to handle WM_NCLBUTTONDOWN
|
||||||
|
/// by calling DefWindowProcW directly to allow dragging
|
||||||
|
/// and forwarding other messages to the original CEF window procedure.
|
||||||
|
unsafe extern "system" fn root_window_proc(
|
||||||
|
hwnd: HWND,
|
||||||
|
msg: u32,
|
||||||
|
wparam: WPARAM,
|
||||||
|
lparam: LPARAM,
|
||||||
|
) -> LRESULT {
|
||||||
|
if msg == WM_NCLBUTTONDOWN {
|
||||||
|
return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other messages, call the original CEF window procedure
|
||||||
|
let original_wnd_proc = GetPropW(hwnd, ORIGINAL_WND_PROP);
|
||||||
|
let original_wnd_proc = std::mem::transmute::<_, WindowProc>(original_wnd_proc.0);
|
||||||
|
CallWindowProcW(Some(original_wnd_proc), hwnd, msg, wparam, lparam)
|
||||||
|
}
|
||||||
|
}
|
||||||
432
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/request_handler.rs
vendored
Normal file
432
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_impl/request_handler.rs
vendored
Normal file
|
|
@ -0,0 +1,432 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
io::{Cursor, Read},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use cef::{rc::*, *};
|
||||||
|
use dioxus_debug_cell::RefCell;
|
||||||
|
use html5ever::{LocalName, interface::QualName, namespace_url, ns};
|
||||||
|
use http::{
|
||||||
|
HeaderMap, HeaderName, HeaderValue,
|
||||||
|
header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE},
|
||||||
|
};
|
||||||
|
use kuchiki::NodeRef;
|
||||||
|
use tauri_runtime::webview::UriSchemeProtocolHandler;
|
||||||
|
use tauri_utils::{
|
||||||
|
config::{Csp, CspDirectiveSources},
|
||||||
|
html::{parse as parse_html, serialize_node},
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use super::CefInitScript;
|
||||||
|
|
||||||
|
type HttpResponse = Arc<RefCell<Option<http::Response<Cursor<Vec<u8>>>>>>;
|
||||||
|
|
||||||
|
fn csp_inject_initialization_scripts_hashes(
|
||||||
|
existing_csp: String,
|
||||||
|
initialization_scripts: &[CefInitScript],
|
||||||
|
) -> String {
|
||||||
|
if initialization_scripts.is_empty() {
|
||||||
|
return existing_csp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For custom schemes, include ALL script hashes (we inject all scripts into HTML)
|
||||||
|
// This matches the HTML injection behavior in inject_scripts_into_html_body
|
||||||
|
let script_hashes: Vec<String> = initialization_scripts
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.hash.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if script_hashes.is_empty() {
|
||||||
|
return existing_csp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse CSP using tauri-utils
|
||||||
|
let mut csp_map: std::collections::HashMap<String, CspDirectiveSources> =
|
||||||
|
Csp::Policy(existing_csp.to_string()).into();
|
||||||
|
|
||||||
|
// Update or create script-src directive with script hashes
|
||||||
|
let script_src = csp_map
|
||||||
|
.entry("script-src".to_string())
|
||||||
|
.or_insert_with(|| CspDirectiveSources::List(vec!["'self'".to_string()]));
|
||||||
|
|
||||||
|
// Extend with script hashes
|
||||||
|
script_src.extend(script_hashes);
|
||||||
|
|
||||||
|
// Convert back to CSP string
|
||||||
|
Csp::DirectiveMap(csp_map).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to inject initialization scripts into HTML body
|
||||||
|
fn inject_scripts_into_html_body(
|
||||||
|
body: &[u8],
|
||||||
|
initialization_scripts: &[CefInitScript],
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
// Check if body is valid UTF-8 HTML
|
||||||
|
let Ok(body_str) = std::str::from_utf8(body) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse HTML and inject scripts
|
||||||
|
let document = parse_html(body_str.to_string());
|
||||||
|
|
||||||
|
let head = if let Ok(ref head_node) = document.select_first("head") {
|
||||||
|
head_node.as_node().clone()
|
||||||
|
} else {
|
||||||
|
let head_node = NodeRef::new_element(
|
||||||
|
QualName::new(None, ns!(html), LocalName::from("head")),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
document.prepend(head_node.clone());
|
||||||
|
head_node
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inject initialization scripts (for custom schemes, inject all scripts)
|
||||||
|
for init_script in initialization_scripts.iter().rev() {
|
||||||
|
let script_el = NodeRef::new_element(QualName::new(None, ns!(html), "script".into()), None);
|
||||||
|
script_el.append(NodeRef::new_text(init_script.script.script.as_str()));
|
||||||
|
head.prepend(script_el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize the modified HTML
|
||||||
|
Some(serialize_node(&document))
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_resource_request_handler! {
|
||||||
|
pub struct WebResourceRequestHandler {
|
||||||
|
initialization_scripts: Arc<Vec<CefInitScript>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceRequestHandler {
|
||||||
|
|
||||||
|
|
||||||
|
fn on_before_resource_load(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut Browser>,
|
||||||
|
_frame: Option<&mut Frame>,
|
||||||
|
_request: Option<&mut Request>,
|
||||||
|
_callback: Option<&mut Callback>,
|
||||||
|
) -> ReturnValue {
|
||||||
|
sys::cef_return_value_t::RV_CONTINUE.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_request_handler! {
|
||||||
|
pub struct WebRequestHandler {
|
||||||
|
initialization_scripts: Arc<Vec<CefInitScript>>,
|
||||||
|
navigation_handler: Option<Arc<tauri_runtime::webview::NavigationHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RequestHandler {
|
||||||
|
fn on_before_browse(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut Browser>,
|
||||||
|
frame: Option<&mut Frame>,
|
||||||
|
request: Option<&mut Request>,
|
||||||
|
_user_gesture: ::std::os::raw::c_int,
|
||||||
|
_is_redirect: ::std::os::raw::c_int,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
let Some(frame) = frame else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
// we only fire main frame navigation events to match the behavior of the wry runtime
|
||||||
|
if frame.is_main() == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let Some(handler) = &self.navigation_handler else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let Some(request) = request else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let url_str = CefString::from(&request.url()).to_string();
|
||||||
|
let Ok(url) = url::Url::parse(&url_str) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let should_navigate = handler(&url);
|
||||||
|
if should_navigate {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_request_handler(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut Browser>,
|
||||||
|
_frame: Option<&mut Frame>,
|
||||||
|
_request: Option<&mut Request>,
|
||||||
|
_is_navigation: ::std::os::raw::c_int,
|
||||||
|
_is_download: ::std::os::raw::c_int,
|
||||||
|
_request_initiator: Option<&CefString>,
|
||||||
|
_disable_default_handling: Option<&mut ::std::os::raw::c_int>,
|
||||||
|
) -> Option<ResourceRequestHandler> {
|
||||||
|
Some(WebResourceRequestHandler::new(
|
||||||
|
self.initialization_scripts.clone(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_resource_handler! {
|
||||||
|
pub struct WebResourceHandler {
|
||||||
|
webview_label: String,
|
||||||
|
handler: Arc<Box<UriSchemeProtocolHandler>>,
|
||||||
|
initialization_scripts: Arc<Vec<CefInitScript>>,
|
||||||
|
// we clone response to send it to the handler thread
|
||||||
|
response: HttpResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceHandler {
|
||||||
|
fn process_request(
|
||||||
|
&self,
|
||||||
|
request: Option<&mut Request>,
|
||||||
|
callback: Option<&mut Callback>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
let Some(request) = request else { return 0 };
|
||||||
|
let Some(callback) = callback else { return 0 };
|
||||||
|
|
||||||
|
let url = CefString::from(&request.url()).to_string();
|
||||||
|
let url = Url::parse(&url).ok();
|
||||||
|
|
||||||
|
if let Some(url) = url {
|
||||||
|
let callback = ThreadSafe(callback.clone());
|
||||||
|
let response_store = ThreadSafe(self.response.clone());
|
||||||
|
let initialization_scripts = self.initialization_scripts.clone();
|
||||||
|
let responder = Box::new(move |response: http::Response<Cow<'static, [u8]>>| {
|
||||||
|
// Check if this is an HTML response that needs script injection
|
||||||
|
let content_type = response.headers().get(CONTENT_TYPE);
|
||||||
|
let is_html = content_type
|
||||||
|
.and_then(|ct| ct.to_str().ok())
|
||||||
|
.map(|ct| ct.to_lowercase().starts_with("text/html"))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
let (parts, body) = response.into_parts();
|
||||||
|
let body_bytes = body.into_owned();
|
||||||
|
|
||||||
|
let modified_body = if is_html {
|
||||||
|
inject_scripts_into_html_body(&body_bytes, &initialization_scripts)
|
||||||
|
.unwrap_or(body_bytes)
|
||||||
|
} else {
|
||||||
|
body_bytes
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = http::Response::from_parts(parts, Cursor::new(modified_body));
|
||||||
|
|
||||||
|
|
||||||
|
let csp = response
|
||||||
|
.headers_mut()
|
||||||
|
.get_mut(CONTENT_SECURITY_POLICY);
|
||||||
|
|
||||||
|
if let Some(csp) = csp {
|
||||||
|
let csp_string = csp.to_str().unwrap().to_string();
|
||||||
|
let new_csp = csp_inject_initialization_scripts_hashes(
|
||||||
|
csp_string,
|
||||||
|
&initialization_scripts,
|
||||||
|
);
|
||||||
|
*csp = HeaderValue::from_str(&new_csp).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
response_store.into_owned().borrow_mut().replace(response);
|
||||||
|
|
||||||
|
let callback = callback.into_owned();
|
||||||
|
callback.cont();
|
||||||
|
});
|
||||||
|
|
||||||
|
let label = self.webview_label.clone();
|
||||||
|
let handler = self.handler.clone();
|
||||||
|
|
||||||
|
let data = read_request_body(request);
|
||||||
|
let headers = get_request_headers(request);
|
||||||
|
let method_str = CefString::from(&request.method()).to_string();
|
||||||
|
let method = http::Method::from_bytes(method_str.as_bytes())
|
||||||
|
.unwrap_or(http::Method::GET);
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut http_request = http::Request::builder().method(method).uri(url.as_str()).body(data).unwrap();
|
||||||
|
*http_request.headers_mut() = headers;
|
||||||
|
// handler is Arc<Box<UriSchemeProtocol>>, so we need to dereference to call it
|
||||||
|
(**handler)(&label, http_request, responder);
|
||||||
|
});
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(
|
||||||
|
&self,
|
||||||
|
data_out: *mut u8,
|
||||||
|
bytes_to_read: ::std::os::raw::c_int,
|
||||||
|
bytes_read: Option<&mut ::std::os::raw::c_int>,
|
||||||
|
_callback: Option<&mut ResourceReadCallback>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
let Ok(bytes_to_read) = usize::try_from(bytes_to_read) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let data_out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read) };
|
||||||
|
let count = self.response.borrow_mut().as_mut().and_then(|response| response.body_mut().read(data_out).ok()).unwrap_or(0);
|
||||||
|
if let Some(bytes_read) = bytes_read {
|
||||||
|
let Ok(count) = count.try_into() else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
*bytes_read = count;
|
||||||
|
if count > 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_headers(
|
||||||
|
&self,
|
||||||
|
response: Option<&mut Response>,
|
||||||
|
response_length: Option<&mut i64>,
|
||||||
|
redirect_url: Option<&mut CefString>,
|
||||||
|
) {
|
||||||
|
let (Some(response), Some(response_data)) = (response, &*self.response.borrow()) else { return };
|
||||||
|
|
||||||
|
response.set_status(response_data.status().as_u16() as i32);
|
||||||
|
let mut content_type = None;
|
||||||
|
|
||||||
|
// First pass: collect CSP header and set other headers
|
||||||
|
for (name, value) in response_data.headers() {
|
||||||
|
let Ok(value) = value.to_str() else { continue; };
|
||||||
|
|
||||||
|
response.set_header_by_name(Some(&name.as_str().into()), Some(&value.into()), 0);
|
||||||
|
|
||||||
|
if name == CONTENT_TYPE {
|
||||||
|
content_type.replace(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.set_header_by_name(
|
||||||
|
Some(&"Cache-Control".into()),
|
||||||
|
Some(&"no-store".into()),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mime_type = content_type
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.split(';').next())
|
||||||
|
.map(str::trim)
|
||||||
|
.unwrap_or("text/plain");
|
||||||
|
response.set_mime_type(Some(&mime_type.into()));
|
||||||
|
|
||||||
|
if let Some(length) = response_length { *length = -1; }
|
||||||
|
|
||||||
|
if let Some(redirect_url) = redirect_url {
|
||||||
|
let _ = std::mem::take(redirect_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrap_scheme_handler_factory! {
|
||||||
|
pub struct UriSchemeHandlerFactory {
|
||||||
|
registry: super::SchemeHandlerRegistry,
|
||||||
|
scheme: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SchemeHandlerFactory {
|
||||||
|
fn create(
|
||||||
|
&self,
|
||||||
|
browser: Option<&mut Browser>,
|
||||||
|
_frame: Option<&mut Frame>,
|
||||||
|
_scheme_name: Option<&CefString>,
|
||||||
|
_request: Option<&mut Request>,
|
||||||
|
) -> Option<ResourceHandler> {
|
||||||
|
let browser = browser?;
|
||||||
|
let id = browser.identifier();
|
||||||
|
|
||||||
|
// get handler from our regsitry based on browser ID and scheme
|
||||||
|
let (webview_label, handler, initialization_scripts) = self
|
||||||
|
.registry
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.get(&(id, self.scheme.clone()))
|
||||||
|
.cloned()?;
|
||||||
|
|
||||||
|
Some(WebResourceHandler::new(webview_label, handler, initialization_scripts, Arc::new(RefCell::new(None))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ThreadSafe<T>(T);
|
||||||
|
|
||||||
|
impl<T> ThreadSafe<T> {
|
||||||
|
fn into_owned(self) -> T {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T> Send for ThreadSafe<T> {}
|
||||||
|
unsafe impl<T> Sync for ThreadSafe<T> {}
|
||||||
|
|
||||||
|
fn read_request_body(request: &mut Request) -> Vec<u8> {
|
||||||
|
let mut body = Vec::new();
|
||||||
|
|
||||||
|
if let Some(post_data) = request.post_data() {
|
||||||
|
let mut elements = vec![None; post_data.element_count()];
|
||||||
|
post_data.elements(Some(&mut elements));
|
||||||
|
for element in elements.into_iter().flatten() {
|
||||||
|
match element.get_type().as_ref() {
|
||||||
|
sys::cef_postdataelement_type_t::PDE_TYPE_BYTES => {
|
||||||
|
let size = element.bytes_count();
|
||||||
|
if size > 0 {
|
||||||
|
let mut buf = vec![0u8; size];
|
||||||
|
// Copy bytes into our buffer
|
||||||
|
let copied = element.bytes(size, buf.as_mut_ptr());
|
||||||
|
// Safety: CEF promises it wrote `copied` bytes into buf
|
||||||
|
unsafe {
|
||||||
|
buf.set_len(copied);
|
||||||
|
}
|
||||||
|
body.extend(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys::cef_postdataelement_type_t::PDE_TYPE_FILE => {
|
||||||
|
// Read file from disk
|
||||||
|
let file_path = CefString::from(&element.file()).to_string();
|
||||||
|
if let Ok(mut file) = std::fs::File::open(&file_path) {
|
||||||
|
use std::io::Read;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
if file.read_to_end(&mut buf).is_ok() {
|
||||||
|
body.extend(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_request_headers(request: &mut Request) -> HeaderMap {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
|
let mut map = CefStringMultimap::new();
|
||||||
|
|
||||||
|
request.header_map(Some(&mut map));
|
||||||
|
|
||||||
|
// Iterate through all entries
|
||||||
|
for (name, value) in map {
|
||||||
|
for v in value {
|
||||||
|
headers.append(
|
||||||
|
HeaderName::from_bytes(name.as_bytes()).unwrap(),
|
||||||
|
HeaderValue::from_str(&v).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headers
|
||||||
|
}
|
||||||
110
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview.rs
vendored
Normal file
110
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview.rs
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
use cef::*;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod macos;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod linux;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum CefWebview {
|
||||||
|
BrowserView(cef::BrowserView),
|
||||||
|
Browser(cef::Browser),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CefWebview {
|
||||||
|
pub fn is_browser(&self) -> bool {
|
||||||
|
matches!(self, CefWebview::Browser(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn browser(&self) -> Option<cef::Browser> {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view.browser(),
|
||||||
|
CefWebview::Browser(browser) => Some(browser.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn browser_id(&self) -> i32 {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view.browser().map_or(-1, |b| b.identifier()),
|
||||||
|
CefWebview::Browser(browser) => browser.identifier(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_background_color(&self, color: Option<u32>) {
|
||||||
|
if let CefWebview::BrowserView(view) = self {
|
||||||
|
let window = view.window();
|
||||||
|
let color = color.or_else(|| {
|
||||||
|
window.map(|w| w.theme_color(ColorId::COLOR_PRIMARY_BACKGROUND.get_raw() as _))
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(color) = color {
|
||||||
|
view.set_background_color(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bounds(&self) -> cef::Rect {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view.bounds(),
|
||||||
|
CefWebview::Browser(browser) => browser.bounds(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_bounds(&self, rect: Option<&cef::Rect>) {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view.set_bounds(rect),
|
||||||
|
CefWebview::Browser(browser) => browser.set_bounds(rect),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale_factor(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view
|
||||||
|
.window()
|
||||||
|
.and_then(|w| w.display())
|
||||||
|
.map_or(1.0, |d| d.device_scale_factor() as f64),
|
||||||
|
CefWebview::Browser(browser) => browser.scale_factor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_visible(&self, visible: i32) {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(view) => view.set_visible(visible),
|
||||||
|
CefWebview::Browser(browser) => browser.set_visible(visible),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(_) => {}
|
||||||
|
CefWebview::Browser(browser) => browser.close(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_parent(&self, parent: &cef::Window) {
|
||||||
|
match self {
|
||||||
|
CefWebview::BrowserView(_) => {}
|
||||||
|
CefWebview::Browser(browser) => browser.set_parent(parent),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait CefBrowserExt {
|
||||||
|
fn bounds(&self) -> cef::Rect;
|
||||||
|
fn set_bounds(&self, rect: Option<&cef::Rect>);
|
||||||
|
fn scale_factor(&self) -> f64;
|
||||||
|
fn set_visible(&self, visible: i32);
|
||||||
|
fn close(&self);
|
||||||
|
fn set_parent(&self, parent: &cef::Window);
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn nsview(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSView>>;
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn hwnd(&self) -> Option<::windows::Win32::Foundation::HWND>;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn xid(&self) -> Option<u64>;
|
||||||
|
}
|
||||||
208
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/linux.rs
vendored
Normal file
208
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/linux.rs
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
use cef::*;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use x11_dl::xlib;
|
||||||
|
|
||||||
|
use crate::cef_webview::CefBrowserExt;
|
||||||
|
|
||||||
|
static X11: LazyLock<Option<xlib::Xlib>> = LazyLock::new(|| xlib::Xlib::open().ok());
|
||||||
|
|
||||||
|
impl CefBrowserExt for cef::Browser {
|
||||||
|
fn xid(&self) -> Option<u64> {
|
||||||
|
let host = self.host()?;
|
||||||
|
let xid = host.window_handle();
|
||||||
|
Some(xid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> cef::Rect {
|
||||||
|
let Some(xid) = self.xid() else {
|
||||||
|
return cef::Rect::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xlib) = X11.as_ref() else {
|
||||||
|
return cef::Rect::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let display = (xlib.XOpenDisplay)(std::ptr::null());
|
||||||
|
if display.is_null() {
|
||||||
|
return cef::Rect::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut root: xlib::Window = 0;
|
||||||
|
let mut x: i32 = 0;
|
||||||
|
let mut y: i32 = 0;
|
||||||
|
let mut width: u32 = 0;
|
||||||
|
let mut height: u32 = 0;
|
||||||
|
let mut border_width: u32 = 0;
|
||||||
|
let mut depth: u32 = 0;
|
||||||
|
|
||||||
|
let status = (xlib.XGetGeometry)(
|
||||||
|
display,
|
||||||
|
xid as xlib::Window,
|
||||||
|
&mut root,
|
||||||
|
&mut x,
|
||||||
|
&mut y,
|
||||||
|
&mut width,
|
||||||
|
&mut height,
|
||||||
|
&mut border_width,
|
||||||
|
&mut depth,
|
||||||
|
);
|
||||||
|
|
||||||
|
(xlib.XCloseDisplay)(display);
|
||||||
|
|
||||||
|
if status == 0 {
|
||||||
|
return cef::Rect::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
// XGetGeometry returns position relative to parent, which is what we need
|
||||||
|
cef::Rect {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: width as i32,
|
||||||
|
height: height as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bounds(&self, rect: Option<&cef::Rect>) {
|
||||||
|
let Some(rect) = rect else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xid) = self.xid() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xlib) = X11.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let display = (xlib.XOpenDisplay)(std::ptr::null());
|
||||||
|
if display.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(xlib.XMoveResizeWindow)(
|
||||||
|
display,
|
||||||
|
xid as xlib::Window,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width as u32,
|
||||||
|
rect.height as u32,
|
||||||
|
);
|
||||||
|
// Ensure window is mapped and raised after setting bounds
|
||||||
|
(xlib.XMapRaised)(display, xid as xlib::Window);
|
||||||
|
(xlib.XFlush)(display);
|
||||||
|
(xlib.XCloseDisplay)(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self) -> f64 {
|
||||||
|
// Get scale factor from primary display
|
||||||
|
// CEF on Linux doesn't provide direct access to the window's display,
|
||||||
|
// so we use the primary display as a reasonable default
|
||||||
|
cef::display_get_primary()
|
||||||
|
.map(|d| d.device_scale_factor() as f64)
|
||||||
|
.unwrap_or(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_visible(&self, visible: i32) {
|
||||||
|
let Some(xid) = self.xid() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xlib) = X11.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let display = (xlib.XOpenDisplay)(std::ptr::null());
|
||||||
|
if display.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if visible != 0 {
|
||||||
|
(xlib.XMapWindow)(display, xid as xlib::Window);
|
||||||
|
} else {
|
||||||
|
(xlib.XUnmapWindow)(display, xid as xlib::Window);
|
||||||
|
}
|
||||||
|
(xlib.XFlush)(display);
|
||||||
|
(xlib.XCloseDisplay)(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self) {
|
||||||
|
let Some(xid) = self.xid() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(xlib) = X11.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let display = (xlib.XOpenDisplay)(std::ptr::null());
|
||||||
|
if display.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(xlib.XDestroyWindow)(display, xid as xlib::Window);
|
||||||
|
(xlib.XFlush)(display);
|
||||||
|
(xlib.XCloseDisplay)(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_parent(&self, parent: &cef::Window) {
|
||||||
|
let Some(xid) = self.xid() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_xid = parent.window_handle();
|
||||||
|
if parent_xid == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(xlib) = X11.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let display = (xlib.XOpenDisplay)(std::ptr::null());
|
||||||
|
if display.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if window exists before reparenting
|
||||||
|
let mut root: xlib::Window = 0;
|
||||||
|
let mut parent_window: xlib::Window = 0;
|
||||||
|
let mut children: *mut xlib::Window = std::ptr::null_mut();
|
||||||
|
let mut nchildren: u32 = 0;
|
||||||
|
let status = (xlib.XQueryTree)(
|
||||||
|
display,
|
||||||
|
xid as xlib::Window,
|
||||||
|
&mut root,
|
||||||
|
&mut parent_window,
|
||||||
|
&mut children,
|
||||||
|
&mut nchildren,
|
||||||
|
);
|
||||||
|
|
||||||
|
if status != 0 && !children.is_null() {
|
||||||
|
(xlib.XFree)(children as *mut std::ffi::c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
(xlib.XReparentWindow)(
|
||||||
|
display,
|
||||||
|
xid as xlib::Window,
|
||||||
|
parent_xid as xlib::Window,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure window is mapped and raised after reparenting
|
||||||
|
(xlib.XMapRaised)(display, xid as xlib::Window);
|
||||||
|
(xlib.XFlush)(display);
|
||||||
|
(xlib.XCloseDisplay)(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
99
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/macos.rs
vendored
Normal file
99
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/macos.rs
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
use crate::cef_webview::CefBrowserExt;
|
||||||
|
use cef::*;
|
||||||
|
use objc2::rc::Retained;
|
||||||
|
use objc2_app_kit::NSView;
|
||||||
|
use objc2_foundation::{NSPoint, NSRect, NSSize};
|
||||||
|
|
||||||
|
impl CefBrowserExt for cef::Browser {
|
||||||
|
fn nsview(&self) -> Option<objc2::rc::Retained<objc2_app_kit::NSView>> {
|
||||||
|
let host = self.host()?;
|
||||||
|
let nsview = host.window_handle() as *mut NSView;
|
||||||
|
unsafe { Retained::<NSView>::retain(nsview) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> cef::Rect {
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return cef::Rect::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent = unsafe { nsview.superview().unwrap() };
|
||||||
|
let parent_frame = parent.frame();
|
||||||
|
let webview_frame = nsview.frame();
|
||||||
|
|
||||||
|
cef::Rect {
|
||||||
|
x: webview_frame.origin.x as i32,
|
||||||
|
y: (parent_frame.size.height - webview_frame.origin.y - webview_frame.size.height) as i32,
|
||||||
|
width: webview_frame.size.width as i32,
|
||||||
|
height: webview_frame.size.height as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bounds(&self, rect: Option<&cef::Rect>) {
|
||||||
|
let Some(rect) = rect else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent = unsafe { nsview.superview().unwrap() };
|
||||||
|
let parent_frame = parent.frame();
|
||||||
|
|
||||||
|
let origin = NSPoint {
|
||||||
|
x: rect.x as f64,
|
||||||
|
y: (parent_frame.size.height - (rect.y as f64 + rect.height as f64)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = NSSize {
|
||||||
|
width: rect.width as f64,
|
||||||
|
height: rect.height as f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { nsview.setFrame(NSRect { origin, size }) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self) -> f64 {
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let screen = nsview.window().and_then(|w| w.screen());
|
||||||
|
screen.map(|s| s.backingScaleFactor()).unwrap_or(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_visible(&self, visible: i32) {
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if visible != 0 {
|
||||||
|
nsview.setHidden(false);
|
||||||
|
} else {
|
||||||
|
nsview.setHidden(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self) {
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { nsview.removeFromSuperview() };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_parent(&self, parent: &cef::Window) {
|
||||||
|
crate::cef_impl::ensure_valid_content_view(parent.window_handle());
|
||||||
|
|
||||||
|
let Some(nsview) = self.nsview() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_nsview = parent.window_handle();
|
||||||
|
let Some(parent_nsview) = (unsafe { Retained::<NSView>::retain(parent_nsview as _) }) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe { parent_nsview.addSubview(&nsview) };
|
||||||
|
}
|
||||||
|
}
|
||||||
210
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/windows.rs
vendored
Normal file
210
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/cef_webview/windows.rs
vendored
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
use cef::*;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use crate::cef_webview::CefBrowserExt;
|
||||||
|
use windows::{
|
||||||
|
Win32::{
|
||||||
|
Foundation::*,
|
||||||
|
Graphics::Gdi::*,
|
||||||
|
System::LibraryLoader::*,
|
||||||
|
UI::{HiDpi::*, WindowsAndMessaging::*},
|
||||||
|
},
|
||||||
|
core::{HRESULT, HSTRING, PCSTR},
|
||||||
|
};
|
||||||
|
|
||||||
|
impl CefBrowserExt for cef::Browser {
|
||||||
|
fn hwnd(&self) -> Option<HWND> {
|
||||||
|
let host = self.host()?;
|
||||||
|
let hwnd = host.window_handle();
|
||||||
|
Some(HWND(hwnd.0 as _))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> cef::Rect {
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return cef::Rect::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rect = RECT::default();
|
||||||
|
let _ = unsafe { GetClientRect(hwnd, &mut rect) };
|
||||||
|
|
||||||
|
let position_point = &mut [POINT {
|
||||||
|
x: rect.left,
|
||||||
|
y: rect.top,
|
||||||
|
}];
|
||||||
|
unsafe { MapWindowPoints(Some(hwnd), GetParent(hwnd).ok(), position_point) };
|
||||||
|
|
||||||
|
cef::Rect {
|
||||||
|
x: position_point[0].x,
|
||||||
|
y: position_point[0].y,
|
||||||
|
width: (rect.right - rect.left) as i32,
|
||||||
|
height: (rect.bottom - rect.top) as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bounds(&self, rect: Option<&cef::Rect>) {
|
||||||
|
let Some(rect) = rect else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = unsafe {
|
||||||
|
SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
None,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOZORDER,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_factor(&self) -> f64 {
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return 1.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let dpi = unsafe { hwnd_dpi(hwnd) };
|
||||||
|
dpi_to_scale_factor(dpi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_visible(&self, visible: i32) {
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if visible != 0 {
|
||||||
|
let _ = unsafe { ShowWindow(hwnd, SW_SHOW) };
|
||||||
|
unsafe { ensure_render_target(hwnd) };
|
||||||
|
} else {
|
||||||
|
let _ = unsafe { ShowWindow(hwnd, SW_HIDE) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&self) {
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = unsafe { DestroyWindow(hwnd) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_parent(&self, parent: &cef::Window) {
|
||||||
|
let Some(hwnd) = self.hwnd() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent_hwnd = HWND(parent.window_handle().0 as _);
|
||||||
|
let _ = unsafe { SetParent(hwnd, Some(parent_hwnd)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle visibility on Chrome_WidgetWin_1 children to force CEF to rebind
|
||||||
|
/// the compositor surface and emit a fresh paint after the child webview has
|
||||||
|
/// been hidden.
|
||||||
|
///
|
||||||
|
/// Required when the child has been hidden, particularly when paired with CDP
|
||||||
|
/// `Page.setWebLifecycleState: frozen`, which pauses the renderer's compositor.
|
||||||
|
/// The Chrome_RenderWidgetHostHWND host window survives the freeze, but its
|
||||||
|
/// compositor surface is stale and won't repaint on its own when the parent is
|
||||||
|
/// shown again — leaving the widget visible with blank content. The hide+show
|
||||||
|
/// dance on Chrome_WidgetWin_1 forces Chromium to rebind the surface and
|
||||||
|
/// schedule a paint.
|
||||||
|
unsafe fn ensure_render_target(hwnd: HWND) {
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
|
||||||
|
const CHROME_WIDGET: PCWSTR = windows::core::w!("Chrome_WidgetWin_1");
|
||||||
|
|
||||||
|
let mut child = unsafe { FindWindowExW(Some(hwnd), None, CHROME_WIDGET, PCWSTR::null()) };
|
||||||
|
while let Ok(child_hwnd) = child {
|
||||||
|
let _ = unsafe { ShowWindow(child_hwnd, SW_HIDE) };
|
||||||
|
let _ = unsafe { ShowWindow(child_hwnd, SW_SHOW) };
|
||||||
|
child = unsafe { FindWindowExW(Some(hwnd), Some(child_hwnd), CHROME_WIDGET, PCWSTR::null()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_function_impl(library: &str, function: &str) -> FARPROC {
|
||||||
|
let library = HSTRING::from(library);
|
||||||
|
assert_eq!(function.chars().last(), Some('\0'));
|
||||||
|
|
||||||
|
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||||
|
let module = unsafe { LoadLibraryW(&library) }.unwrap_or_default();
|
||||||
|
if module.is_invalid() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! get_function {
|
||||||
|
($lib:expr, $func:ident) => {
|
||||||
|
get_function_impl($lib, concat!(stringify!($func), '\0'))
|
||||||
|
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
|
||||||
|
pub type GetDpiForMonitor = unsafe extern "system" fn(
|
||||||
|
hmonitor: HMONITOR,
|
||||||
|
dpi_type: MONITOR_DPI_TYPE,
|
||||||
|
dpi_x: *mut u32,
|
||||||
|
dpi_y: *mut u32,
|
||||||
|
) -> HRESULT;
|
||||||
|
|
||||||
|
static GET_DPI_FOR_WINDOW: LazyLock<Option<GetDpiForWindow>> =
|
||||||
|
LazyLock::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||||
|
static GET_DPI_FOR_MONITOR: LazyLock<Option<GetDpiForMonitor>> =
|
||||||
|
LazyLock::new(|| get_function!("shcore.dll", GetDpiForMonitor));
|
||||||
|
|
||||||
|
pub const BASE_DPI: u32 = 96;
|
||||||
|
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||||
|
dpi as f64 / BASE_DPI as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||||
|
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||||
|
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||||
|
match GetDpiForWindow(hwnd) {
|
||||||
|
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
dpi => dpi as u32,
|
||||||
|
}
|
||||||
|
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||||
|
// We are on Windows 8.1 or later.
|
||||||
|
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||||
|
if monitor.is_invalid() {
|
||||||
|
return BASE_DPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dpi_x = 0;
|
||||||
|
let mut dpi_y = 0;
|
||||||
|
#[allow(clippy::unnecessary_cast)]
|
||||||
|
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||||
|
dpi_x as u32
|
||||||
|
} else {
|
||||||
|
BASE_DPI
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hdc = GetDC(Some(hwnd));
|
||||||
|
if hdc.is_invalid() {
|
||||||
|
return BASE_DPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are on Vista or later.
|
||||||
|
if IsProcessDPIAware().as_bool() {
|
||||||
|
// If the process is DPI aware, then scaling must be handled by the application using
|
||||||
|
// this DPI value.
|
||||||
|
GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32
|
||||||
|
} else {
|
||||||
|
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||||
|
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||||
|
// application and the WM.
|
||||||
|
BASE_DPI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2541
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/lib.rs
vendored
Normal file
2541
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/lib.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
42
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/utils.rs
vendored
Normal file
42
apps/tauri/src-tauri/vendor/tauri-runtime-cef/src/utils.rs
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub mod windows {
|
||||||
|
use tauri_runtime::dpi::PhysicalSize;
|
||||||
|
use windows::Win32::Foundation::*;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::*;
|
||||||
|
|
||||||
|
pub fn inner_size(hwnd: cef::sys::HWND) -> PhysicalSize<u32> {
|
||||||
|
let hwnd = HWND(hwnd.0 as _);
|
||||||
|
let mut rect = RECT::default();
|
||||||
|
let _ = unsafe { GetClientRect(hwnd, &mut rect) };
|
||||||
|
|
||||||
|
PhysicalSize::new(
|
||||||
|
(rect.right - rect.left) as u32,
|
||||||
|
(rect.bottom - rect.top) as u32,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adjusts the given size to account for window borders, so that the resulting inner size matches the requested size.
|
||||||
|
///
|
||||||
|
/// Expects and returns a size in physical pixels.
|
||||||
|
pub fn adjust_size(hwnd: cef::sys::HWND, size: cef::Size) -> cef::Size {
|
||||||
|
let hwnd = HWND(hwnd.0 as _);
|
||||||
|
|
||||||
|
let mut client_rect = RECT::default();
|
||||||
|
let _ = unsafe { GetClientRect(hwnd, &mut client_rect) };
|
||||||
|
let client_width = client_rect.right - client_rect.left;
|
||||||
|
let client_height = client_rect.bottom - client_rect.top;
|
||||||
|
|
||||||
|
let mut window_rect = RECT::default();
|
||||||
|
let _ = unsafe { GetWindowRect(hwnd, &mut window_rect) };
|
||||||
|
let window_width = window_rect.right - window_rect.left;
|
||||||
|
let window_height = window_rect.bottom - window_rect.top;
|
||||||
|
|
||||||
|
let width_diff = window_width - client_width;
|
||||||
|
let height_diff = window_height - client_height;
|
||||||
|
|
||||||
|
cef::Size {
|
||||||
|
width: size.width + width_diff,
|
||||||
|
height: size.height + height_diff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1083
apps/tauri/src-tauri/vendor/tauri-runtime/CHANGELOG.md
vendored
Normal file
1083
apps/tauri/src-tauri/vendor/tauri-runtime/CHANGELOG.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
62
apps/tauri/src-tauri/vendor/tauri-runtime/Cargo.toml
vendored
Normal file
62
apps/tauri/src-tauri/vendor/tauri-runtime/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
[package]
|
||||||
|
name = "tauri-runtime"
|
||||||
|
version = "2.11.0"
|
||||||
|
description = "Runtime for Tauri applications"
|
||||||
|
exclude = ["CHANGELOG.md", "/target"]
|
||||||
|
readme = "README.md"
|
||||||
|
authors.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
default-target = "x86_64-unknown-linux-gnu"
|
||||||
|
targets = [
|
||||||
|
"x86_64-pc-windows-msvc",
|
||||||
|
"x86_64-unknown-linux-gnu",
|
||||||
|
"x86_64-apple-darwin",
|
||||||
|
"x86_64-linux-android",
|
||||||
|
"x86_64-apple-ios",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
thiserror = "2"
|
||||||
|
tauri-utils = { version = "2.9.0", path = "../tauri-utils" }
|
||||||
|
http = "1"
|
||||||
|
raw-window-handle = "0.6"
|
||||||
|
url = { version = "2" }
|
||||||
|
dpi = { version = "0.1", features = ["serde"] }
|
||||||
|
# WARNING: cookie::Cookie is re-exported so bumping this is a breaking change, documented to be done as a minor bump
|
||||||
|
cookie = "0.18"
|
||||||
|
|
||||||
|
[target."cfg(windows)".dependencies.windows]
|
||||||
|
version = "0.61"
|
||||||
|
features = ["Win32_Foundation", "Win32_System_WinRT"]
|
||||||
|
|
||||||
|
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||||
|
gtk = { version = "0.18", features = ["v3_24"] }
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"android\")".dependencies]
|
||||||
|
jni = "0.21"
|
||||||
|
|
||||||
|
[target.'cfg(all(target_vendor = "apple", not(target_os = "macos")))'.dependencies]
|
||||||
|
objc2 = "0.6"
|
||||||
|
objc2-ui-kit = { version = "0.3.0", default-features = false, features = [
|
||||||
|
"UIView",
|
||||||
|
"UIResponder",
|
||||||
|
"UIScene",
|
||||||
|
"UISceneOptions",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
|
url = "2"
|
||||||
|
objc2 = "0.6"
|
||||||
|
[features]
|
||||||
|
devtools = []
|
||||||
|
macos-private-api = []
|
||||||
44
apps/tauri/src-tauri/vendor/tauri-runtime/README.md
vendored
Normal file
44
apps/tauri/src-tauri/vendor/tauri-runtime/README.md
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# tauri-runtime
|
||||||
|
|
||||||
|
<img align="right" src="https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png" height="128" width="128">
|
||||||
|
|
||||||
|
[](https://github.com/tauri-apps/tauri)
|
||||||
|
[](https://discord.gg/SpmNs4S)
|
||||||
|
|
||||||
|
[](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)
|
||||||
|
[](https://tauri.app)
|
||||||
|
|
||||||
|
[](https://good-labs.github.io/greater-good-affirmation)
|
||||||
|
[](https://opencollective.com/tauri)
|
||||||
|
|
||||||
|
| Component | Version |
|
||||||
|
| ------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| tauri-runtime | [](https://crates.io/crates/tauri-runtime) |
|
||||||
|
|
||||||
|
## About Tauri
|
||||||
|
|
||||||
|
Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.
|
||||||
|
|
||||||
|
Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.
|
||||||
|
|
||||||
|
## This module
|
||||||
|
|
||||||
|
This is the glue layer between tauri itself and lower level webview libraries.
|
||||||
|
None of the exposed API of this crate is stable, and it may break semver
|
||||||
|
compatibility in the future. The major version only signifies the intended Tauri version.
|
||||||
|
|
||||||
|
To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.
|
||||||
|
|
||||||
|
## Semver
|
||||||
|
|
||||||
|
**tauri** is following [Semantic Versioning 2.0](https://semver.org/).
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
Code: (c) 2021 - The Tauri Programme within The Commons Conservancy.
|
||||||
|
|
||||||
|
MIT or MIT/Apache 2.0 where applicable.
|
||||||
|
|
||||||
|
Logo: CC-BY-NC-ND
|
||||||
|
|
||||||
|
- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)
|
||||||
19
apps/tauri/src-tauri/vendor/tauri-runtime/build.rs
vendored
Normal file
19
apps/tauri/src-tauri/vendor/tauri-runtime/build.rs
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// creates a cfg alias if `has_feature` is true.
|
||||||
|
// `alias` must be a snake case string.
|
||||||
|
fn alias(alias: &str, has_feature: bool) {
|
||||||
|
println!("cargo:rustc-check-cfg=cfg({alias})");
|
||||||
|
if has_feature {
|
||||||
|
println!("cargo:rustc-cfg={alias}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||||
|
let mobile = target_os == "ios" || target_os == "android";
|
||||||
|
alias("desktop", !mobile);
|
||||||
|
alias("mobile", mobile);
|
||||||
|
}
|
||||||
60
apps/tauri/src-tauri/vendor/tauri-runtime/src/dpi.rs
vendored
Normal file
60
apps/tauri/src-tauri/vendor/tauri-runtime/src/dpi.rs
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pub use dpi::*;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// A rectangular region.
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
|
pub struct Rect {
|
||||||
|
/// Rect position.
|
||||||
|
pub position: dpi::Position,
|
||||||
|
/// Rect size.
|
||||||
|
pub size: dpi::Size,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Rect {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
position: Position::Logical((0, 0).into()),
|
||||||
|
size: Size::Logical((0, 0).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rectangular region in physical pixels.
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
|
pub struct PhysicalRect<P: dpi::Pixel, S: dpi::Pixel> {
|
||||||
|
/// Rect position.
|
||||||
|
pub position: dpi::PhysicalPosition<P>,
|
||||||
|
/// Rect size.
|
||||||
|
pub size: dpi::PhysicalSize<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: dpi::Pixel, S: dpi::Pixel> Default for PhysicalRect<P, S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
position: (0, 0).into(),
|
||||||
|
size: (0, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rectangular region in logical pixels.
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
|
pub struct LogicalRect<P: dpi::Pixel, S: dpi::Pixel> {
|
||||||
|
/// Rect position.
|
||||||
|
pub position: dpi::LogicalPosition<P>,
|
||||||
|
/// Rect size.
|
||||||
|
pub size: dpi::LogicalSize<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: dpi::Pixel, S: dpi::Pixel> Default for LogicalRect<P, S> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
position: (0, 0).into(),
|
||||||
|
size: (0, 0).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1048
apps/tauri/src-tauri/vendor/tauri-runtime/src/lib.rs
vendored
Normal file
1048
apps/tauri/src-tauri/vendor/tauri-runtime/src/lib.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
21
apps/tauri/src-tauri/vendor/tauri-runtime/src/monitor.rs
vendored
Normal file
21
apps/tauri/src-tauri/vendor/tauri-runtime/src/monitor.rs
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use crate::dpi::{PhysicalPosition, PhysicalRect, PhysicalSize};
|
||||||
|
|
||||||
|
/// Monitor descriptor.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Monitor {
|
||||||
|
/// A human-readable name of the monitor.
|
||||||
|
/// `None` if the monitor doesn't exist anymore.
|
||||||
|
pub name: Option<String>,
|
||||||
|
/// The monitor's resolution.
|
||||||
|
pub size: PhysicalSize<u32>,
|
||||||
|
/// The top-left corner position of the monitor relative to the larger full screen area.
|
||||||
|
pub position: PhysicalPosition<i32>,
|
||||||
|
/// The monitor's work_area.
|
||||||
|
pub work_area: PhysicalRect<i32, u32>,
|
||||||
|
/// Returns the scale factor that can be used to map logical pixels to physical pixels, and vice versa.
|
||||||
|
pub scale_factor: f64,
|
||||||
|
}
|
||||||
820
apps/tauri/src-tauri/vendor/tauri-runtime/src/webview.rs
vendored
Normal file
820
apps/tauri/src-tauri/vendor/tauri-runtime/src/webview.rs
vendored
Normal file
|
|
@ -0,0 +1,820 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! A layer between raw [`Runtime`] webviews and Tauri.
|
||||||
|
//!
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
use crate::window::WindowId;
|
||||||
|
use crate::{Rect, Runtime, UserEvent, window::is_label_valid};
|
||||||
|
|
||||||
|
use http::Request;
|
||||||
|
use tauri_utils::config::{
|
||||||
|
BackgroundThrottlingPolicy, Color, ScrollBarStyle as ConfigScrollBarStyle, WebviewUrl,
|
||||||
|
WindowConfig, WindowEffectsConfig,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::HashMap,
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type UriSchemeProtocolHandler = dyn Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static;
|
||||||
|
|
||||||
|
pub type WebResourceRequestHandler =
|
||||||
|
dyn Fn(http::Request<Vec<u8>>, &mut http::Response<Cow<'static, [u8]>>) + Send + Sync;
|
||||||
|
|
||||||
|
pub type NavigationHandler = dyn Fn(&Url) -> bool + Send;
|
||||||
|
|
||||||
|
pub type NewWindowHandler<T, R> =
|
||||||
|
dyn Fn(Url, NewWindowFeatures<T, R>) -> NewWindowResponse + Send + Sync;
|
||||||
|
|
||||||
|
pub type OnPageLoadHandler = dyn Fn(Url, PageLoadEvent) + Send;
|
||||||
|
|
||||||
|
pub type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static;
|
||||||
|
|
||||||
|
pub type AddressChangedHandler = dyn Fn(&Url) + Send + Sync + 'static;
|
||||||
|
|
||||||
|
pub type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
type OnWebContentProcessTerminateHandler = dyn Fn() + Send;
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
type InputAccessoryViewBuilderFn = dyn Fn(&objc2_ui_kit::UIView) -> Option<objc2::rc::Retained<objc2_ui_kit::UIView>>
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static;
|
||||||
|
|
||||||
|
/// Download event.
|
||||||
|
pub enum DownloadEvent<'a> {
|
||||||
|
/// Download requested.
|
||||||
|
Requested {
|
||||||
|
/// The url being downloaded.
|
||||||
|
url: Url,
|
||||||
|
/// Represents where the file will be downloaded to.
|
||||||
|
/// Can be used to set the download location by assigning a new path to it.
|
||||||
|
/// The assigned path _must_ be absolute.
|
||||||
|
destination: &'a mut PathBuf,
|
||||||
|
},
|
||||||
|
/// Download finished.
|
||||||
|
Finished {
|
||||||
|
/// The URL of the original download request.
|
||||||
|
url: Url,
|
||||||
|
/// Potentially representing the filesystem path the file was downloaded to.
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
/// Indicates if the download succeeded or not.
|
||||||
|
success: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub struct CreationContext<'a, 'b> {
|
||||||
|
pub env: &'a mut jni::JNIEnv<'b>,
|
||||||
|
pub activity: &'a jni::objects::JObject<'b>,
|
||||||
|
pub webview: &'a jni::objects::JObject<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kind of event for the page load handler.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PageLoadEvent {
|
||||||
|
/// Page started to load.
|
||||||
|
Started,
|
||||||
|
/// Page finished loading.
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Window features of a window requested to open.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NewWindowFeatures<T: UserEvent, R: Runtime<T>> {
|
||||||
|
pub(crate) size: Option<crate::dpi::LogicalSize<f64>>,
|
||||||
|
pub(crate) position: Option<crate::dpi::LogicalPosition<f64>>,
|
||||||
|
pub(crate) opener: R::WindowOpener,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> NewWindowFeatures<T, R> {
|
||||||
|
pub fn new(
|
||||||
|
size: Option<crate::dpi::LogicalSize<f64>>,
|
||||||
|
position: Option<crate::dpi::LogicalPosition<f64>>,
|
||||||
|
opener: R::WindowOpener,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
size,
|
||||||
|
position,
|
||||||
|
opener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the size of the content area
|
||||||
|
/// as defined by the user's operating system where the new window will be generated.
|
||||||
|
pub fn size(&self) -> Option<crate::dpi::LogicalSize<f64>> {
|
||||||
|
self.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the position of the window relative to the work area
|
||||||
|
/// as defined by the user's operating system where the new window will be generated.
|
||||||
|
pub fn position(&self) -> Option<crate::dpi::LogicalPosition<f64>> {
|
||||||
|
self.position
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns information about the webview that initiated a new window request.
|
||||||
|
pub fn opener(&self) -> &R::WindowOpener {
|
||||||
|
&self.opener
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns information about the webview that initiated a new window request.
|
||||||
|
pub fn into_opener(self) -> R::WindowOpener {
|
||||||
|
self.opener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response for the new window request handler.
|
||||||
|
pub enum NewWindowResponse {
|
||||||
|
/// Allow the window to be opened with the default implementation.
|
||||||
|
Allow,
|
||||||
|
/// Allow the window to be opened, with the given window.
|
||||||
|
///
|
||||||
|
/// The window must be created referencing the opener window so it can inherit the appropriate attributes.
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
Create { window_id: WindowId },
|
||||||
|
/// Deny the window from being opened.
|
||||||
|
Deny,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scrollbar style to use in the webview.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows**: This option must be given the same value for all webviews that target the same data directory.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub enum ScrollBarStyle {
|
||||||
|
#[default]
|
||||||
|
/// The default scrollbar style for the webview.
|
||||||
|
Default,
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
/// Fluent UI style overlay scrollbars. **Windows Only**
|
||||||
|
///
|
||||||
|
/// Requires WebView2 Runtime version 125.0.2535.41 or higher, does nothing on older versions,
|
||||||
|
/// see <https://learn.microsoft.com/en-us/microsoft-edge/webview2/release-notes/?tabs=dotnetcsharp#10253541>
|
||||||
|
FluentOverlay,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A webview that has yet to be built.
|
||||||
|
pub struct PendingWebview<T: UserEvent, R: Runtime<T>> {
|
||||||
|
/// The label that the webview will be named.
|
||||||
|
pub label: String,
|
||||||
|
|
||||||
|
/// The [`WebviewAttributes`] that the webview will be created with.
|
||||||
|
pub webview_attributes: WebviewAttributes,
|
||||||
|
|
||||||
|
/// Information about the webview that initiated a new window request.
|
||||||
|
pub opener: Option<R::WindowOpener>,
|
||||||
|
|
||||||
|
/// Runtime specific attributes.
|
||||||
|
pub platform_specific_attributes: Vec<R::PlatformSpecificWebviewAttribute>,
|
||||||
|
|
||||||
|
/// Custom protocols to register on the webview
|
||||||
|
pub uri_scheme_protocols: HashMap<String, Box<UriSchemeProtocolHandler>>,
|
||||||
|
|
||||||
|
/// How to handle IPC calls on the webview.
|
||||||
|
pub ipc_handler: Option<WebviewIpcHandler<T, R>>,
|
||||||
|
|
||||||
|
/// A handler to decide if incoming url is allowed to navigate.
|
||||||
|
pub navigation_handler: Option<Box<NavigationHandler>>,
|
||||||
|
|
||||||
|
pub new_window_handler: Option<Box<NewWindowHandler<T, R>>>,
|
||||||
|
|
||||||
|
pub document_title_changed_handler: Option<Box<DocumentTitleChangedHandler>>,
|
||||||
|
|
||||||
|
pub address_changed_handler: Option<Box<AddressChangedHandler>>,
|
||||||
|
|
||||||
|
/// The resolved URL to load on the webview.
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub on_webview_created:
|
||||||
|
Option<Box<dyn Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + Sync>>,
|
||||||
|
|
||||||
|
pub web_resource_request_handler: Option<Box<WebResourceRequestHandler>>,
|
||||||
|
|
||||||
|
pub on_page_load_handler: Option<Box<OnPageLoadHandler>>,
|
||||||
|
|
||||||
|
pub download_handler: Option<Arc<DownloadHandler>>,
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
pub on_web_content_process_terminate_handler: Option<Box<OnWebContentProcessTerminateHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> PendingWebview<T, R> {
|
||||||
|
/// Create a new [`PendingWebview`] with a label from the given [`WebviewAttributes`].
|
||||||
|
pub fn new(
|
||||||
|
webview_attributes: WebviewAttributes,
|
||||||
|
platform_specific_attributes: Vec<R::PlatformSpecificWebviewAttribute>,
|
||||||
|
label: impl Into<String>,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
let label = label.into();
|
||||||
|
if !is_label_valid(&label) {
|
||||||
|
Err(crate::Error::InvalidWindowLabel)
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
webview_attributes,
|
||||||
|
opener: None,
|
||||||
|
platform_specific_attributes,
|
||||||
|
uri_scheme_protocols: Default::default(),
|
||||||
|
label,
|
||||||
|
ipc_handler: None,
|
||||||
|
navigation_handler: None,
|
||||||
|
new_window_handler: None,
|
||||||
|
document_title_changed_handler: None,
|
||||||
|
address_changed_handler: None,
|
||||||
|
url: "tauri://localhost".to_string(),
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
on_webview_created: None,
|
||||||
|
web_resource_request_handler: None,
|
||||||
|
on_page_load_handler: None,
|
||||||
|
download_handler: None,
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
|
on_web_content_process_terminate_handler: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_uri_scheme_protocol<
|
||||||
|
N: Into<String>,
|
||||||
|
H: Fn(&str, http::Request<Vec<u8>>, Box<dyn FnOnce(http::Response<Cow<'static, [u8]>>) + Send>)
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
>(
|
||||||
|
&mut self,
|
||||||
|
uri_scheme: N,
|
||||||
|
protocol_handler: H,
|
||||||
|
) {
|
||||||
|
let uri_scheme = uri_scheme.into();
|
||||||
|
self
|
||||||
|
.uri_scheme_protocols
|
||||||
|
.insert(uri_scheme, Box::new(protocol_handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub fn on_webview_created<
|
||||||
|
F: Fn(CreationContext<'_, '_>) -> Result<(), jni::errors::Error> + Send + Sync + 'static,
|
||||||
|
>(
|
||||||
|
mut self,
|
||||||
|
f: F,
|
||||||
|
) -> Self {
|
||||||
|
self.on_webview_created.replace(Box::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A webview that is not yet managed by Tauri.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DetachedWebview<T: UserEvent, R: Runtime<T>> {
|
||||||
|
/// Name of the window
|
||||||
|
pub label: String,
|
||||||
|
|
||||||
|
/// The [`crate::WebviewDispatch`] associated with the window.
|
||||||
|
pub dispatcher: R::WebviewDispatcher,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWebview<T, R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
label: self.label.clone(),
|
||||||
|
dispatcher: self.dispatcher.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWebview<T, R> {
|
||||||
|
/// Only use the [`DetachedWebview`]'s label to represent its hash.
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.label.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWebview<T, R> {}
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWebview<T, R> {
|
||||||
|
/// Only use the [`DetachedWebview`]'s label to compare equality.
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.label.eq(&other.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The attributes used to create an webview.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WebviewAttributes {
|
||||||
|
pub url: WebviewUrl,
|
||||||
|
pub user_agent: Option<String>,
|
||||||
|
/// A list of initialization javascript scripts to run when loading new pages.
|
||||||
|
/// When webview load a new page, this initialization code will be executed.
|
||||||
|
/// It is guaranteed that code is executed before `window.onload`.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:** scripts are always added to subframes.
|
||||||
|
/// - **Android:** When [addDocumentStartJavaScript] is not supported,
|
||||||
|
/// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
|
||||||
|
/// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
|
||||||
|
///
|
||||||
|
/// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
|
||||||
|
/// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
|
||||||
|
pub initialization_scripts: Vec<InitializationScript>,
|
||||||
|
pub data_directory: Option<PathBuf>,
|
||||||
|
pub drag_drop_handler_enabled: bool,
|
||||||
|
pub clipboard: bool,
|
||||||
|
pub accept_first_mouse: bool,
|
||||||
|
pub additional_browser_args: Option<String>,
|
||||||
|
pub window_effects: Option<WindowEffectsConfig>,
|
||||||
|
pub incognito: bool,
|
||||||
|
pub transparent: bool,
|
||||||
|
pub focus: bool,
|
||||||
|
pub bounds: Option<Rect>,
|
||||||
|
pub auto_resize: bool,
|
||||||
|
pub proxy_url: Option<Url>,
|
||||||
|
pub zoom_hotkeys_enabled: bool,
|
||||||
|
pub browser_extensions_enabled: bool,
|
||||||
|
pub extensions_path: Option<PathBuf>,
|
||||||
|
pub data_store_identifier: Option<[u8; 16]>,
|
||||||
|
pub use_https_scheme: bool,
|
||||||
|
pub devtools: Option<bool>,
|
||||||
|
pub background_color: Option<Color>,
|
||||||
|
pub traffic_light_position: Option<dpi::Position>,
|
||||||
|
pub background_throttling: Option<BackgroundThrottlingPolicy>,
|
||||||
|
pub javascript_disabled: bool,
|
||||||
|
/// on macOS and iOS there is a link preview on long pressing links, this is enabled by default.
|
||||||
|
/// see https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
|
||||||
|
pub allow_link_preview: bool,
|
||||||
|
pub scroll_bar_style: ScrollBarStyle,
|
||||||
|
/// Controls the WebView's browser-level general autofill behavior.
|
||||||
|
///
|
||||||
|
/// **This option does not disable password or credit card autofill.**
|
||||||
|
///
|
||||||
|
/// When set to `false`, the WebView will not automatically populate
|
||||||
|
/// general form fields using previously stored data such as addresses
|
||||||
|
/// or contact information.
|
||||||
|
///
|
||||||
|
/// If not specified, this is `true` by default.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows**: Supported. WebView2's autofill feature (called
|
||||||
|
/// "Suggestions") may not honor `autocomplete="off"` on input
|
||||||
|
/// elements in some cases.
|
||||||
|
/// - **Linux / Android / iOS / macOS**: Unsupported and performs no
|
||||||
|
/// operation.
|
||||||
|
pub general_autofill_enabled: bool,
|
||||||
|
/// Allows overriding the keyboard accessory view on iOS.
|
||||||
|
/// Returning `None` effectively removes the view.
|
||||||
|
///
|
||||||
|
/// The closure parameter is the webview instance.
|
||||||
|
///
|
||||||
|
/// The accessory view is the view that appears above the keyboard when a text input element is focused.
|
||||||
|
/// It usually displays a view with "Done", "Next" buttons.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This relies on [`objc2_ui_kit`] which does not provide a stable API yet, so it can receive breaking changes in minor releases.
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
pub input_accessory_view_builder: Option<InputAccessoryViewBuilder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for WebviewAttributes {}
|
||||||
|
unsafe impl Sync for WebviewAttributes {}
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct InputAccessoryViewBuilder(pub Box<InputAccessoryViewBuilderFn>);
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
impl std::fmt::Debug for InputAccessoryViewBuilder {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
f.debug_struct("InputAccessoryViewBuilder").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
impl InputAccessoryViewBuilder {
|
||||||
|
pub fn new(builder: Box<InputAccessoryViewBuilderFn>) -> Self {
|
||||||
|
Self(builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&WindowConfig> for WebviewAttributes {
|
||||||
|
fn from(config: &WindowConfig) -> Self {
|
||||||
|
let mut builder = Self::new(config.url.clone())
|
||||||
|
.incognito(config.incognito)
|
||||||
|
.focused(config.focus)
|
||||||
|
.zoom_hotkeys_enabled(config.zoom_hotkeys_enabled)
|
||||||
|
.use_https_scheme(config.use_https_scheme)
|
||||||
|
.browser_extensions_enabled(config.browser_extensions_enabled)
|
||||||
|
.background_throttling(config.background_throttling.clone())
|
||||||
|
.devtools(config.devtools)
|
||||||
|
.scroll_bar_style(match config.scroll_bar_style {
|
||||||
|
ConfigScrollBarStyle::Default => ScrollBarStyle::Default,
|
||||||
|
#[cfg(windows)]
|
||||||
|
ConfigScrollBarStyle::FluentOverlay => ScrollBarStyle::FluentOverlay,
|
||||||
|
_ => ScrollBarStyle::Default,
|
||||||
|
})
|
||||||
|
.general_autofill_enabled(config.general_autofill_enabled);
|
||||||
|
|
||||||
|
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||||
|
{
|
||||||
|
builder = builder.transparent(config.transparent);
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
if let Some(position) = &config.traffic_light_position {
|
||||||
|
builder =
|
||||||
|
builder.traffic_light_position(dpi::LogicalPosition::new(position.x, position.y).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder = builder.accept_first_mouse(config.accept_first_mouse);
|
||||||
|
if !config.drag_drop_enabled {
|
||||||
|
builder = builder.disable_drag_drop_handler();
|
||||||
|
}
|
||||||
|
if let Some(user_agent) = &config.user_agent {
|
||||||
|
builder = builder.user_agent(user_agent);
|
||||||
|
}
|
||||||
|
if let Some(additional_browser_args) = &config.additional_browser_args {
|
||||||
|
builder = builder.additional_browser_args(additional_browser_args);
|
||||||
|
}
|
||||||
|
if let Some(effects) = &config.window_effects {
|
||||||
|
builder = builder.window_effects(effects.clone());
|
||||||
|
}
|
||||||
|
if let Some(url) = &config.proxy_url {
|
||||||
|
builder = builder.proxy_url(url.to_owned());
|
||||||
|
}
|
||||||
|
if let Some(color) = config.background_color {
|
||||||
|
builder = builder.background_color(color);
|
||||||
|
}
|
||||||
|
builder.javascript_disabled = config.javascript_disabled;
|
||||||
|
builder.allow_link_preview = config.allow_link_preview;
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
if config.disable_input_accessory_view {
|
||||||
|
builder
|
||||||
|
.input_accessory_view_builder
|
||||||
|
.replace(InputAccessoryViewBuilder::new(Box::new(|_webview| None)));
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebviewAttributes {
|
||||||
|
/// Initializes the default attributes for a webview.
|
||||||
|
pub fn new(url: WebviewUrl) -> Self {
|
||||||
|
Self {
|
||||||
|
url,
|
||||||
|
user_agent: None,
|
||||||
|
initialization_scripts: Vec::new(),
|
||||||
|
data_directory: None,
|
||||||
|
drag_drop_handler_enabled: true,
|
||||||
|
clipboard: false,
|
||||||
|
accept_first_mouse: false,
|
||||||
|
additional_browser_args: None,
|
||||||
|
window_effects: None,
|
||||||
|
incognito: false,
|
||||||
|
transparent: false,
|
||||||
|
focus: true,
|
||||||
|
bounds: None,
|
||||||
|
auto_resize: false,
|
||||||
|
proxy_url: None,
|
||||||
|
zoom_hotkeys_enabled: false,
|
||||||
|
browser_extensions_enabled: false,
|
||||||
|
data_store_identifier: None,
|
||||||
|
extensions_path: None,
|
||||||
|
use_https_scheme: false,
|
||||||
|
devtools: None,
|
||||||
|
background_color: None,
|
||||||
|
traffic_light_position: None,
|
||||||
|
background_throttling: None,
|
||||||
|
javascript_disabled: false,
|
||||||
|
allow_link_preview: true,
|
||||||
|
scroll_bar_style: ScrollBarStyle::Default,
|
||||||
|
general_autofill_enabled: true,
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
input_accessory_view_builder: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the user agent
|
||||||
|
#[must_use]
|
||||||
|
pub fn user_agent(mut self, user_agent: &str) -> Self {
|
||||||
|
self.user_agent = Some(user_agent.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an init script for the main frame.
|
||||||
|
///
|
||||||
|
/// When webview load a new page, this initialization code will be executed.
|
||||||
|
/// It is guaranteed that code is executed before `window.onload`.
|
||||||
|
///
|
||||||
|
/// This is executed only on the main frame.
|
||||||
|
/// If you only want to run it in all frames, use [`Self::initialization_script_on_all_frames`] instead.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:** scripts are always added to subframes.
|
||||||
|
/// - **Android:** When [addDocumentStartJavaScript] is not supported,
|
||||||
|
/// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
|
||||||
|
/// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
|
||||||
|
///
|
||||||
|
/// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
|
||||||
|
/// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
|
||||||
|
#[must_use]
|
||||||
|
pub fn initialization_script(mut self, script: impl Into<String>) -> Self {
|
||||||
|
self.initialization_scripts.push(InitializationScript {
|
||||||
|
script: script.into(),
|
||||||
|
for_main_frame_only: true,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds an init script for all frames.
|
||||||
|
///
|
||||||
|
/// When webview load a new page, this initialization code will be executed.
|
||||||
|
/// It is guaranteed that code is executed before `window.onload`.
|
||||||
|
///
|
||||||
|
/// This is executed on all frames, main frame and also sub frames.
|
||||||
|
/// If you only want to run it in the main frame, use [`Self::initialization_script`] instead.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:** scripts are always added to subframes.
|
||||||
|
/// - **Android:** When [addDocumentStartJavaScript] is not supported,
|
||||||
|
/// we prepend initialization scripts to each HTML head (implementation only supported on custom protocol URLs).
|
||||||
|
/// For remote URLs, we use [onPageStarted] which is not guaranteed to run before other scripts.
|
||||||
|
///
|
||||||
|
/// [addDocumentStartJavaScript]: https://developer.android.com/reference/androidx/webkit/WebViewCompat#addDocumentStartJavaScript(android.webkit.WebView,java.lang.String,java.util.Set%3Cjava.lang.String%3E)
|
||||||
|
/// [onPageStarted]: https://developer.android.com/reference/android/webkit/WebViewClient#onPageStarted(android.webkit.WebView,%20java.lang.String,%20android.graphics.Bitmap)
|
||||||
|
#[must_use]
|
||||||
|
pub fn initialization_script_on_all_frames(mut self, script: impl Into<String>) -> Self {
|
||||||
|
self.initialization_scripts.push(InitializationScript {
|
||||||
|
script: script.into(),
|
||||||
|
for_main_frame_only: false,
|
||||||
|
});
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data directory for the webview.
|
||||||
|
#[must_use]
|
||||||
|
pub fn data_directory(mut self, data_directory: PathBuf) -> Self {
|
||||||
|
self.data_directory.replace(data_directory);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables the drag and drop handler. This is required to use HTML5 drag and drop APIs on the frontend on Windows.
|
||||||
|
#[must_use]
|
||||||
|
pub fn disable_drag_drop_handler(mut self) -> Self {
|
||||||
|
self.drag_drop_handler_enabled = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables clipboard access for the page rendered on **Linux** and **Windows**.
|
||||||
|
///
|
||||||
|
/// **macOS** doesn't provide such method and is always enabled by default,
|
||||||
|
/// but you still need to add menu item accelerators to use shortcuts.
|
||||||
|
#[must_use]
|
||||||
|
pub fn enable_clipboard_access(mut self) -> Self {
|
||||||
|
self.clipboard = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether clicking an inactive window also clicks through to the webview.
|
||||||
|
#[must_use]
|
||||||
|
pub fn accept_first_mouse(mut self, accept: bool) -> Self {
|
||||||
|
self.accept_first_mouse = accept;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets additional browser arguments. **Windows Only**
|
||||||
|
#[must_use]
|
||||||
|
pub fn additional_browser_args(mut self, additional_args: &str) -> Self {
|
||||||
|
self.additional_browser_args = Some(additional_args.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets window effects
|
||||||
|
#[must_use]
|
||||||
|
pub fn window_effects(mut self, effects: WindowEffectsConfig) -> Self {
|
||||||
|
self.window_effects = Some(effects);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable incognito mode for the WebView.
|
||||||
|
#[must_use]
|
||||||
|
pub fn incognito(mut self, incognito: bool) -> Self {
|
||||||
|
self.incognito = incognito;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable transparency for the WebView.
|
||||||
|
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||||
|
#[must_use]
|
||||||
|
pub fn transparent(mut self, transparent: bool) -> Self {
|
||||||
|
self.transparent = transparent;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the webview should be focused or not.
|
||||||
|
#[must_use]
|
||||||
|
pub fn focused(mut self, focus: bool) -> Self {
|
||||||
|
self.focus = focus;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the webview to automatically grow and shrink its size and position when the parent window resizes.
|
||||||
|
#[must_use]
|
||||||
|
pub fn auto_resize(mut self) -> Self {
|
||||||
|
self.auto_resize = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable proxy for the WebView
|
||||||
|
#[must_use]
|
||||||
|
pub fn proxy_url(mut self, url: Url) -> Self {
|
||||||
|
self.proxy_url = Some(url);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether page zooming by hotkeys is enabled
|
||||||
|
///
|
||||||
|
/// ## Platform-specific:
|
||||||
|
///
|
||||||
|
/// - **Windows**: Controls WebView2's [`IsZoomControlEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings?view=webview2-winrt-1.0.2420.47#iszoomcontrolenabled) setting.
|
||||||
|
/// - **MacOS / Linux**: Injects a polyfill that zooms in and out with `ctrl/command` + `-/=`,
|
||||||
|
/// 20% in each step, ranging from 20% to 1000%. Requires `webview:allow-set-webview-zoom` permission
|
||||||
|
///
|
||||||
|
/// - **Android / iOS**: Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
pub fn zoom_hotkeys_enabled(mut self, enabled: bool) -> Self {
|
||||||
|
self.zoom_hotkeys_enabled = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether browser extensions can be installed for the webview process
|
||||||
|
///
|
||||||
|
/// ## Platform-specific:
|
||||||
|
///
|
||||||
|
/// - **Windows**: Enables the WebView2 environment's [`AreBrowserExtensionsEnabled`](https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2environmentoptions?view=webview2-winrt-1.0.2739.15#arebrowserextensionsenabled)
|
||||||
|
/// - **MacOS / Linux / iOS / Android** - Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
pub fn browser_extensions_enabled(mut self, enabled: bool) -> Self {
|
||||||
|
self.browser_extensions_enabled = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets whether the custom protocols should use `https://<scheme>.localhost` instead of the default `http://<scheme>.localhost` on Windows and Android. Defaults to `false`.
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// Using a `https` scheme will NOT allow mixed content when trying to fetch `http` endpoints and therefore will not match the behavior of the `<scheme>://localhost` protocols used on macOS and Linux.
|
||||||
|
///
|
||||||
|
/// ## Warning
|
||||||
|
///
|
||||||
|
/// Changing this value between releases will change the IndexedDB, cookies and localstorage location and your app will not be able to access the old data.
|
||||||
|
#[must_use]
|
||||||
|
pub fn use_https_scheme(mut self, enabled: bool) -> Self {
|
||||||
|
self.use_https_scheme = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether web inspector, which is usually called browser devtools, is enabled or not. Enabled by default.
|
||||||
|
///
|
||||||
|
/// This API works in **debug** builds, but requires `devtools` feature flag to enable it in **release** builds.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - macOS: This will call private functions on **macOS**.
|
||||||
|
/// - Android: Open `chrome://inspect/#devices` in Chrome to get the devtools window. Wry's `WebView` devtools API isn't supported on Android.
|
||||||
|
/// - iOS: Open Safari > Develop > [Your Device Name] > [Your WebView] to get the devtools window.
|
||||||
|
#[must_use]
|
||||||
|
pub fn devtools(mut self, enabled: Option<bool>) -> Self {
|
||||||
|
self.devtools = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the window and webview background color.
|
||||||
|
/// ## Platform-specific:
|
||||||
|
///
|
||||||
|
/// - **Windows**: On Windows 7, alpha channel is ignored for the webview layer.
|
||||||
|
/// - **Windows**: On Windows 8 and newer, if alpha channel is not `0`, it will be ignored.
|
||||||
|
#[must_use]
|
||||||
|
pub fn background_color(mut self, color: Color) -> Self {
|
||||||
|
self.background_color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the position of the window controls. Available on macOS only.
|
||||||
|
///
|
||||||
|
/// Requires titleBarStyle: Overlay and decorations: true.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Linux / Windows / iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
pub fn traffic_light_position(mut self, position: dpi::Position) -> Self {
|
||||||
|
self.traffic_light_position = Some(position);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to show a link preview when long pressing on links. Available on macOS and iOS only.
|
||||||
|
///
|
||||||
|
/// Default is true.
|
||||||
|
///
|
||||||
|
/// See https://docs.rs/objc2-web-kit/latest/objc2_web_kit/struct.WKWebView.html#method.allowsLinkPreview
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Linux / Windows / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
pub fn allow_link_preview(mut self, allow_link_preview: bool) -> Self {
|
||||||
|
self.allow_link_preview = allow_link_preview;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change the default background throttling behavior.
|
||||||
|
///
|
||||||
|
/// By default, browsers use a suspend policy that will throttle timers and even unload
|
||||||
|
/// the whole tab (view) to free resources after roughly 5 minutes when a view became
|
||||||
|
/// minimized or hidden. This will pause all tasks until the documents visibility state
|
||||||
|
/// changes back from hidden to visible by bringing the view back to the foreground.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Linux / Windows / Android**: Unsupported. Workarounds like a pending WebLock transaction might suffice.
|
||||||
|
/// - **iOS**: Supported since version 17.0+.
|
||||||
|
/// - **macOS**: Supported since version 14.0+.
|
||||||
|
///
|
||||||
|
/// see <https://github.com/tauri-apps/tauri/issues/5250#issuecomment-2569380578>
|
||||||
|
#[must_use]
|
||||||
|
pub fn background_throttling(mut self, policy: Option<BackgroundThrottlingPolicy>) -> Self {
|
||||||
|
self.background_throttling = policy;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the native scrollbar style to use with the webview.
|
||||||
|
/// CSS styles that modify the scrollbar are applied on top of the native appearance configured here.
|
||||||
|
///
|
||||||
|
/// Defaults to [`ScrollBarStyle::Default`], which is the browser default.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows**:
|
||||||
|
/// - [`ScrollBarStyle::FluentOverlay`] requires WebView2 Runtime version 125.0.2535.41 or higher,
|
||||||
|
/// and does nothing on older versions.
|
||||||
|
/// - This option must be given the same value for all webviews that target the same data directory. Use
|
||||||
|
/// [`WebviewAttributes::data_directory`] to change data directories if needed.
|
||||||
|
/// - **Linux / Android / iOS / macOS**: Unsupported. Only supports `Default` and performs no operation.
|
||||||
|
#[must_use]
|
||||||
|
pub fn scroll_bar_style(mut self, style: ScrollBarStyle) -> Self {
|
||||||
|
self.scroll_bar_style = style;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controls the WebView's browser-level general autofill behavior.
|
||||||
|
///
|
||||||
|
/// **This option does not disable password or credit card autofill.**
|
||||||
|
///
|
||||||
|
/// When set to `false`, the WebView will not automatically populate
|
||||||
|
/// general form fields using previously stored data such as addresses
|
||||||
|
/// or contact information.
|
||||||
|
///
|
||||||
|
/// By default, this is `true`.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows**: Supported. WebView2's autofill feature (called
|
||||||
|
/// "Suggestions") may not honor `autocomplete="off"` on input
|
||||||
|
/// elements in some cases.
|
||||||
|
/// - **Linux / Android / iOS / macOS**: Unsupported and performs no
|
||||||
|
/// operation.
|
||||||
|
#[must_use]
|
||||||
|
pub fn general_autofill_enabled(mut self, enabled: bool) -> Self {
|
||||||
|
self.general_autofill_enabled = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IPC handler.
|
||||||
|
pub type WebviewIpcHandler<T, R> = Box<dyn Fn(DetachedWebview<T, R>, Request<String>) + Send>;
|
||||||
|
|
||||||
|
/// An initialization script
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InitializationScript {
|
||||||
|
/// The script to run
|
||||||
|
pub script: String,
|
||||||
|
/// Whether the script should be injected to main frame only
|
||||||
|
pub for_main_frame_only: bool,
|
||||||
|
}
|
||||||
661
apps/tauri/src-tauri/vendor/tauri-runtime/src/window.rs
vendored
Normal file
661
apps/tauri/src-tauri/vendor/tauri-runtime/src/window.rs
vendored
Normal file
|
|
@ -0,0 +1,661 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! A layer between raw [`Runtime`] windows and Tauri.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Icon, Runtime, UserEvent, WindowDispatch,
|
||||||
|
webview::{DetachedWebview, PendingWebview},
|
||||||
|
};
|
||||||
|
|
||||||
|
use dpi::PixelUnit;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
use tauri_utils::{
|
||||||
|
Theme,
|
||||||
|
config::{Color, WindowConfig},
|
||||||
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
marker::PhantomData,
|
||||||
|
path::PathBuf,
|
||||||
|
sync::mpsc::Sender,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An event from a window.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WindowEvent {
|
||||||
|
/// The size of the window has changed. Contains the client area's new dimensions.
|
||||||
|
Resized(dpi::PhysicalSize<u32>),
|
||||||
|
/// The position of the window has changed. Contains the window's new position.
|
||||||
|
Moved(dpi::PhysicalPosition<i32>),
|
||||||
|
/// The window has been requested to close.
|
||||||
|
CloseRequested {
|
||||||
|
/// A signal sender. If a `true` value is emitted, the window won't be closed.
|
||||||
|
signal_tx: Sender<bool>,
|
||||||
|
},
|
||||||
|
/// The window has been destroyed.
|
||||||
|
Destroyed,
|
||||||
|
/// The window gained or lost focus.
|
||||||
|
///
|
||||||
|
/// The parameter is true if the window has gained focus, and false if it has lost focus.
|
||||||
|
Focused(bool),
|
||||||
|
/// The window's scale factor has changed.
|
||||||
|
///
|
||||||
|
/// The following user actions can cause DPI changes:
|
||||||
|
///
|
||||||
|
/// - Changing the display's resolution.
|
||||||
|
/// - Changing the display's scale factor (e.g. in Control Panel on Windows).
|
||||||
|
/// - Moving the window to a display with a different scale factor.
|
||||||
|
ScaleFactorChanged {
|
||||||
|
/// The new scale factor.
|
||||||
|
scale_factor: f64,
|
||||||
|
/// The window inner size.
|
||||||
|
new_inner_size: dpi::PhysicalSize<u32>,
|
||||||
|
},
|
||||||
|
/// An event associated with the drag and drop action.
|
||||||
|
DragDrop(DragDropEvent),
|
||||||
|
/// The system window theme has changed.
|
||||||
|
///
|
||||||
|
/// Applications might wish to react to this to change the theme of the content of the window when the system changes the window theme.
|
||||||
|
ThemeChanged(Theme),
|
||||||
|
|
||||||
|
/// Emitted when the application has been suspended.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Android**: This is triggered by `onPause` method of the Activity.
|
||||||
|
/// - **iOS**: This is triggered by `applicationWillResignActive` method of the UIApplicationDelegate.
|
||||||
|
/// - **Linux / macOS / Windows**: Unsupported.
|
||||||
|
#[cfg(mobile)]
|
||||||
|
Suspended,
|
||||||
|
|
||||||
|
/// Emitted when the application has been resumed.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Android**: This is triggered by `onResume` method of the Activity. The first onResume() is ignored to match the iOS implementation, since that is called on activity creation.
|
||||||
|
/// - **iOS**: This is triggered by `applicationWillEnterForeground` method of the UIApplicationDelegate.
|
||||||
|
/// - **Linux / macOS / Windows**: Unsupported.
|
||||||
|
#[cfg(mobile)]
|
||||||
|
Resumed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An event from a window.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum WebviewEvent {
|
||||||
|
/// An event associated with the drag and drop action.
|
||||||
|
DragDrop(DragDropEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The drag drop event payload.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum DragDropEvent {
|
||||||
|
/// A drag operation has entered the webview.
|
||||||
|
Enter {
|
||||||
|
/// List of paths that are being dragged onto the webview.
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
/// The position of the mouse cursor.
|
||||||
|
position: dpi::PhysicalPosition<f64>,
|
||||||
|
},
|
||||||
|
/// A drag operation is moving over the webview.
|
||||||
|
Over {
|
||||||
|
/// The position of the mouse cursor.
|
||||||
|
position: dpi::PhysicalPosition<f64>,
|
||||||
|
},
|
||||||
|
/// The file(s) have been dropped onto the webview.
|
||||||
|
Drop {
|
||||||
|
/// List of paths that are being dropped onto the window.
|
||||||
|
paths: Vec<PathBuf>,
|
||||||
|
/// The position of the mouse cursor.
|
||||||
|
position: dpi::PhysicalPosition<f64>,
|
||||||
|
},
|
||||||
|
/// The drag operation has been cancelled or left the window.
|
||||||
|
Leave,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes the appearance of the mouse cursor.
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CursorIcon {
|
||||||
|
/// The platform-dependent default cursor.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// A simple crosshair.
|
||||||
|
Crosshair,
|
||||||
|
/// A hand (often used to indicate links in web browsers).
|
||||||
|
Hand,
|
||||||
|
/// Self explanatory.
|
||||||
|
Arrow,
|
||||||
|
/// Indicates something is to be moved.
|
||||||
|
Move,
|
||||||
|
/// Indicates text that may be selected or edited.
|
||||||
|
Text,
|
||||||
|
/// Program busy indicator.
|
||||||
|
Wait,
|
||||||
|
/// Help indicator (often rendered as a "?")
|
||||||
|
Help,
|
||||||
|
/// Progress indicator. Shows that processing is being done. But in contrast
|
||||||
|
/// with "Wait" the user may still interact with the program. Often rendered
|
||||||
|
/// as a spinning beach ball, or an arrow with a watch or hourglass.
|
||||||
|
Progress,
|
||||||
|
|
||||||
|
/// Cursor showing that something cannot be done.
|
||||||
|
NotAllowed,
|
||||||
|
ContextMenu,
|
||||||
|
Cell,
|
||||||
|
VerticalText,
|
||||||
|
Alias,
|
||||||
|
Copy,
|
||||||
|
NoDrop,
|
||||||
|
/// Indicates something can be grabbed.
|
||||||
|
Grab,
|
||||||
|
/// Indicates something is grabbed.
|
||||||
|
Grabbing,
|
||||||
|
AllScroll,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut,
|
||||||
|
|
||||||
|
/// Indicate that some edge is to be moved. For example, the 'SeResize' cursor
|
||||||
|
/// is used when the movement starts from the south-east corner of the box.
|
||||||
|
EResize,
|
||||||
|
NResize,
|
||||||
|
NeResize,
|
||||||
|
NwResize,
|
||||||
|
SResize,
|
||||||
|
SeResize,
|
||||||
|
SwResize,
|
||||||
|
WResize,
|
||||||
|
EwResize,
|
||||||
|
NsResize,
|
||||||
|
NeswResize,
|
||||||
|
NwseResize,
|
||||||
|
ColResize,
|
||||||
|
RowResize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for CursorIcon {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(match s.to_lowercase().as_str() {
|
||||||
|
"default" => CursorIcon::Default,
|
||||||
|
"crosshair" => CursorIcon::Crosshair,
|
||||||
|
"hand" => CursorIcon::Hand,
|
||||||
|
"arrow" => CursorIcon::Arrow,
|
||||||
|
"move" => CursorIcon::Move,
|
||||||
|
"text" => CursorIcon::Text,
|
||||||
|
"wait" => CursorIcon::Wait,
|
||||||
|
"help" => CursorIcon::Help,
|
||||||
|
"progress" => CursorIcon::Progress,
|
||||||
|
"notallowed" => CursorIcon::NotAllowed,
|
||||||
|
"contextmenu" => CursorIcon::ContextMenu,
|
||||||
|
"cell" => CursorIcon::Cell,
|
||||||
|
"verticaltext" => CursorIcon::VerticalText,
|
||||||
|
"alias" => CursorIcon::Alias,
|
||||||
|
"copy" => CursorIcon::Copy,
|
||||||
|
"nodrop" => CursorIcon::NoDrop,
|
||||||
|
"grab" => CursorIcon::Grab,
|
||||||
|
"grabbing" => CursorIcon::Grabbing,
|
||||||
|
"allscroll" => CursorIcon::AllScroll,
|
||||||
|
"zoomin" => CursorIcon::ZoomIn,
|
||||||
|
"zoomout" => CursorIcon::ZoomOut,
|
||||||
|
"eresize" => CursorIcon::EResize,
|
||||||
|
"nresize" => CursorIcon::NResize,
|
||||||
|
"neresize" => CursorIcon::NeResize,
|
||||||
|
"nwresize" => CursorIcon::NwResize,
|
||||||
|
"sresize" => CursorIcon::SResize,
|
||||||
|
"seresize" => CursorIcon::SeResize,
|
||||||
|
"swresize" => CursorIcon::SwResize,
|
||||||
|
"wresize" => CursorIcon::WResize,
|
||||||
|
"ewresize" => CursorIcon::EwResize,
|
||||||
|
"nsresize" => CursorIcon::NsResize,
|
||||||
|
"neswresize" => CursorIcon::NeswResize,
|
||||||
|
"nwseresize" => CursorIcon::NwseResize,
|
||||||
|
"colresize" => CursorIcon::ColResize,
|
||||||
|
"rowresize" => CursorIcon::RowResize,
|
||||||
|
_ => CursorIcon::Default,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Window size constraints
|
||||||
|
#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct WindowSizeConstraints {
|
||||||
|
/// The minimum width a window can be, If this is `None`, the window will have no minimum width.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
pub min_width: Option<PixelUnit>,
|
||||||
|
/// The minimum height a window can be, If this is `None`, the window will have no minimum height.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
pub min_height: Option<PixelUnit>,
|
||||||
|
/// The maximum width a window can be, If this is `None`, the window will have no maximum width.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
pub max_width: Option<PixelUnit>,
|
||||||
|
/// The maximum height a window can be, If this is `None`, the window will have no maximum height.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
pub max_height: Option<PixelUnit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Do **NOT** implement this trait except for use in a custom [`Runtime`]
|
||||||
|
///
|
||||||
|
/// This trait is separate from [`WindowBuilder`] to prevent "accidental" implementation.
|
||||||
|
pub trait WindowBuilderBase: std::fmt::Debug + Clone + Sized {}
|
||||||
|
|
||||||
|
/// A builder for all attributes related to a single window.
|
||||||
|
///
|
||||||
|
/// This trait is only meant to be implemented by a custom [`Runtime`]
|
||||||
|
/// and not by applications.
|
||||||
|
pub trait WindowBuilder: WindowBuilderBase {
|
||||||
|
/// Initializes a new window attributes builder.
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
|
/// Initializes a new window builder from a [`WindowConfig`]
|
||||||
|
fn with_config(config: &WindowConfig) -> Self;
|
||||||
|
|
||||||
|
/// Show window in the center of the screen.
|
||||||
|
#[must_use]
|
||||||
|
fn center(self) -> Self;
|
||||||
|
|
||||||
|
/// The initial position of the window in logical pixels.
|
||||||
|
#[must_use]
|
||||||
|
fn position(self, x: f64, y: f64) -> Self;
|
||||||
|
|
||||||
|
/// Window size in logical pixels.
|
||||||
|
#[must_use]
|
||||||
|
fn inner_size(self, width: f64, height: f64) -> Self;
|
||||||
|
|
||||||
|
/// Window min inner size in logical pixels.
|
||||||
|
#[must_use]
|
||||||
|
fn min_inner_size(self, min_width: f64, min_height: f64) -> Self;
|
||||||
|
|
||||||
|
/// Window max inner size in logical pixels.
|
||||||
|
#[must_use]
|
||||||
|
fn max_inner_size(self, max_width: f64, max_height: f64) -> Self;
|
||||||
|
|
||||||
|
/// Window inner size constraints.
|
||||||
|
#[must_use]
|
||||||
|
fn inner_size_constraints(self, constraints: WindowSizeConstraints) -> Self;
|
||||||
|
|
||||||
|
/// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size) on creation
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn prevent_overflow(self) -> Self;
|
||||||
|
|
||||||
|
/// Prevent the window from overflowing the working area (e.g. monitor size - taskbar size)
|
||||||
|
/// on creation with a margin
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn prevent_overflow_with_margin(self, margin: dpi::Size) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window is resizable or not.
|
||||||
|
/// When resizable is set to false, native window's maximize button is automatically disabled.
|
||||||
|
#[must_use]
|
||||||
|
fn resizable(self, resizable: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window's native maximize button is enabled or not.
|
||||||
|
/// If resizable is set to false, this setting is ignored.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **macOS:** Disables the "zoom" button in the window titlebar, which is also used to enter fullscreen mode.
|
||||||
|
/// - **Linux / iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn maximizable(self, maximizable: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window's native minimize button is enabled or not.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Linux / iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn minimizable(self, minimizable: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window's native close button is enabled or not.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Linux:** "GTK+ will do its best to convince the window manager not to show a close button.
|
||||||
|
/// Depending on the system, this function may not have any effect when called on a window that is already visible"
|
||||||
|
/// - **iOS / Android:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn closable(self, closable: bool) -> Self;
|
||||||
|
|
||||||
|
/// The title of the window in the title bar.
|
||||||
|
#[must_use]
|
||||||
|
fn title<S: Into<String>>(self, title: S) -> Self;
|
||||||
|
|
||||||
|
/// Whether to start the window in fullscreen or not.
|
||||||
|
#[must_use]
|
||||||
|
fn fullscreen(self, fullscreen: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window will be initially focused or not.
|
||||||
|
#[must_use]
|
||||||
|
fn focused(self, focused: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window will be focusable or not.
|
||||||
|
#[must_use]
|
||||||
|
fn focusable(self, focusable: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should be maximized upon creation.
|
||||||
|
#[must_use]
|
||||||
|
fn maximized(self, maximized: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should be immediately visible upon creation.
|
||||||
|
#[must_use]
|
||||||
|
fn visible(self, visible: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should be transparent. If this is true, writing colors
|
||||||
|
/// with alpha values different than `1.0` will produce a transparent window.
|
||||||
|
#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(not(target_os = "macos"), feature = "macos-private-api")))
|
||||||
|
)]
|
||||||
|
#[must_use]
|
||||||
|
fn transparent(self, transparent: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should have borders and bars.
|
||||||
|
#[must_use]
|
||||||
|
fn decorations(self, decorations: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should always be below other windows.
|
||||||
|
#[must_use]
|
||||||
|
fn always_on_bottom(self, always_on_bottom: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should always be on top of other windows.
|
||||||
|
#[must_use]
|
||||||
|
fn always_on_top(self, always_on_top: bool) -> Self;
|
||||||
|
|
||||||
|
/// Whether the window should be visible on all workspaces or virtual desktops.
|
||||||
|
#[must_use]
|
||||||
|
fn visible_on_all_workspaces(self, visible_on_all_workspaces: bool) -> Self;
|
||||||
|
|
||||||
|
/// Prevents the window contents from being captured by other apps.
|
||||||
|
#[must_use]
|
||||||
|
fn content_protected(self, protected: bool) -> Self;
|
||||||
|
|
||||||
|
/// Sets the window icon.
|
||||||
|
fn icon(self, icon: Icon) -> crate::Result<Self>;
|
||||||
|
|
||||||
|
/// Sets whether or not the window icon should be added to the taskbar.
|
||||||
|
#[must_use]
|
||||||
|
fn skip_taskbar(self, skip: bool) -> Self;
|
||||||
|
|
||||||
|
/// Set the window background color.
|
||||||
|
#[must_use]
|
||||||
|
fn background_color(self, color: Color) -> Self;
|
||||||
|
|
||||||
|
/// Sets whether or not the window has shadow.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:**
|
||||||
|
/// - `false` has no effect on decorated window, shadows are always ON.
|
||||||
|
/// - `true` will make undecorated window have a 1px white border,
|
||||||
|
/// and on Windows 11, it will have a rounded corners.
|
||||||
|
/// - **Linux:** Unsupported.
|
||||||
|
#[must_use]
|
||||||
|
fn shadow(self, enable: bool) -> Self;
|
||||||
|
|
||||||
|
/// Set an owner to the window to be created.
|
||||||
|
///
|
||||||
|
/// From MSDN:
|
||||||
|
/// - An owned window is always above its owner in the z-order.
|
||||||
|
/// - The system automatically destroys an owned window when its owner is destroyed.
|
||||||
|
/// - An owned window is hidden when its owner is minimized.
|
||||||
|
///
|
||||||
|
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#owned-windows>
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[must_use]
|
||||||
|
fn owner(self, owner: HWND) -> Self;
|
||||||
|
|
||||||
|
/// Sets a parent to the window to be created.
|
||||||
|
///
|
||||||
|
/// A child window has the WS_CHILD style and is confined to the client area of its parent window.
|
||||||
|
///
|
||||||
|
/// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[must_use]
|
||||||
|
fn parent(self, parent: HWND) -> Self;
|
||||||
|
|
||||||
|
/// Sets a parent to the window to be created.
|
||||||
|
///
|
||||||
|
/// See <https://developer.apple.com/documentation/appkit/nswindow/1419152-addchildwindow?language=objc>
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[must_use]
|
||||||
|
fn parent(self, parent: *mut std::ffi::c_void) -> Self;
|
||||||
|
|
||||||
|
/// Sets the window to be created transient for parent.
|
||||||
|
///
|
||||||
|
/// See <https://docs.gtk.org/gtk3/method.Window.set_transient_for.html>
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
fn transient_for(self, parent: &impl gtk::glib::IsA<gtk::Window>) -> Self;
|
||||||
|
|
||||||
|
/// Enables or disables drag and drop support.
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[must_use]
|
||||||
|
fn drag_and_drop(self, enabled: bool) -> Self;
|
||||||
|
|
||||||
|
/// Hide the titlebar. Titlebar buttons will still be visible.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[must_use]
|
||||||
|
fn title_bar_style(self, style: tauri_utils::TitleBarStyle) -> Self;
|
||||||
|
|
||||||
|
/// Change the position of the window controls on macOS.
|
||||||
|
///
|
||||||
|
/// Requires titleBarStyle: Overlay and decorations: true.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[must_use]
|
||||||
|
fn traffic_light_position<P: Into<dpi::Position>>(self, position: P) -> Self;
|
||||||
|
|
||||||
|
/// Hide the window title.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[must_use]
|
||||||
|
fn hidden_title(self, hidden: bool) -> Self;
|
||||||
|
|
||||||
|
/// Defines the window [tabbing identifier] for macOS.
|
||||||
|
///
|
||||||
|
/// Windows with matching tabbing identifiers will be grouped together.
|
||||||
|
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
|
||||||
|
///
|
||||||
|
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[must_use]
|
||||||
|
fn tabbing_identifier(self, identifier: &str) -> Self;
|
||||||
|
|
||||||
|
/// Forces a theme or uses the system settings if None was provided.
|
||||||
|
fn theme(self, theme: Option<Theme>) -> Self;
|
||||||
|
|
||||||
|
/// Whether the icon was set or not.
|
||||||
|
fn has_icon(&self) -> bool;
|
||||||
|
|
||||||
|
fn get_theme(&self) -> Option<Theme>;
|
||||||
|
|
||||||
|
/// Sets custom name for Windows' window class. **Windows only**.
|
||||||
|
#[must_use]
|
||||||
|
fn window_classname<S: Into<String>>(self, window_classname: S) -> Self;
|
||||||
|
|
||||||
|
/// The name of the activity to create for this webview window.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
fn activity_name<S: Into<String>>(self, class_name: S) -> Self;
|
||||||
|
|
||||||
|
/// Sets the name of the activity that is creating this webview window.
|
||||||
|
///
|
||||||
|
/// This is important to determine which stack the activity will belong to.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
fn created_by_activity_name<S: Into<String>>(self, class_name: S) -> Self;
|
||||||
|
|
||||||
|
/// Sets the identifier of the UIScene that is requesting the creation of this new scene,
|
||||||
|
/// establishing a relationship between the two scenes.
|
||||||
|
///
|
||||||
|
/// By default the system uses the foreground scene.
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
fn requested_by_scene_identifier<S: Into<String>>(self, identifier: S) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A window that has yet to be built.
|
||||||
|
pub struct PendingWindow<T: UserEvent, R: Runtime<T>> {
|
||||||
|
/// The label that the window will be named.
|
||||||
|
pub label: String,
|
||||||
|
|
||||||
|
/// The [`WindowBuilder`] that the window will be created with.
|
||||||
|
pub window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
|
||||||
|
|
||||||
|
/// The webview that gets added to the window. Optional in case you want to use child webviews or other window content instead.
|
||||||
|
pub webview: Option<PendingWebview<T, R>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_label_valid(label: &str) -> bool {
|
||||||
|
label
|
||||||
|
.chars()
|
||||||
|
.all(|c| char::is_alphanumeric(c) || c == '-' || c == '/' || c == ':' || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_label_is_valid(label: &str) {
|
||||||
|
assert!(
|
||||||
|
is_label_valid(label),
|
||||||
|
"Window label must include only alphanumeric characters, `-`, `/`, `:` and `_`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> PendingWindow<T, R> {
|
||||||
|
/// Create a new [`PendingWindow`] with a label from the given [`WindowBuilder`].
|
||||||
|
pub fn new(
|
||||||
|
window_builder: <R::WindowDispatcher as WindowDispatch<T>>::WindowBuilder,
|
||||||
|
label: impl Into<String>,
|
||||||
|
) -> crate::Result<Self> {
|
||||||
|
let label = label.into();
|
||||||
|
if !is_label_valid(&label) {
|
||||||
|
Err(crate::Error::InvalidWindowLabel)
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
window_builder,
|
||||||
|
label,
|
||||||
|
webview: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a webview to be created on the window.
|
||||||
|
pub fn set_webview(&mut self, webview: PendingWebview<T, R>) -> &mut Self {
|
||||||
|
self.webview.replace(webview);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifier of a window.
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub struct WindowId(u32);
|
||||||
|
|
||||||
|
impl From<u32> for WindowId {
|
||||||
|
fn from(value: u32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A window that is not yet managed by Tauri.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DetachedWindow<T: UserEvent, R: Runtime<T>> {
|
||||||
|
/// The identifier of the window.
|
||||||
|
pub id: WindowId,
|
||||||
|
/// Name of the window
|
||||||
|
pub label: String,
|
||||||
|
|
||||||
|
/// The [`WindowDispatch`] associated with the window.
|
||||||
|
pub dispatcher: R::WindowDispatcher,
|
||||||
|
|
||||||
|
/// The webview dispatcher in case this window has an attached webview.
|
||||||
|
pub webview: Option<DetachedWindowWebview<T, R>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A detached webview associated with a window.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DetachedWindowWebview<T: UserEvent, R: Runtime<T>> {
|
||||||
|
pub webview: DetachedWebview<T, R>,
|
||||||
|
pub use_https_scheme: bool,
|
||||||
|
/// Whether devtools was enabled in [`crate::webview::WebviewAttributes`]. `Some(false)` disables the inspector.
|
||||||
|
pub devtools: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindowWebview<T, R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
webview: self.webview.clone(),
|
||||||
|
use_https_scheme: self.use_https_scheme,
|
||||||
|
devtools: self.devtools,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Clone for DetachedWindow<T, R> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
id: self.id,
|
||||||
|
label: self.label.clone(),
|
||||||
|
dispatcher: self.dispatcher.clone(),
|
||||||
|
webview: self.webview.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Hash for DetachedWindow<T, R> {
|
||||||
|
/// Only use the [`DetachedWindow`]'s label to represent its hash.
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.label.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> Eq for DetachedWindow<T, R> {}
|
||||||
|
impl<T: UserEvent, R: Runtime<T>> PartialEq for DetachedWindow<T, R> {
|
||||||
|
/// Only use the [`DetachedWindow`]'s label to compare equality.
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.label.eq(&other.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A raw window type that contains fields to access
|
||||||
|
/// the HWND on Windows, gtk::ApplicationWindow on Linux
|
||||||
|
pub struct RawWindow<'a> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub hwnd: isize,
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
pub gtk_window: &'a gtk::ApplicationWindow,
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "openbsd"
|
||||||
|
))]
|
||||||
|
pub default_vbox: Option<&'a gtk::Box>,
|
||||||
|
pub _marker: &'a PhantomData<()>,
|
||||||
|
}
|
||||||
1035
apps/tauri/src-tauri/vendor/tauri-utils/CHANGELOG.md
vendored
Normal file
1035
apps/tauri/src-tauri/vendor/tauri-utils/CHANGELOG.md
vendored
Normal file
File diff suppressed because it is too large
Load diff
94
apps/tauri/src-tauri/vendor/tauri-utils/Cargo.toml
vendored
Normal file
94
apps/tauri/src-tauri/vendor/tauri-utils/Cargo.toml
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
[package]
|
||||||
|
name = "tauri-utils"
|
||||||
|
version = "2.9.0"
|
||||||
|
description = "Utilities for Tauri"
|
||||||
|
exclude = ["CHANGELOG.md", "/target"]
|
||||||
|
readme = "README.md"
|
||||||
|
authors.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
anyhow = "1"
|
||||||
|
thiserror = "2"
|
||||||
|
phf = { version = "0.11", features = ["macros"] }
|
||||||
|
brotli = { version = "8", optional = true, default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
] }
|
||||||
|
url = { version = "2", features = ["serde"] }
|
||||||
|
html5ever = { version = "0.29", optional = true }
|
||||||
|
kuchiki = { package = "kuchikiki", version = "0.8.8-speedreader", optional = true }
|
||||||
|
dom_query = { version = "0.27", optional = true, default-features = false }
|
||||||
|
proc-macro2 = { version = "1", optional = true }
|
||||||
|
quote = { version = "1", optional = true }
|
||||||
|
schemars = { version = "1", features = ["url2", "uuid1"], optional = true }
|
||||||
|
serde_with = "3"
|
||||||
|
aes-gcm = { version = "0.10", optional = true }
|
||||||
|
getrandom = { version = "0.3", optional = true, features = ["std"] }
|
||||||
|
serialize-to-javascript = { version = "0.1.2", optional = true }
|
||||||
|
ctor = { version = "0.8", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"proc_macro",
|
||||||
|
] }
|
||||||
|
json5 = { version = "0.4", optional = true }
|
||||||
|
# Part of public api in error type
|
||||||
|
toml = { version = ">=0.9, <=1", features = ["parse"] }
|
||||||
|
json-patch = "3.0"
|
||||||
|
# Our code requires at least 0.3.1
|
||||||
|
glob = "0.3.1"
|
||||||
|
urlpattern = "0.3"
|
||||||
|
regex = "1"
|
||||||
|
walkdir = { version = "2", optional = true }
|
||||||
|
memchr = "2"
|
||||||
|
semver = "1"
|
||||||
|
infer = "0.19"
|
||||||
|
dunce = "1"
|
||||||
|
log = "0.4.21"
|
||||||
|
cargo_metadata = { version = "0.19", optional = true }
|
||||||
|
serde-untagged = "0.1"
|
||||||
|
uuid = { version = "1", features = ["serde"] }
|
||||||
|
http = "1"
|
||||||
|
plist = "1"
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"macos\")".dependencies]
|
||||||
|
swift-rs = { version = "1", optional = true, features = ["build"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
getrandom = { version = "0.3", features = ["std"] }
|
||||||
|
serial_test = "3"
|
||||||
|
tauri = { path = "../tauri" }
|
||||||
|
tempfile = "3.15.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
build = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"cargo_metadata",
|
||||||
|
"schema",
|
||||||
|
"swift-rs",
|
||||||
|
"html-manipulation",
|
||||||
|
]
|
||||||
|
# Same as `build` but uses `html-manipulation-2` to avoid the `kuchikiki` dependency.
|
||||||
|
build-2 = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"cargo_metadata",
|
||||||
|
"schema",
|
||||||
|
"swift-rs",
|
||||||
|
"html-manipulation-2",
|
||||||
|
]
|
||||||
|
compression = ["brotli"]
|
||||||
|
schema = ["schemars"]
|
||||||
|
isolation = ["aes-gcm", "getrandom", "serialize-to-javascript"]
|
||||||
|
process-relaunch-dangerous-allow-symlink-macos = []
|
||||||
|
config-json5 = ["json5"]
|
||||||
|
config-toml = []
|
||||||
|
resources = ["walkdir"]
|
||||||
|
html-manipulation = ["dep:html5ever", "dep:kuchiki"]
|
||||||
|
html-manipulation-2 = ["dep:dom_query"]
|
||||||
42
apps/tauri/src-tauri/vendor/tauri-utils/README.md
vendored
Normal file
42
apps/tauri/src-tauri/vendor/tauri-utils/README.md
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# tauri-utils
|
||||||
|
|
||||||
|
<img align="right" src="https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png" height="128" width="128">
|
||||||
|
|
||||||
|
[](https://github.com/tauri-apps/tauri/tree/dev)
|
||||||
|
[](https://opencollective.com/tauri)
|
||||||
|
[](https://github.com/tauri-apps/tauri/actions/workflows/test-core.yml)
|
||||||
|
[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftauri-apps%2Ftauri?ref=badge_shield)
|
||||||
|
[](https://discord.gg/SpmNs4S)
|
||||||
|
[](https://tauri.app)
|
||||||
|
[](https://good-labs.github.io/greater-good-affirmation)
|
||||||
|
[](https://opencollective.com/tauri)
|
||||||
|
|
||||||
|
| Component | Version |
|
||||||
|
| ----------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
|
| tauri-utils | [](https://crates.io/crates/tauri-utils) |
|
||||||
|
|
||||||
|
## About Tauri
|
||||||
|
|
||||||
|
Tauri is a polyglot and generic system that is very composable and allows engineers to make a wide variety of applications. It is used for building applications for Desktop Computers using a combination of Rust tools and HTML rendered in a Webview. Apps built with Tauri can ship with any number of pieces of an optional JS API / Rust API so that webviews can control the system via message passing. In fact, developers can extend the default API with their own functionality and bridge the Webview and Rust-based backend easily.
|
||||||
|
|
||||||
|
Tauri apps can have custom menus and have tray-type interfaces. They can be updated, and are managed by the user's operating system as expected. They are very small, because they use the system's webview. They do not ship a runtime, since the final binary is compiled from rust. This makes the reversing of Tauri apps not a trivial task.
|
||||||
|
|
||||||
|
## This module
|
||||||
|
|
||||||
|
This is common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.
|
||||||
|
|
||||||
|
To learn more about the details of how all of these pieces fit together, please consult this [ARCHITECTURE.md](https://github.com/tauri-apps/tauri/blob/dev/ARCHITECTURE.md) document.
|
||||||
|
|
||||||
|
## Semver
|
||||||
|
|
||||||
|
**tauri** is following [Semantic Versioning 2.0](https://semver.org/).
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
Code: (c) 2021 - The Tauri Programme within The Commons Conservancy.
|
||||||
|
|
||||||
|
MIT or MIT/Apache 2.0 where applicable.
|
||||||
|
|
||||||
|
Logo: CC-BY-NC-ND
|
||||||
|
|
||||||
|
- Original Tauri Logo Designs by [Daniel Thompson-Yvetot](https://github.com/nothingismagick) and [Guillaume Chau](https://github.com/akryum)
|
||||||
500
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/build.rs
vendored
Normal file
500
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/build.rs
vendored
Normal file
|
|
@ -0,0 +1,500 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! ACL items that are only useful inside of build script/codegen context.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashMap},
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
acl::{AllowedCommands, Error, has_app_manifest},
|
||||||
|
config::Config,
|
||||||
|
write_if_changed,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ALLOWED_COMMANDS_FILE_NAME, PERMISSION_SCHEMA_FILE_NAME, PERMISSION_SCHEMAS_FOLDER_NAME,
|
||||||
|
REMOVE_UNUSED_COMMANDS_ENV_VAR,
|
||||||
|
capability::{Capability, CapabilityFile},
|
||||||
|
manifest::PermissionFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Known name of the folder containing autogenerated permissions.
|
||||||
|
pub const AUTOGENERATED_FOLDER_NAME: &str = "autogenerated";
|
||||||
|
|
||||||
|
/// Cargo cfg key for permissions file paths
|
||||||
|
pub const PERMISSION_FILES_PATH_KEY: &str = "PERMISSION_FILES_PATH";
|
||||||
|
|
||||||
|
/// Cargo cfg key for global scope schemas
|
||||||
|
pub const GLOBAL_SCOPE_SCHEMA_PATH_KEY: &str = "GLOBAL_SCOPE_SCHEMA_PATH";
|
||||||
|
|
||||||
|
/// Allowed permission file extensions
|
||||||
|
pub const PERMISSION_FILE_EXTENSIONS: &[&str] = &["json", "toml"];
|
||||||
|
|
||||||
|
/// Known filename of the permission documentation file
|
||||||
|
pub const PERMISSION_DOCS_FILE_NAME: &str = "reference.md";
|
||||||
|
|
||||||
|
/// Allowed capability file extensions
|
||||||
|
const CAPABILITY_FILE_EXTENSIONS: &[&str] = &[
|
||||||
|
"json",
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
"json5",
|
||||||
|
"toml",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Known folder name of the capability schemas
|
||||||
|
const CAPABILITIES_SCHEMA_FOLDER_NAME: &str = "schemas";
|
||||||
|
|
||||||
|
const CORE_PLUGIN_PERMISSIONS_TOKEN: &str = "__CORE_PLUGIN__";
|
||||||
|
|
||||||
|
fn parse_permissions(paths: Vec<PathBuf>) -> Result<Vec<PermissionFile>, Error> {
|
||||||
|
let mut permissions = Vec::new();
|
||||||
|
for path in paths {
|
||||||
|
let ext = path.extension().unwrap().to_string_lossy().to_string();
|
||||||
|
let permission_file = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?;
|
||||||
|
let permission: PermissionFile = match ext.as_str() {
|
||||||
|
"toml" => toml::from_str(&permission_file)?,
|
||||||
|
"json" => serde_json::from_str(&permission_file)?,
|
||||||
|
_ => return Err(Error::UnknownPermissionFormat(ext)),
|
||||||
|
};
|
||||||
|
permissions.push(permission);
|
||||||
|
}
|
||||||
|
Ok(permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the permissions to a temporary directory and pass it to the immediate consuming crate.
|
||||||
|
pub fn define_permissions<F: Fn(&Path) -> bool>(
|
||||||
|
pattern: &str,
|
||||||
|
pkg_name: &str,
|
||||||
|
out_dir: &Path,
|
||||||
|
filter_fn: F,
|
||||||
|
) -> Result<Vec<PermissionFile>, Error> {
|
||||||
|
let permission_files = glob::glob(pattern)?
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|p| p.canonicalize())
|
||||||
|
// filter extension
|
||||||
|
.filter(|p| {
|
||||||
|
p.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.map(|e| PERMISSION_FILE_EXTENSIONS.contains(&e))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.filter(|p| filter_fn(p))
|
||||||
|
// filter schemas
|
||||||
|
.filter(|p| p.parent().unwrap().file_name().unwrap() != PERMISSION_SCHEMAS_FOLDER_NAME)
|
||||||
|
.collect::<Vec<PathBuf>>();
|
||||||
|
|
||||||
|
let pkg_name_valid_path = pkg_name.replace(':', "-");
|
||||||
|
let permission_files_path = out_dir.join(format!("{pkg_name_valid_path}-permission-files"));
|
||||||
|
let permission_files_json = serde_json::to_string(&permission_files)?;
|
||||||
|
|
||||||
|
write_if_changed(&permission_files_path, permission_files_json)
|
||||||
|
.map_err(|e| Error::WriteFile(e, permission_files_path.clone()))?;
|
||||||
|
|
||||||
|
if let Some(plugin_name) = pkg_name.strip_prefix("tauri:") {
|
||||||
|
println!(
|
||||||
|
"cargo:{plugin_name}{CORE_PLUGIN_PERMISSIONS_TOKEN}_{PERMISSION_FILES_PATH_KEY}={}",
|
||||||
|
permission_files_path.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"cargo:{PERMISSION_FILES_PATH_KEY}={}",
|
||||||
|
permission_files_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_permissions(permission_files)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read all permissions listed from the defined cargo cfg key value.
|
||||||
|
pub fn read_permissions() -> Result<HashMap<String, Vec<PermissionFile>>, Error> {
|
||||||
|
let mut permissions_map = HashMap::new();
|
||||||
|
|
||||||
|
for (key, value) in env::vars_os() {
|
||||||
|
let key = key.to_string_lossy();
|
||||||
|
|
||||||
|
if let Some(plugin_crate_name_var) = key
|
||||||
|
.strip_prefix("DEP_")
|
||||||
|
.and_then(|v| v.strip_suffix(&format!("_{PERMISSION_FILES_PATH_KEY}")))
|
||||||
|
.map(|v| {
|
||||||
|
v.strip_suffix(CORE_PLUGIN_PERMISSIONS_TOKEN)
|
||||||
|
.and_then(|v| v.strip_prefix("TAURI_"))
|
||||||
|
.unwrap_or(v)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let permissions_path = PathBuf::from(value);
|
||||||
|
let permissions_str =
|
||||||
|
fs::read_to_string(&permissions_path).map_err(|e| Error::ReadFile(e, permissions_path))?;
|
||||||
|
let permissions: Vec<PathBuf> = serde_json::from_str(&permissions_str)?;
|
||||||
|
let permissions = parse_permissions(permissions)?;
|
||||||
|
|
||||||
|
let plugin_crate_name = plugin_crate_name_var.to_lowercase().replace('_', "-");
|
||||||
|
let plugin_crate_name = plugin_crate_name
|
||||||
|
.strip_prefix("tauri-plugin-")
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or(plugin_crate_name);
|
||||||
|
|
||||||
|
permissions_map.insert(plugin_crate_name, permissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(permissions_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define the global scope schema JSON file path if it exists and pass it to the immediate consuming crate.
|
||||||
|
pub fn define_global_scope_schema(
|
||||||
|
schema: schemars::Schema,
|
||||||
|
pkg_name: &str,
|
||||||
|
out_dir: &Path,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let path = out_dir.join("global-scope.json");
|
||||||
|
write_if_changed(&path, serde_json::to_vec(&schema)?)
|
||||||
|
.map_err(|e| Error::WriteFile(e, path.clone()))?;
|
||||||
|
|
||||||
|
if let Some(plugin_name) = pkg_name.strip_prefix("tauri:") {
|
||||||
|
println!(
|
||||||
|
"cargo:{plugin_name}{CORE_PLUGIN_PERMISSIONS_TOKEN}_{GLOBAL_SCOPE_SCHEMA_PATH_KEY}={}",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("cargo:{GLOBAL_SCOPE_SCHEMA_PATH_KEY}={}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read all global scope schemas listed from the defined cargo cfg key value.
|
||||||
|
pub fn read_global_scope_schemas() -> Result<HashMap<String, serde_json::Value>, Error> {
|
||||||
|
let mut schemas_map = HashMap::new();
|
||||||
|
|
||||||
|
for (key, value) in env::vars_os() {
|
||||||
|
let key = key.to_string_lossy();
|
||||||
|
|
||||||
|
if let Some(plugin_crate_name_var) = key
|
||||||
|
.strip_prefix("DEP_")
|
||||||
|
.and_then(|v| v.strip_suffix(&format!("_{GLOBAL_SCOPE_SCHEMA_PATH_KEY}")))
|
||||||
|
.map(|v| {
|
||||||
|
v.strip_suffix(CORE_PLUGIN_PERMISSIONS_TOKEN)
|
||||||
|
.and_then(|v| v.strip_prefix("TAURI_"))
|
||||||
|
.unwrap_or(v)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
let path = PathBuf::from(value);
|
||||||
|
let json = fs::read_to_string(&path).map_err(|e| Error::ReadFile(e, path))?;
|
||||||
|
let schema: serde_json::Value = serde_json::from_str(&json)?;
|
||||||
|
|
||||||
|
let plugin_crate_name = plugin_crate_name_var.to_lowercase().replace('_', "-");
|
||||||
|
let plugin_crate_name = plugin_crate_name
|
||||||
|
.strip_prefix("tauri-plugin-")
|
||||||
|
.map(ToString::to_string)
|
||||||
|
.unwrap_or(plugin_crate_name);
|
||||||
|
|
||||||
|
schemas_map.insert(plugin_crate_name, schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(schemas_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses all capability files with the given glob pattern.
|
||||||
|
pub fn parse_capabilities(pattern: &str) -> Result<BTreeMap<String, Capability>, Error> {
|
||||||
|
let mut capabilities_map = BTreeMap::new();
|
||||||
|
|
||||||
|
for path in glob::glob(pattern)?
|
||||||
|
.flatten() // filter extension
|
||||||
|
.filter(|p| {
|
||||||
|
p.extension()
|
||||||
|
.and_then(|e| e.to_str())
|
||||||
|
.map(|e| CAPABILITY_FILE_EXTENSIONS.contains(&e))
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
// filter schema files
|
||||||
|
// TODO: remove this before stable
|
||||||
|
.filter(|p| p.parent().unwrap().file_name().unwrap() != CAPABILITIES_SCHEMA_FOLDER_NAME)
|
||||||
|
{
|
||||||
|
match CapabilityFile::load(&path)? {
|
||||||
|
CapabilityFile::Capability(capability) => {
|
||||||
|
if capabilities_map.contains_key(&capability.identifier) {
|
||||||
|
return Err(Error::CapabilityAlreadyExists {
|
||||||
|
identifier: capability.identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
capabilities_map.insert(capability.identifier.clone(), capability);
|
||||||
|
}
|
||||||
|
CapabilityFile::List(capabilities) | CapabilityFile::NamedList { capabilities } => {
|
||||||
|
for capability in capabilities {
|
||||||
|
if capabilities_map.contains_key(&capability.identifier) {
|
||||||
|
return Err(Error::CapabilityAlreadyExists {
|
||||||
|
identifier: capability.identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
capabilities_map.insert(capability.identifier.clone(), capability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(capabilities_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Permissions that are generated from commands using [`autogenerate_command_permissions`].
|
||||||
|
pub struct AutogeneratedPermissions {
|
||||||
|
/// The allow permissions generated from commands.
|
||||||
|
pub allowed: Vec<String>,
|
||||||
|
/// The deny permissions generated from commands.
|
||||||
|
pub denied: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Autogenerate permission files for a list of commands.
|
||||||
|
pub fn autogenerate_command_permissions(
|
||||||
|
path: &Path,
|
||||||
|
commands: &[&str],
|
||||||
|
license_header: &str,
|
||||||
|
schema_ref: bool,
|
||||||
|
) -> AutogeneratedPermissions {
|
||||||
|
if !path.exists() {
|
||||||
|
fs::create_dir_all(path).expect("unable to create autogenerated commands dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema_entry = if schema_ref {
|
||||||
|
let cwd = env::current_dir().unwrap();
|
||||||
|
let components_len = path.strip_prefix(&cwd).unwrap_or(path).components().count();
|
||||||
|
let schema_path = (1..components_len)
|
||||||
|
.map(|_| "..")
|
||||||
|
.collect::<PathBuf>()
|
||||||
|
.join(PERMISSION_SCHEMAS_FOLDER_NAME)
|
||||||
|
.join(PERMISSION_SCHEMA_FILE_NAME);
|
||||||
|
format!(
|
||||||
|
"\n\"$schema\" = \"{}\"\n",
|
||||||
|
dunce::simplified(&schema_path)
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
.replace('\\', "/")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut autogenerated = AutogeneratedPermissions {
|
||||||
|
allowed: Vec::new(),
|
||||||
|
denied: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
for command in commands {
|
||||||
|
let slugified_command = command.replace('_', "-");
|
||||||
|
|
||||||
|
let toml = format!(
|
||||||
|
r###"{license_header}# Automatically generated - DO NOT EDIT!
|
||||||
|
{schema_entry}
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-{slugified_command}"
|
||||||
|
description = "Enables the {command} command without any pre-configured scope."
|
||||||
|
commands.allow = ["{command}"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-{slugified_command}"
|
||||||
|
description = "Denies the {command} command without any pre-configured scope."
|
||||||
|
commands.deny = ["{command}"]
|
||||||
|
"###,
|
||||||
|
);
|
||||||
|
|
||||||
|
let out_path = path.join(format!("{command}.toml"));
|
||||||
|
write_if_changed(&out_path, toml)
|
||||||
|
.unwrap_or_else(|_| panic!("unable to autogenerate {out_path:?}"));
|
||||||
|
|
||||||
|
autogenerated
|
||||||
|
.allowed
|
||||||
|
.push(format!("allow-{slugified_command}"));
|
||||||
|
autogenerated
|
||||||
|
.denied
|
||||||
|
.push(format!("deny-{slugified_command}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
autogenerated
|
||||||
|
}
|
||||||
|
|
||||||
|
const PERMISSION_TABLE_HEADER: &str =
|
||||||
|
"## Permission Table\n\n<table>\n<tr>\n<th>Identifier</th>\n<th>Description</th>\n</tr>\n";
|
||||||
|
|
||||||
|
/// Generate a markdown documentation page containing the list of permissions of the plugin.
|
||||||
|
pub fn generate_docs(
|
||||||
|
permissions: &[PermissionFile],
|
||||||
|
out_dir: &Path,
|
||||||
|
plugin_identifier: &str,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut default_permission = "".to_owned();
|
||||||
|
let mut permission_table = "".to_string();
|
||||||
|
|
||||||
|
fn docs_from(id: &str, description: Option<&str>, plugin_identifier: &str) -> String {
|
||||||
|
let mut docs = format!("\n<tr>\n<td>\n\n`{plugin_identifier}:{id}`\n\n</td>\n");
|
||||||
|
if let Some(d) = description {
|
||||||
|
docs.push_str(&format!("<td>\n\n{d}\n\n</td>"));
|
||||||
|
}
|
||||||
|
docs.push_str("\n</tr>");
|
||||||
|
docs
|
||||||
|
}
|
||||||
|
|
||||||
|
for permission in permissions {
|
||||||
|
for set in &permission.set {
|
||||||
|
permission_table.push_str(&docs_from(
|
||||||
|
&set.identifier,
|
||||||
|
Some(&set.description),
|
||||||
|
plugin_identifier,
|
||||||
|
));
|
||||||
|
permission_table.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = &permission.default {
|
||||||
|
default_permission.push_str("## Default Permission\n\n");
|
||||||
|
default_permission.push_str(default.description.as_deref().unwrap_or_default().trim());
|
||||||
|
default_permission.push('\n');
|
||||||
|
default_permission.push('\n');
|
||||||
|
if !default.permissions.is_empty() {
|
||||||
|
default_permission.push_str("#### This default permission set includes the following:\n\n");
|
||||||
|
for permission in &default.permissions {
|
||||||
|
default_permission.push_str(&format!("- `{permission}`\n"));
|
||||||
|
}
|
||||||
|
default_permission.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for permission in &permission.permission {
|
||||||
|
permission_table.push_str(&docs_from(
|
||||||
|
&permission.identifier,
|
||||||
|
permission.description.as_deref(),
|
||||||
|
plugin_identifier,
|
||||||
|
));
|
||||||
|
permission_table.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let docs = format!("{default_permission}{PERMISSION_TABLE_HEADER}\n{permission_table}</table>\n");
|
||||||
|
|
||||||
|
let reference_path = out_dir.join(PERMISSION_DOCS_FILE_NAME);
|
||||||
|
write_if_changed(&reference_path, docs).map_err(|e| Error::WriteFile(e, reference_path))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We have way too many duplicated code around getting the config files, e.g.
|
||||||
|
// - crates/tauri-codegen/src/lib.rs (`get_config`)
|
||||||
|
// - crates/tauri-build/src/lib.rs (`try_build`)
|
||||||
|
// - crates/tauri-cli/src/helpers/config.rs (`get_internal`)
|
||||||
|
/// Generate allowed commands file for the `generate_handler` macro to remove never allowed commands
|
||||||
|
pub fn generate_allowed_commands(
|
||||||
|
out_dir: &Path,
|
||||||
|
capabilities_from_files: Option<BTreeMap<String, Capability>>,
|
||||||
|
permissions_map: BTreeMap<String, Vec<PermissionFile>>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
println!("cargo:rerun-if-env-changed={REMOVE_UNUSED_COMMANDS_ENV_VAR}");
|
||||||
|
|
||||||
|
let allowed_commands_file_path = out_dir.join(ALLOWED_COMMANDS_FILE_NAME);
|
||||||
|
|
||||||
|
let remove_unused_commands_env_var = std::env::var(REMOVE_UNUSED_COMMANDS_ENV_VAR);
|
||||||
|
|
||||||
|
let should_generate_allowed_commands =
|
||||||
|
remove_unused_commands_env_var.is_ok() && !permissions_map.is_empty();
|
||||||
|
|
||||||
|
if !should_generate_allowed_commands {
|
||||||
|
let _ = std::fs::remove_file(allowed_commands_file_path);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's safe to `unwrap` here since we have checked if the result is ok above
|
||||||
|
let config_directory = PathBuf::from(remove_unused_commands_env_var.unwrap());
|
||||||
|
let capabilities_path = config_directory.join("capabilities");
|
||||||
|
// Cargo re-builds if the variable points to an empty path,
|
||||||
|
// so we check for exists here
|
||||||
|
// see https://github.com/rust-lang/cargo/issues/4213
|
||||||
|
if capabilities_path.exists() {
|
||||||
|
println!("cargo:rerun-if-changed={}", capabilities_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_triple = env::var("TARGET")?;
|
||||||
|
let target = crate::platform::Target::from_triple(&target_triple);
|
||||||
|
let (mut config, config_paths) = crate::config::parse::read_from(target, &config_directory)?;
|
||||||
|
|
||||||
|
for config_file_path in config_paths {
|
||||||
|
println!("cargo:rerun-if-changed={}", config_file_path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(env) = std::env::var("TAURI_CONFIG") {
|
||||||
|
let merge_config: serde_json::Value = serde_json::from_str(&env)?;
|
||||||
|
json_patch::merge(&mut config, &merge_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-env-changed=TAURI_CONFIG");
|
||||||
|
|
||||||
|
// Set working directory to where `tauri.config.json` is, so that relative paths in it are parsed correctly.
|
||||||
|
let old_cwd = std::env::current_dir()?;
|
||||||
|
std::env::set_current_dir(config_directory)?;
|
||||||
|
|
||||||
|
let config: Config = serde_json::from_value(config)?;
|
||||||
|
|
||||||
|
// Reset working directory.
|
||||||
|
std::env::set_current_dir(old_cwd)?;
|
||||||
|
|
||||||
|
let acl: BTreeMap<String, crate::acl::manifest::Manifest> = permissions_map
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, permissions)| {
|
||||||
|
let key = key
|
||||||
|
.strip_prefix("tauri-plugin-")
|
||||||
|
.unwrap_or(&key)
|
||||||
|
.to_string();
|
||||||
|
let manifest = crate::acl::manifest::Manifest::new(permissions, None);
|
||||||
|
(key, manifest)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let capabilities_from_files = if let Some(capabilities) = capabilities_from_files {
|
||||||
|
capabilities
|
||||||
|
} else {
|
||||||
|
crate::acl::build::parse_capabilities(&format!(
|
||||||
|
"{}/**/*",
|
||||||
|
glob::Pattern::escape(&capabilities_path.to_string_lossy())
|
||||||
|
))?
|
||||||
|
};
|
||||||
|
let capabilities = crate::acl::get_capabilities(&config, capabilities_from_files, None)?;
|
||||||
|
|
||||||
|
let permission_entries = capabilities
|
||||||
|
.into_values()
|
||||||
|
.flat_map(|capabilities| capabilities.permissions);
|
||||||
|
let mut allowed_commands = AllowedCommands {
|
||||||
|
has_app_acl: has_app_manifest(&acl),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for permission_entry in permission_entries {
|
||||||
|
let Ok(permissions) =
|
||||||
|
crate::acl::resolved::get_permissions(permission_entry.identifier(), &acl)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for permission in permissions {
|
||||||
|
let plugin_name = permission.key;
|
||||||
|
let allowed_command_names = &permission.permission.commands.allow;
|
||||||
|
for allowed_command in allowed_command_names {
|
||||||
|
let command_name = if plugin_name == crate::acl::APP_ACL_KEY {
|
||||||
|
allowed_command.to_string()
|
||||||
|
} else if let Some(core_plugin_name) = plugin_name.strip_prefix("core:") {
|
||||||
|
format!("plugin:{core_plugin_name}|{allowed_command}")
|
||||||
|
} else {
|
||||||
|
format!("plugin:{plugin_name}|{allowed_command}")
|
||||||
|
};
|
||||||
|
allowed_commands.commands.insert(command_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_if_changed(
|
||||||
|
allowed_commands_file_path,
|
||||||
|
serde_json::to_string(&allowed_commands)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
452
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/capability.rs
vendored
Normal file
452
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/capability.rs
vendored
Normal file
|
|
@ -0,0 +1,452 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! End-user abstraction for selecting permissions a window has access to.
|
||||||
|
|
||||||
|
use std::{path::Path, str::FromStr};
|
||||||
|
|
||||||
|
use crate::{acl::Identifier, platform::Target};
|
||||||
|
use serde::{
|
||||||
|
Deserialize, Deserializer, Serialize,
|
||||||
|
de::{Error, IntoDeserializer},
|
||||||
|
};
|
||||||
|
use serde_untagged::UntaggedEnumVisitor;
|
||||||
|
|
||||||
|
use super::Scopes;
|
||||||
|
|
||||||
|
/// An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`]
|
||||||
|
/// or an object that references a permission and extends its scope.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum PermissionEntry {
|
||||||
|
/// Reference a permission or permission set by identifier.
|
||||||
|
PermissionRef(Identifier),
|
||||||
|
/// Reference a permission or permission set by identifier and extends its scope.
|
||||||
|
ExtendedPermission {
|
||||||
|
/// Identifier of the permission or permission set.
|
||||||
|
identifier: Identifier,
|
||||||
|
/// Scope to append to the existing permission scope.
|
||||||
|
#[serde(default, flatten)]
|
||||||
|
scope: Scopes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PermissionEntry {
|
||||||
|
/// The identifier of the permission referenced in this entry.
|
||||||
|
pub fn identifier(&self) -> &Identifier {
|
||||||
|
match self {
|
||||||
|
Self::PermissionRef(identifier) => identifier,
|
||||||
|
Self::ExtendedPermission {
|
||||||
|
identifier,
|
||||||
|
scope: _,
|
||||||
|
} => identifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for PermissionEntry {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ExtendedPermissionStruct {
|
||||||
|
identifier: Identifier,
|
||||||
|
#[serde(default, flatten)]
|
||||||
|
scope: Scopes,
|
||||||
|
}
|
||||||
|
|
||||||
|
UntaggedEnumVisitor::new()
|
||||||
|
.string(|string| {
|
||||||
|
let de = string.into_deserializer();
|
||||||
|
Identifier::deserialize(de).map(Self::PermissionRef)
|
||||||
|
})
|
||||||
|
.map(|map| {
|
||||||
|
let ext_perm = map.deserialize::<ExtendedPermissionStruct>()?;
|
||||||
|
Ok(Self::ExtendedPermission {
|
||||||
|
identifier: ext_perm.identifier,
|
||||||
|
scope: ext_perm.scope,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A grouping and boundary mechanism developers can use to isolate access to the IPC layer.
|
||||||
|
///
|
||||||
|
/// It controls application windows' and webviews' fine grained access
|
||||||
|
/// to the Tauri core, application, or plugin commands.
|
||||||
|
/// If a webview or its window is not matching any capability then it has no access to the IPC layer at all.
|
||||||
|
///
|
||||||
|
/// This can be done to create groups of windows, based on their required system access, which can reduce
|
||||||
|
/// impact of frontend vulnerabilities in less privileged windows.
|
||||||
|
/// Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`.
|
||||||
|
/// A Window can have none, one, or multiple associated capabilities.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "identifier": "main-user-files-write",
|
||||||
|
/// "description": "This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.",
|
||||||
|
/// "windows": [
|
||||||
|
/// "main"
|
||||||
|
/// ],
|
||||||
|
/// "permissions": [
|
||||||
|
/// "core:default",
|
||||||
|
/// "dialog:open",
|
||||||
|
/// {
|
||||||
|
/// "identifier": "fs:allow-write-text-file",
|
||||||
|
/// "allow": [{ "path": "$HOME/test.txt" }]
|
||||||
|
/// },
|
||||||
|
/// ],
|
||||||
|
/// "platforms": ["macOS","windows"]
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Capability {
|
||||||
|
/// Identifier of the capability.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// `main-user-files-write`
|
||||||
|
///
|
||||||
|
pub identifier: String,
|
||||||
|
/// Description of what the capability is intended to allow on associated windows.
|
||||||
|
///
|
||||||
|
/// It should contain a description of what the grouped permissions should allow.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// This capability allows the `main` window access to `filesystem` write related
|
||||||
|
/// commands and `dialog` commands to enable programmatic access to files selected by the user.
|
||||||
|
#[serde(default)]
|
||||||
|
pub description: String,
|
||||||
|
/// Configure remote URLs that can use the capability permissions.
|
||||||
|
///
|
||||||
|
/// This setting is optional and defaults to not being set, as our
|
||||||
|
/// default use case is that the content is served from our local application.
|
||||||
|
///
|
||||||
|
/// :::caution
|
||||||
|
/// Make sure you understand the security implications of providing remote
|
||||||
|
/// sources with local system access.
|
||||||
|
/// :::
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "urls": ["https://*.mydomain.dev"]
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub remote: Option<CapabilityRemote>,
|
||||||
|
/// Whether this capability is enabled for local app URLs or not. Defaults to `true`.
|
||||||
|
#[serde(default = "default_capability_local")]
|
||||||
|
pub local: bool,
|
||||||
|
/// List of windows that are affected by this capability. Can be a glob pattern.
|
||||||
|
///
|
||||||
|
/// If a window label matches any of the patterns in this list,
|
||||||
|
/// the capability will be enabled on all the webviews of that window,
|
||||||
|
/// regardless of the value of [`Self::webviews`].
|
||||||
|
///
|
||||||
|
/// On multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`]
|
||||||
|
/// for a fine grained access control.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// `["main"]`
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub windows: Vec<String>,
|
||||||
|
/// List of webviews that are affected by this capability. Can be a glob pattern.
|
||||||
|
///
|
||||||
|
/// The capability will be enabled on all the webviews
|
||||||
|
/// whose label matches any of the patterns in this list,
|
||||||
|
/// regardless of whether the webview's window label matches a pattern in [`Self::windows`].
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// `["sub-webview-one", "sub-webview-two"]`
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub webviews: Vec<String>,
|
||||||
|
/// List of permissions attached to this capability.
|
||||||
|
///
|
||||||
|
/// Must include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`.
|
||||||
|
/// For commands directly implemented in the application itself only `${permission-name}`
|
||||||
|
/// is required.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// [
|
||||||
|
/// "core:default",
|
||||||
|
/// "shell:allow-open",
|
||||||
|
/// "dialog:open",
|
||||||
|
/// {
|
||||||
|
/// "identifier": "fs:allow-write-text-file",
|
||||||
|
/// "allow": [{ "path": "$HOME/test.txt" }]
|
||||||
|
/// }
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[cfg_attr(feature = "schema", schemars(schema_with = "unique_permission"))]
|
||||||
|
pub permissions: Vec<PermissionEntry>,
|
||||||
|
/// Limit which target platforms this capability applies to.
|
||||||
|
///
|
||||||
|
/// By default all platforms are targeted.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// `["macOS","windows"]`
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub platforms: Option<Vec<Target>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capability {
|
||||||
|
/// Whether this capability should be active based on the platform target or not.
|
||||||
|
pub fn is_active(&self, target: &Target) -> bool {
|
||||||
|
self
|
||||||
|
.platforms
|
||||||
|
.as_ref()
|
||||||
|
.map(|platforms| platforms.contains(target))
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
fn unique_permission(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||||
|
let items = serde_json::Value::from(generator.subschema_for::<PermissionEntry>());
|
||||||
|
schemars::json_schema!({
|
||||||
|
"type": "array",
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": items
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_capability_local() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration for remote URLs that are associated with the capability.
|
||||||
|
#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CapabilityRemote {
|
||||||
|
/// Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).
|
||||||
|
///
|
||||||
|
/// ## Examples
|
||||||
|
///
|
||||||
|
/// - "https://*.mydomain.dev": allows subdomains of mydomain.dev
|
||||||
|
/// - "https://mydomain.dev/api/*": allows any subpath of mydomain.dev/api
|
||||||
|
pub urls: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capability formats accepted in a capability file.
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[cfg_attr(feature = "schema", schemars(untagged))]
|
||||||
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
pub enum CapabilityFile {
|
||||||
|
/// A single capability.
|
||||||
|
Capability(Capability),
|
||||||
|
/// A list of capabilities.
|
||||||
|
List(Vec<Capability>),
|
||||||
|
/// A list of capabilities.
|
||||||
|
NamedList {
|
||||||
|
/// The list of capabilities.
|
||||||
|
capabilities: Vec<Capability>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CapabilityFile {
|
||||||
|
/// Load the given capability file.
|
||||||
|
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, super::Error> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let capability_file =
|
||||||
|
std::fs::read_to_string(path).map_err(|e| super::Error::ReadFile(e, path.into()))?;
|
||||||
|
let ext = path.extension().unwrap().to_string_lossy().to_string();
|
||||||
|
let file: Self = match ext.as_str() {
|
||||||
|
"toml" => toml::from_str(&capability_file)?,
|
||||||
|
"json" => serde_json::from_str(&capability_file)?,
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
"json5" => json5::from_str(&capability_file)?,
|
||||||
|
_ => return Err(super::Error::UnknownCapabilityFormat(ext)),
|
||||||
|
};
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for CapabilityFile {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
UntaggedEnumVisitor::new()
|
||||||
|
.seq(|seq| seq.deserialize::<Vec<Capability>>().map(Self::List))
|
||||||
|
.map(|map| {
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct CapabilityNamedList {
|
||||||
|
capabilities: Vec<Capability>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: serde_json::Map<String, serde_json::Value> = map.deserialize()?;
|
||||||
|
if value.contains_key("capabilities") {
|
||||||
|
serde_json::from_value::<CapabilityNamedList>(value.into())
|
||||||
|
.map(|named| Self::NamedList {
|
||||||
|
capabilities: named.capabilities,
|
||||||
|
})
|
||||||
|
.map_err(|e| serde_untagged::de::Error::custom(e.to_string()))
|
||||||
|
} else {
|
||||||
|
serde_json::from_value::<Capability>(value.into())
|
||||||
|
.map(Self::Capability)
|
||||||
|
.map_err(|e| serde_untagged::de::Error::custom(e.to_string()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.deserialize(deserializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for CapabilityFile {
|
||||||
|
type Err = super::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
serde_json::from_str(s)
|
||||||
|
.or_else(|_| toml::from_str(s))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use std::convert::identity;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{literal_struct, tokens::*};
|
||||||
|
|
||||||
|
impl ToTokens for CapabilityRemote {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let urls = vec_lit(&self.urls, str_lit);
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::capability::CapabilityRemote,
|
||||||
|
urls
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for PermissionEntry {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let prefix = quote! { ::tauri::utils::acl::capability::PermissionEntry };
|
||||||
|
|
||||||
|
tokens.append_all(match self {
|
||||||
|
Self::PermissionRef(id) => {
|
||||||
|
quote! { #prefix::PermissionRef(#id) }
|
||||||
|
}
|
||||||
|
Self::ExtendedPermission { identifier, scope } => {
|
||||||
|
quote! { #prefix::ExtendedPermission {
|
||||||
|
identifier: #identifier,
|
||||||
|
scope: #scope
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Capability {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let identifier = str_lit(&self.identifier);
|
||||||
|
let description = str_lit(&self.description);
|
||||||
|
let remote = opt_lit(self.remote.as_ref());
|
||||||
|
let local = self.local;
|
||||||
|
let windows = vec_lit(&self.windows, str_lit);
|
||||||
|
let webviews = vec_lit(&self.webviews, str_lit);
|
||||||
|
let permissions = vec_lit(&self.permissions, identity);
|
||||||
|
let platforms = opt_vec_lit(self.platforms.as_ref(), identity);
|
||||||
|
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::capability::Capability,
|
||||||
|
identifier,
|
||||||
|
description,
|
||||||
|
remote,
|
||||||
|
local,
|
||||||
|
windows,
|
||||||
|
webviews,
|
||||||
|
permissions,
|
||||||
|
platforms
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::acl::{Identifier, Scopes};
|
||||||
|
|
||||||
|
use super::{Capability, CapabilityFile, PermissionEntry};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn permission_entry_de() {
|
||||||
|
let identifier = Identifier::try_from("plugin:perm".to_string()).unwrap();
|
||||||
|
let identifier_json = serde_json::to_string(&identifier).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<PermissionEntry>(&identifier_json).unwrap(),
|
||||||
|
PermissionEntry::PermissionRef(identifier.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_value::<PermissionEntry>(serde_json::json!({
|
||||||
|
"identifier": identifier,
|
||||||
|
"allow": [],
|
||||||
|
"deny": null
|
||||||
|
}))
|
||||||
|
.unwrap(),
|
||||||
|
PermissionEntry::ExtendedPermission {
|
||||||
|
identifier,
|
||||||
|
scope: Scopes {
|
||||||
|
allow: Some(vec![]),
|
||||||
|
deny: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn capability_file_de() {
|
||||||
|
let capability = Capability {
|
||||||
|
identifier: "test".into(),
|
||||||
|
description: "".into(),
|
||||||
|
remote: None,
|
||||||
|
local: true,
|
||||||
|
windows: vec![],
|
||||||
|
webviews: vec![],
|
||||||
|
permissions: vec![],
|
||||||
|
platforms: None,
|
||||||
|
};
|
||||||
|
let capability_json = serde_json::to_string(&capability).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<CapabilityFile>(&capability_json).unwrap(),
|
||||||
|
CapabilityFile::Capability(capability.clone())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<CapabilityFile>(&format!("[{capability_json}]")).unwrap(),
|
||||||
|
CapabilityFile::List(vec![capability.clone()])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::from_str::<CapabilityFile>(&format!(
|
||||||
|
"{{ \"capabilities\": [{capability_json}] }}"
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
CapabilityFile::NamedList {
|
||||||
|
capabilities: vec![capability]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
301
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/identifier.rs
vendored
Normal file
301
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/identifier.rs
vendored
Normal file
|
|
@ -0,0 +1,301 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Identifier for plugins.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::num::NonZeroU8;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
const IDENTIFIER_SEPARATOR: u8 = b':';
|
||||||
|
const PLUGIN_PREFIX: &str = "tauri-plugin-";
|
||||||
|
const CORE_PLUGIN_IDENTIFIER_PREFIX: &str = "core:";
|
||||||
|
|
||||||
|
// <https://doc.rust-lang.org/cargo/reference/manifest.html#the-name-field>
|
||||||
|
const MAX_LEN_PREFIX: usize = 64 - PLUGIN_PREFIX.len();
|
||||||
|
const MAX_LEN_BASE: usize = 64;
|
||||||
|
const MAX_LEN_IDENTIFIER: usize = MAX_LEN_PREFIX + 1 + MAX_LEN_BASE;
|
||||||
|
|
||||||
|
/// Plugin identifier.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Identifier {
|
||||||
|
inner: String,
|
||||||
|
separator: Option<NonZeroU8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
impl schemars::JsonSchema for Identifier {
|
||||||
|
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||||
|
"Identifier".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema_id() -> std::borrow::Cow<'static, str> {
|
||||||
|
std::borrow::Cow::Borrowed(concat!(module_path!(), "::Identifier"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||||
|
String::json_schema(generator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for Identifier {
|
||||||
|
#[inline(always)]
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identifier {
|
||||||
|
/// Get the identifier str.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self) -> &str {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the identifier without prefix.
|
||||||
|
pub fn get_base(&self) -> &str {
|
||||||
|
match self.separator_index() {
|
||||||
|
None => self.get(),
|
||||||
|
Some(i) => &self.inner[i + 1..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the prefix of the identifier.
|
||||||
|
pub fn get_prefix(&self) -> Option<&str> {
|
||||||
|
self.separator_index().map(|i| &self.inner[0..i])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the identifier prefix.
|
||||||
|
pub fn set_prefix(&mut self) -> Result<(), ParseIdentifierError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the identifier string and its separator.
|
||||||
|
pub fn into_inner(self) -> (String, Option<NonZeroU8>) {
|
||||||
|
(self.inner, self.separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn separator_index(&self) -> Option<usize> {
|
||||||
|
self.separator.map(|i| i.get() as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ValidByte {
|
||||||
|
Separator,
|
||||||
|
Byte(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValidByte {
|
||||||
|
fn alpha_numeric(byte: u8) -> Option<Self> {
|
||||||
|
byte.is_ascii_alphanumeric().then_some(Self::Byte(byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alpha_numeric_hyphen(byte: u8) -> Option<Self> {
|
||||||
|
(byte.is_ascii_alphanumeric() || byte == b'-').then_some(Self::Byte(byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&self, next: u8) -> Option<ValidByte> {
|
||||||
|
match (self, next) {
|
||||||
|
(ValidByte::Byte(b'-'), IDENTIFIER_SEPARATOR) => None,
|
||||||
|
(ValidByte::Separator, b'-') => None,
|
||||||
|
|
||||||
|
(_, IDENTIFIER_SEPARATOR) => Some(ValidByte::Separator),
|
||||||
|
(ValidByte::Separator, next) => ValidByte::alpha_numeric(next),
|
||||||
|
(ValidByte::Byte(b'-'), next) => ValidByte::alpha_numeric_hyphen(next),
|
||||||
|
(ValidByte::Byte(b'_'), next) => ValidByte::alpha_numeric_hyphen(next),
|
||||||
|
(ValidByte::Byte(_), next) => ValidByte::alpha_numeric_hyphen(next),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that can happen when parsing an identifier.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ParseIdentifierError {
|
||||||
|
/// Identifier start with the plugin prefix.
|
||||||
|
#[error("identifiers cannot start with {}", PLUGIN_PREFIX)]
|
||||||
|
StartsWithTauriPlugin,
|
||||||
|
|
||||||
|
/// Identifier empty.
|
||||||
|
#[error("identifiers cannot be empty")]
|
||||||
|
Empty,
|
||||||
|
|
||||||
|
/// Identifier is too long.
|
||||||
|
#[error("identifiers cannot be longer than {len}, found {0}", len = MAX_LEN_IDENTIFIER)]
|
||||||
|
Humongous(usize),
|
||||||
|
|
||||||
|
/// Identifier is not in a valid format.
|
||||||
|
#[error(
|
||||||
|
"identifiers can only include lowercase ASCII, hyphens which are not leading or trailing, and a single colon if using a prefix"
|
||||||
|
)]
|
||||||
|
InvalidFormat,
|
||||||
|
|
||||||
|
/// Identifier has multiple separators.
|
||||||
|
#[error(
|
||||||
|
"identifiers can only include a single separator '{}'",
|
||||||
|
IDENTIFIER_SEPARATOR
|
||||||
|
)]
|
||||||
|
MultipleSeparators,
|
||||||
|
|
||||||
|
/// Identifier has a trailing hyphen.
|
||||||
|
#[error("identifiers cannot have a trailing hyphen")]
|
||||||
|
TrailingHyphen,
|
||||||
|
|
||||||
|
/// Identifier has a prefix without a base.
|
||||||
|
#[error("identifiers cannot have a prefix without a base")]
|
||||||
|
PrefixWithoutBase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Identifier {
|
||||||
|
type Error = ParseIdentifierError;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
if value.starts_with(PLUGIN_PREFIX) {
|
||||||
|
return Err(Self::Error::StartsWithTauriPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.is_empty() {
|
||||||
|
return Err(Self::Error::Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.len() > MAX_LEN_IDENTIFIER {
|
||||||
|
return Err(Self::Error::Humongous(value.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_core_identifier = value.starts_with(CORE_PLUGIN_IDENTIFIER_PREFIX);
|
||||||
|
|
||||||
|
let mut bytes = value.bytes();
|
||||||
|
|
||||||
|
// grab the first byte only before parsing the rest
|
||||||
|
let mut prev = bytes
|
||||||
|
.next()
|
||||||
|
.and_then(ValidByte::alpha_numeric)
|
||||||
|
.ok_or(Self::Error::InvalidFormat)?;
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut separator = None;
|
||||||
|
for byte in bytes {
|
||||||
|
idx += 1; // we already consumed first item
|
||||||
|
match prev.next(byte) {
|
||||||
|
None => return Err(Self::Error::InvalidFormat),
|
||||||
|
Some(next @ ValidByte::Byte(_)) => prev = next,
|
||||||
|
Some(ValidByte::Separator) => {
|
||||||
|
if separator.is_none() || is_core_identifier {
|
||||||
|
// safe to unwrap because idx starts at 1 and cannot go over MAX_IDENTIFIER_LEN
|
||||||
|
separator = Some(idx.try_into().unwrap());
|
||||||
|
prev = ValidByte::Separator
|
||||||
|
} else {
|
||||||
|
return Err(Self::Error::MultipleSeparators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match prev {
|
||||||
|
// empty base
|
||||||
|
ValidByte::Separator => return Err(Self::Error::PrefixWithoutBase),
|
||||||
|
|
||||||
|
// trailing hyphen
|
||||||
|
ValidByte::Byte(b'-') => return Err(Self::Error::TrailingHyphen),
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: value,
|
||||||
|
separator,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Identifier {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
Self::try_from(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Identifier {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn ident(s: impl Into<String>) -> Result<Identifier, ParseIdentifierError> {
|
||||||
|
Identifier::try_from(s.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn max_len_fits_in_u8() {
|
||||||
|
assert!(MAX_LEN_IDENTIFIER < u8::MAX as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format() {
|
||||||
|
assert!(ident("prefix:base").is_ok());
|
||||||
|
assert!(ident("prefix3:base").is_ok());
|
||||||
|
assert!(ident("preFix:base").is_ok());
|
||||||
|
|
||||||
|
// bad
|
||||||
|
assert!(ident("tauri-plugin-prefix:base").is_err());
|
||||||
|
|
||||||
|
assert!(ident("-prefix-:-base-").is_err());
|
||||||
|
assert!(ident("-prefix:base").is_err());
|
||||||
|
assert!(ident("prefix-:base").is_err());
|
||||||
|
assert!(ident("prefix:-base").is_err());
|
||||||
|
assert!(ident("prefix:base-").is_err());
|
||||||
|
|
||||||
|
assert!(ident("pre--fix:base--sep").is_ok());
|
||||||
|
assert!(ident("prefix:base--sep").is_ok());
|
||||||
|
assert!(ident("pre--fix:base").is_ok());
|
||||||
|
|
||||||
|
assert!(ident("prefix::base").is_err());
|
||||||
|
assert!(ident(":base").is_err());
|
||||||
|
assert!(ident("prefix:").is_err());
|
||||||
|
assert!(ident(":prefix:base:").is_err());
|
||||||
|
assert!(ident("base:").is_err());
|
||||||
|
|
||||||
|
assert!(ident("").is_err());
|
||||||
|
assert!(ident("💩").is_err());
|
||||||
|
|
||||||
|
assert!(ident("a".repeat(MAX_LEN_IDENTIFIER + 1)).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn base() {
|
||||||
|
assert_eq!(ident("prefix:base").unwrap().get_base(), "base");
|
||||||
|
assert_eq!(ident("base").unwrap().get_base(), "base");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefix() {
|
||||||
|
assert_eq!(ident("prefix:base").unwrap().get_prefix(), Some("prefix"));
|
||||||
|
assert_eq!(ident("base").unwrap().get_prefix(), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl ToTokens for Identifier {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let s = self.get();
|
||||||
|
tokens
|
||||||
|
.append_all(quote! { ::tauri::utils::acl::Identifier::try_from(#s.to_string()).unwrap() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
196
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/manifest.rs
vendored
Normal file
196
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/manifest.rs
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Plugin ACL types.
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, num::NonZeroU64};
|
||||||
|
|
||||||
|
use super::{Permission, PermissionSet};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// The default permission set of the plugin.
|
||||||
|
///
|
||||||
|
/// Works similarly to a permission with the "default" identifier.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct DefaultPermission {
|
||||||
|
/// The version of the permission.
|
||||||
|
pub version: Option<NonZeroU64>,
|
||||||
|
|
||||||
|
/// Human-readable description of what the permission does.
|
||||||
|
/// Tauri convention is to use `<h4>` headings in markdown content
|
||||||
|
/// for Tauri documentation generation purposes.
|
||||||
|
pub description: Option<String>,
|
||||||
|
|
||||||
|
/// All permissions this set contains.
|
||||||
|
pub permissions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Permission file that can define a default permission, a set of permissions or a list of inlined permissions.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct PermissionFile {
|
||||||
|
/// The default permission set for the plugin
|
||||||
|
pub default: Option<DefaultPermission>,
|
||||||
|
|
||||||
|
/// A list of permissions sets defined
|
||||||
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub set: Vec<PermissionSet>,
|
||||||
|
|
||||||
|
/// A list of inlined permissions
|
||||||
|
#[serde(default)]
|
||||||
|
pub permission: Vec<Permission>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plugin manifest.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Manifest {
|
||||||
|
/// Default permission.
|
||||||
|
pub default_permission: Option<PermissionSet>,
|
||||||
|
/// Plugin permissions.
|
||||||
|
pub permissions: BTreeMap<String, Permission>,
|
||||||
|
/// Plugin permission sets.
|
||||||
|
pub permission_sets: BTreeMap<String, PermissionSet>,
|
||||||
|
/// The global scope schema.
|
||||||
|
pub global_scope_schema: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manifest {
|
||||||
|
/// Creates a new manifest from the given plugin permission files and global scope schema.
|
||||||
|
pub fn new(
|
||||||
|
permission_files: Vec<PermissionFile>,
|
||||||
|
global_scope_schema: Option<serde_json::Value>,
|
||||||
|
) -> Self {
|
||||||
|
let mut manifest = Self {
|
||||||
|
default_permission: None,
|
||||||
|
permissions: BTreeMap::new(),
|
||||||
|
permission_sets: BTreeMap::new(),
|
||||||
|
global_scope_schema,
|
||||||
|
};
|
||||||
|
|
||||||
|
for permission_file in permission_files {
|
||||||
|
if let Some(default) = permission_file.default {
|
||||||
|
manifest.default_permission.replace(PermissionSet {
|
||||||
|
identifier: "default".into(),
|
||||||
|
description: default
|
||||||
|
.description
|
||||||
|
.unwrap_or_else(|| "Default plugin permissions.".to_string()),
|
||||||
|
permissions: default.permissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for permission in permission_file.permission {
|
||||||
|
let key = permission.identifier.clone();
|
||||||
|
manifest.permissions.insert(key, permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
for set in permission_file.set {
|
||||||
|
let key = set.identifier.clone();
|
||||||
|
manifest.permission_sets.insert(key, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
type ScopeSchema = (schemars::Schema, serde_json::Map<String, serde_json::Value>);
|
||||||
|
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
impl Manifest {
|
||||||
|
/// Return scope schema and extra schema definitions for this plugin manifest.
|
||||||
|
pub fn global_scope_schema(&self) -> Result<Option<ScopeSchema>, super::Error> {
|
||||||
|
self
|
||||||
|
.global_scope_schema
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| {
|
||||||
|
serde_json::from_value::<schemars::Schema>(s.clone()).map(|mut root| {
|
||||||
|
// Extract definitions from the schema
|
||||||
|
let definitions = root
|
||||||
|
.remove("$defs")
|
||||||
|
.or_else(|| root.remove("definitions"))
|
||||||
|
.and_then(|v| match v {
|
||||||
|
serde_json::Value::Object(m) => Some(m),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Wrap in an array schema
|
||||||
|
let items = serde_json::Value::from(root);
|
||||||
|
let scope_schema = schemars::json_schema!({
|
||||||
|
"type": "array",
|
||||||
|
"items": items
|
||||||
|
});
|
||||||
|
|
||||||
|
(scope_schema, definitions)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.transpose()
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
use std::convert::identity;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{literal_struct, tokens::*};
|
||||||
|
|
||||||
|
impl ToTokens for DefaultPermission {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let version = opt_lit_owned(self.version.as_ref().map(|v| {
|
||||||
|
let v = v.get();
|
||||||
|
quote!(::core::num::NonZeroU64::new(#v).unwrap())
|
||||||
|
}));
|
||||||
|
// Only used in build script and macros, so don't include them in runtime
|
||||||
|
let description = quote! { ::core::option::Option::None };
|
||||||
|
let permissions = vec_lit(&self.permissions, str_lit);
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::plugin::DefaultPermission,
|
||||||
|
version,
|
||||||
|
description,
|
||||||
|
permissions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Manifest {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let default_permission = opt_lit(self.default_permission.as_ref());
|
||||||
|
|
||||||
|
let permissions = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.permissions,
|
||||||
|
str_lit,
|
||||||
|
identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
let permission_sets = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.permission_sets,
|
||||||
|
str_lit,
|
||||||
|
identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only used in build script and macros, so don't include them in runtime
|
||||||
|
// let global_scope_schema =
|
||||||
|
// opt_lit_owned(self.global_scope_schema.as_ref().map(json_value_lit));
|
||||||
|
let global_scope_schema = quote! { ::core::option::Option::None };
|
||||||
|
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::manifest::Manifest,
|
||||||
|
default_permission,
|
||||||
|
permissions,
|
||||||
|
permission_sets,
|
||||||
|
global_scope_schema
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
550
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/mod.rs
vendored
Normal file
550
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,550 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Access Control List types.
|
||||||
|
//!
|
||||||
|
//! # Stability
|
||||||
|
//!
|
||||||
|
//! This is a core functionality that is not considered part of the stable API.
|
||||||
|
//! If you use it, note that it may include breaking changes in the future.
|
||||||
|
//!
|
||||||
|
//! These items are intended to be non-breaking from a de/serialization standpoint only.
|
||||||
|
//! Using and modifying existing config values will try to avoid breaking changes, but they are
|
||||||
|
//! free to add fields in the future - causing breaking changes for creating and full destructuring.
|
||||||
|
//!
|
||||||
|
//! To avoid this, [ignore unknown fields when destructuring] with the `{my, config, ..}` pattern.
|
||||||
|
//! If you need to create the Rust config directly without deserializing, then create the struct
|
||||||
|
//! the [Struct Update Syntax] with `..Default::default()`, which may need a
|
||||||
|
//! `#[allow(clippy::needless_update)]` attribute if you are declaring all fields.
|
||||||
|
//!
|
||||||
|
//! [ignore unknown fields when destructuring]: https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-remaining-parts-of-a-value-with-
|
||||||
|
//! [Struct Update Syntax]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#creating-instances-from-other-instances-with-struct-update-syntax
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use capability::{Capability, CapabilityFile};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
fs,
|
||||||
|
num::NonZeroU64,
|
||||||
|
path::PathBuf,
|
||||||
|
str::FromStr,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{CapabilityEntry, Config},
|
||||||
|
platform::Target,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use self::{identifier::*, value::*};
|
||||||
|
|
||||||
|
/// Known foldername of the permission schema files
|
||||||
|
pub const PERMISSION_SCHEMAS_FOLDER_NAME: &str = "schemas";
|
||||||
|
/// Known filename of the permission schema JSON file
|
||||||
|
pub const PERMISSION_SCHEMA_FILE_NAME: &str = "schema.json";
|
||||||
|
/// Known ACL key for the app permissions.
|
||||||
|
pub const APP_ACL_KEY: &str = "__app-acl__";
|
||||||
|
/// Known acl manifests file
|
||||||
|
pub const ACL_MANIFESTS_FILE_NAME: &str = "acl-manifests.json";
|
||||||
|
/// Known capabilities file
|
||||||
|
pub const CAPABILITIES_FILE_NAME: &str = "capabilities.json";
|
||||||
|
/// Allowed commands file name
|
||||||
|
pub const ALLOWED_COMMANDS_FILE_NAME: &str = "allowed-commands.json";
|
||||||
|
/// Set by the CLI with when `build > removeUnusedCommands` is set for dead code elimination,
|
||||||
|
/// the value is set to the config's directory
|
||||||
|
pub const REMOVE_UNUSED_COMMANDS_ENV_VAR: &str = "REMOVE_UNUSED_COMMANDS";
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
pub mod build;
|
||||||
|
pub mod capability;
|
||||||
|
pub mod identifier;
|
||||||
|
pub mod manifest;
|
||||||
|
pub mod resolved;
|
||||||
|
#[cfg(feature = "schema")]
|
||||||
|
pub mod schema;
|
||||||
|
pub mod value;
|
||||||
|
|
||||||
|
/// Possible errors while processing ACL files.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Could not find an environmental variable that is set inside of build scripts.
|
||||||
|
///
|
||||||
|
/// Whatever generated this should be called inside of a build script.
|
||||||
|
#[error(
|
||||||
|
"expected build script env var {0}, but it was not found - ensure this is called in a build script"
|
||||||
|
)]
|
||||||
|
BuildVar(&'static str),
|
||||||
|
|
||||||
|
/// The links field in the manifest **MUST** be set and match the name of the crate.
|
||||||
|
#[error(
|
||||||
|
"package.links field in the Cargo manifest is not set, it should be set to the same as package.name"
|
||||||
|
)]
|
||||||
|
LinksMissing,
|
||||||
|
|
||||||
|
/// The links field in the manifest **MUST** match the name of the crate.
|
||||||
|
#[error(
|
||||||
|
"package.links field in the Cargo manifest MUST be set to the same value as package.name"
|
||||||
|
)]
|
||||||
|
LinksName,
|
||||||
|
|
||||||
|
/// IO error while reading a file
|
||||||
|
#[error("failed to read file '{}': {}", _1.display(), _0)]
|
||||||
|
ReadFile(std::io::Error, PathBuf),
|
||||||
|
|
||||||
|
/// IO error while writing a file
|
||||||
|
#[error("failed to write file '{}': {}", _1.display(), _0)]
|
||||||
|
WriteFile(std::io::Error, PathBuf),
|
||||||
|
|
||||||
|
/// IO error while creating a file
|
||||||
|
#[error("failed to create file '{}': {}", _1.display(), _0)]
|
||||||
|
CreateFile(std::io::Error, PathBuf),
|
||||||
|
|
||||||
|
/// IO error while creating a dir
|
||||||
|
#[error("failed to create dir '{}': {}", _1.display(), _0)]
|
||||||
|
CreateDir(std::io::Error, PathBuf),
|
||||||
|
|
||||||
|
/// [`cargo_metadata`] was not able to complete successfully
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
#[error("failed to execute: {0}")]
|
||||||
|
Metadata(#[from] ::cargo_metadata::Error),
|
||||||
|
|
||||||
|
/// Invalid glob
|
||||||
|
#[error("failed to run glob: {0}")]
|
||||||
|
Glob(#[from] glob::PatternError),
|
||||||
|
|
||||||
|
/// Invalid TOML encountered
|
||||||
|
#[error("failed to parse TOML: {0}")]
|
||||||
|
Toml(#[from] toml::de::Error),
|
||||||
|
|
||||||
|
/// Invalid JSON encountered
|
||||||
|
#[error("failed to parse JSON: {0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
/// Invalid JSON5 encountered
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
#[error("failed to parse JSON5: {0}")]
|
||||||
|
Json5(#[from] json5::Error),
|
||||||
|
|
||||||
|
/// Invalid permissions file format
|
||||||
|
#[error("unknown permission format {0}")]
|
||||||
|
UnknownPermissionFormat(String),
|
||||||
|
|
||||||
|
/// Invalid capabilities file format
|
||||||
|
#[error("unknown capability format {0}")]
|
||||||
|
UnknownCapabilityFormat(String),
|
||||||
|
|
||||||
|
/// Permission referenced in set not found.
|
||||||
|
#[error("permission {permission} not found from set {set}")]
|
||||||
|
SetPermissionNotFound {
|
||||||
|
/// Permission identifier.
|
||||||
|
permission: String,
|
||||||
|
/// Set identifier.
|
||||||
|
set: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Unknown ACL manifest.
|
||||||
|
#[error("unknown ACL for {key}, expected one of {available}")]
|
||||||
|
UnknownManifest {
|
||||||
|
/// Manifest key.
|
||||||
|
key: String,
|
||||||
|
/// Available manifest keys.
|
||||||
|
available: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Unknown permission.
|
||||||
|
#[error("unknown permission {permission} for {key}")]
|
||||||
|
UnknownPermission {
|
||||||
|
/// Manifest key.
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
/// Permission identifier.
|
||||||
|
permission: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Capability with the given identifier already exists.
|
||||||
|
#[error("capability with identifier `{identifier}` already exists")]
|
||||||
|
CapabilityAlreadyExists {
|
||||||
|
/// Capability identifier.
|
||||||
|
identifier: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allowed and denied commands inside a permission.
|
||||||
|
///
|
||||||
|
/// If two commands clash inside of `allow` and `deny`, it should be denied by default.
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Commands {
|
||||||
|
/// Allowed command.
|
||||||
|
#[serde(default)]
|
||||||
|
pub allow: Vec<String>,
|
||||||
|
|
||||||
|
/// Denied command, which takes priority.
|
||||||
|
#[serde(default)]
|
||||||
|
pub deny: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An argument for fine grained behavior control of Tauri commands.
|
||||||
|
///
|
||||||
|
/// It can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command.
|
||||||
|
/// The configured scope is passed to the command and will be enforced by the command implementation.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```json
|
||||||
|
/// {
|
||||||
|
/// "allow": [{ "path": "$HOME/**" }],
|
||||||
|
/// "deny": [{ "path": "$HOME/secret.txt" }]
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Scopes {
|
||||||
|
/// Data that defines what is allowed by the scope.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub allow: Option<Vec<Value>>,
|
||||||
|
/// Data that defines what is denied by the scope. This should be prioritized by validation logic.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub deny: Option<Vec<Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scopes {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.allow.is_none() && self.deny.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Descriptions of explicit privileges of commands.
|
||||||
|
///
|
||||||
|
/// It can enable commands to be accessible in the frontend of the application.
|
||||||
|
///
|
||||||
|
/// If the scope is defined it can be used to fine grain control the access of individual or multiple commands.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct Permission {
|
||||||
|
/// The version of the permission.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub version: Option<NonZeroU64>,
|
||||||
|
|
||||||
|
/// A unique identifier for the permission.
|
||||||
|
pub identifier: String,
|
||||||
|
|
||||||
|
/// Human-readable description of what the permission does.
|
||||||
|
/// Tauri internal convention is to use `<h4>` headings in markdown content
|
||||||
|
/// for Tauri documentation generation purposes.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
|
||||||
|
/// Allowed or denied commands when using this permission.
|
||||||
|
#[serde(default)]
|
||||||
|
pub commands: Commands,
|
||||||
|
|
||||||
|
/// Allowed or denied scoped when using this permission.
|
||||||
|
#[serde(default, skip_serializing_if = "Scopes::is_empty")]
|
||||||
|
pub scope: Scopes,
|
||||||
|
|
||||||
|
/// Target platforms this permission applies. By default all platforms are affected by this permission.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub platforms: Option<Vec<Target>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Permission {
|
||||||
|
/// Whether this permission should be active based on the platform target or not.
|
||||||
|
pub fn is_active(&self, target: &Target) -> bool {
|
||||||
|
self
|
||||||
|
.platforms
|
||||||
|
.as_ref()
|
||||||
|
.map(|platforms| platforms.contains(target))
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of direct permissions grouped together under a new name.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
pub struct PermissionSet {
|
||||||
|
/// A unique identifier for the permission.
|
||||||
|
pub identifier: String,
|
||||||
|
|
||||||
|
/// Human-readable description of what the permission does.
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// All permissions this set contains.
|
||||||
|
pub permissions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UrlPattern for [`ExecutionContext::Remote`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RemoteUrlPattern(Arc<urlpattern::UrlPattern>, String);
|
||||||
|
|
||||||
|
impl FromStr for RemoteUrlPattern {
|
||||||
|
type Err = urlpattern::quirks::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||||
|
let mut init = urlpattern::UrlPatternInit::parse_constructor_string::<regex::Regex>(s, None)?;
|
||||||
|
if init.search.as_ref().map(|p| p.is_empty()).unwrap_or(true) {
|
||||||
|
init.search.replace("*".to_string());
|
||||||
|
}
|
||||||
|
if init.hash.as_ref().map(|p| p.is_empty()).unwrap_or(true) {
|
||||||
|
init.hash.replace("*".to_string());
|
||||||
|
}
|
||||||
|
if init
|
||||||
|
.pathname
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.is_empty() || p == "/")
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
init.pathname.replace("*".to_string());
|
||||||
|
}
|
||||||
|
let pattern = urlpattern::UrlPattern::parse(init, Default::default())?;
|
||||||
|
Ok(Self(Arc::new(pattern), s.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RemoteUrlPattern {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if a given URL matches the pattern.
|
||||||
|
pub fn test(&self, url: &Url) -> bool {
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.test(urlpattern::UrlPatternMatchInput::Url(url.clone()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for RemoteUrlPattern {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.protocol() == other.0.protocol()
|
||||||
|
&& self.0.username() == other.0.username()
|
||||||
|
&& self.0.password() == other.0.password()
|
||||||
|
&& self.0.hostname() == other.0.hostname()
|
||||||
|
&& self.0.port() == other.0.port()
|
||||||
|
&& self.0.pathname() == other.0.pathname()
|
||||||
|
&& self.0.search() == other.0.search()
|
||||||
|
&& self.0.hash() == other.0.hash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for RemoteUrlPattern {}
|
||||||
|
|
||||||
|
/// Execution context of an IPC call.
|
||||||
|
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||||
|
pub enum ExecutionContext {
|
||||||
|
/// A local URL is used (the Tauri app URL).
|
||||||
|
#[default]
|
||||||
|
Local,
|
||||||
|
/// Remote URL is trying to use the IPC.
|
||||||
|
Remote {
|
||||||
|
/// The URL trying to access the IPC (URL pattern).
|
||||||
|
url: RemoteUrlPattern,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if the app has an application manifest from the ACL
|
||||||
|
pub fn has_app_manifest(acl: &BTreeMap<String, crate::acl::manifest::Manifest>) -> bool {
|
||||||
|
acl.contains_key(APP_ACL_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the capabilities from the config file
|
||||||
|
pub fn get_capabilities(
|
||||||
|
config: &Config,
|
||||||
|
mut capabilities_from_files: BTreeMap<String, Capability>,
|
||||||
|
additional_capability_files: Option<&[PathBuf]>,
|
||||||
|
) -> anyhow::Result<BTreeMap<String, Capability>> {
|
||||||
|
let mut capabilities = if config.app.security.capabilities.is_empty() {
|
||||||
|
capabilities_from_files
|
||||||
|
} else {
|
||||||
|
let mut capabilities = BTreeMap::new();
|
||||||
|
for capability_entry in &config.app.security.capabilities {
|
||||||
|
match capability_entry {
|
||||||
|
CapabilityEntry::Inlined(capability) => {
|
||||||
|
capabilities.insert(capability.identifier.clone(), capability.clone());
|
||||||
|
}
|
||||||
|
CapabilityEntry::Reference(id) => {
|
||||||
|
let capability = capabilities_from_files
|
||||||
|
.remove(id)
|
||||||
|
.with_context(|| format!("capability with identifier {id} not found"))?;
|
||||||
|
capabilities.insert(id.clone(), capability);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
capabilities
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(paths) = additional_capability_files {
|
||||||
|
for path in paths {
|
||||||
|
let capability = CapabilityFile::load(path)
|
||||||
|
.with_context(|| format!("failed to read capability {}", path.display()))?;
|
||||||
|
match capability {
|
||||||
|
CapabilityFile::Capability(c) => {
|
||||||
|
capabilities.insert(c.identifier.clone(), c);
|
||||||
|
}
|
||||||
|
CapabilityFile::List(capabilities_list)
|
||||||
|
| CapabilityFile::NamedList {
|
||||||
|
capabilities: capabilities_list,
|
||||||
|
} => {
|
||||||
|
capabilities.extend(
|
||||||
|
capabilities_list
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| (c.identifier.clone(), c)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(capabilities)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allowed commands used to communicate between `generate_handle` and `generate_allowed_commands` through json files
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct AllowedCommands {
|
||||||
|
/// The commands allowed
|
||||||
|
pub commands: HashSet<String>,
|
||||||
|
/// Has application ACL or not
|
||||||
|
pub has_app_acl: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to reads allowed commands from the out dir made by our build script
|
||||||
|
pub fn read_allowed_commands() -> Option<AllowedCommands> {
|
||||||
|
let out_file = std::env::var("OUT_DIR")
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok()?
|
||||||
|
.join(ALLOWED_COMMANDS_FILE_NAME);
|
||||||
|
let file = fs::read_to_string(&out_file).ok()?;
|
||||||
|
let json = serde_json::from_str(&file).ok()?;
|
||||||
|
Some(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::acl::RemoteUrlPattern;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_pattern_domain_wildcard() {
|
||||||
|
let pattern: RemoteUrlPattern = "http://*".parse().unwrap();
|
||||||
|
|
||||||
|
assert!(pattern.test(&"http://tauri.app/path".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"http://tauri.app/path?q=1".parse().unwrap()));
|
||||||
|
|
||||||
|
assert!(pattern.test(&"http://localhost/path".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"http://localhost/path?q=1".parse().unwrap()));
|
||||||
|
|
||||||
|
let pattern: RemoteUrlPattern = "http://*.tauri.app".parse().unwrap();
|
||||||
|
|
||||||
|
assert!(!pattern.test(&"http://tauri.app/path".parse().unwrap()));
|
||||||
|
assert!(!pattern.test(&"http://tauri.app/path?q=1".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"http://api.tauri.app/path".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"http://api.tauri.app/path?q=1".parse().unwrap()));
|
||||||
|
assert!(!pattern.test(&"http://localhost/path".parse().unwrap()));
|
||||||
|
assert!(!pattern.test(&"http://localhost/path?q=1".parse().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_pattern_path_wildcard() {
|
||||||
|
let pattern: RemoteUrlPattern = "http://localhost/*".parse().unwrap();
|
||||||
|
assert!(pattern.test(&"http://localhost/path".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"http://localhost/path?q=1".parse().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_pattern_scheme_wildcard() {
|
||||||
|
let pattern: RemoteUrlPattern = "*://localhost".parse().unwrap();
|
||||||
|
assert!(pattern.test(&"http://localhost/path".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"https://localhost/path?q=1".parse().unwrap()));
|
||||||
|
assert!(pattern.test(&"custom://localhost/path".parse().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build_ {
|
||||||
|
use std::convert::identity;
|
||||||
|
|
||||||
|
use crate::{literal_struct, tokens::*};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
|
||||||
|
impl ToTokens for ExecutionContext {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let prefix = quote! { ::tauri::utils::acl::ExecutionContext };
|
||||||
|
|
||||||
|
tokens.append_all(match self {
|
||||||
|
Self::Local => {
|
||||||
|
quote! { #prefix::Local }
|
||||||
|
}
|
||||||
|
Self::Remote { url } => {
|
||||||
|
let url = url.as_str();
|
||||||
|
quote! { #prefix::Remote { url: #url.parse().unwrap() } }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Commands {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let allow = vec_lit(&self.allow, str_lit);
|
||||||
|
let deny = vec_lit(&self.deny, str_lit);
|
||||||
|
literal_struct!(tokens, ::tauri::utils::acl::Commands, allow, deny)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Scopes {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let allow = opt_vec_lit(self.allow.as_ref(), identity);
|
||||||
|
let deny = opt_vec_lit(self.deny.as_ref(), identity);
|
||||||
|
literal_struct!(tokens, ::tauri::utils::acl::Scopes, allow, deny)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Permission {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let version = opt_lit_owned(self.version.as_ref().map(|v| {
|
||||||
|
let v = v.get();
|
||||||
|
quote!(::core::num::NonZeroU64::new(#v).unwrap())
|
||||||
|
}));
|
||||||
|
let identifier = str_lit(&self.identifier);
|
||||||
|
// Only used in build script and macros, so don't include them in runtime
|
||||||
|
let description = quote! { ::core::option::Option::None };
|
||||||
|
let commands = &self.commands;
|
||||||
|
let scope = &self.scope;
|
||||||
|
let platforms = opt_vec_lit(self.platforms.as_ref(), identity);
|
||||||
|
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::Permission,
|
||||||
|
version,
|
||||||
|
identifier,
|
||||||
|
description,
|
||||||
|
commands,
|
||||||
|
scope,
|
||||||
|
platforms
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for PermissionSet {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let identifier = str_lit(&self.identifier);
|
||||||
|
// Only used in build script and macros, so don't include them in runtime
|
||||||
|
let description = quote! { "".to_string() };
|
||||||
|
let permissions = vec_lit(&self.permissions, str_lit);
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::PermissionSet,
|
||||||
|
identifier,
|
||||||
|
description,
|
||||||
|
permissions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
683
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/resolved.rs
vendored
Normal file
683
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/resolved.rs
vendored
Normal file
|
|
@ -0,0 +1,683 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Resolved ACL for runtime usage.
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, fmt};
|
||||||
|
|
||||||
|
use crate::platform::Target;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
APP_ACL_KEY, Commands, Error, ExecutionContext, Identifier, Permission, PermissionSet, Scopes,
|
||||||
|
Value,
|
||||||
|
capability::{Capability, PermissionEntry},
|
||||||
|
has_app_manifest,
|
||||||
|
manifest::Manifest,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A key for a scope, used to link a [`ResolvedCommand#structfield.scope`] to the store [`Resolved#structfield.scopes`].
|
||||||
|
pub type ScopeKey = u64;
|
||||||
|
|
||||||
|
/// Metadata for what referenced a [`ResolvedCommand`].
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ResolvedCommandReference {
|
||||||
|
/// Identifier of the capability.
|
||||||
|
pub capability: String,
|
||||||
|
/// Identifier of the permission.
|
||||||
|
pub permission: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resolved command permission.
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ResolvedCommand {
|
||||||
|
/// The execution context of this command.
|
||||||
|
pub context: ExecutionContext,
|
||||||
|
/// The capability/permission that referenced this command.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub referenced_by: ResolvedCommandReference,
|
||||||
|
/// The list of window label patterns that was resolved for this command.
|
||||||
|
pub windows: Vec<glob::Pattern>,
|
||||||
|
/// The list of webview label patterns that was resolved for this command.
|
||||||
|
pub webviews: Vec<glob::Pattern>,
|
||||||
|
/// The reference of the scope that is associated with this command. See [`Resolved#structfield.command_scopes`].
|
||||||
|
pub scope_id: Option<ScopeKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ResolvedCommand {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ResolvedCommand")
|
||||||
|
.field("context", &self.context)
|
||||||
|
.field("windows", &self.windows)
|
||||||
|
.field("webviews", &self.webviews)
|
||||||
|
.field("scope_id", &self.scope_id)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resolved scope. Merges all scopes defined for a single command.
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub struct ResolvedScope {
|
||||||
|
/// Allows something on the command.
|
||||||
|
pub allow: Vec<Value>,
|
||||||
|
/// Denies something on the command.
|
||||||
|
pub deny: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolved access control list.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Resolved {
|
||||||
|
/// If we should check the ACL for the app commands
|
||||||
|
pub has_app_acl: bool,
|
||||||
|
/// The commands that are allowed. Map each command with its context to a [`ResolvedCommand`].
|
||||||
|
pub allowed_commands: BTreeMap<String, Vec<ResolvedCommand>>,
|
||||||
|
/// The commands that are denied. Map each command with its context to a [`ResolvedCommand`].
|
||||||
|
pub denied_commands: BTreeMap<String, Vec<ResolvedCommand>>,
|
||||||
|
/// The store of scopes referenced by a [`ResolvedCommand`].
|
||||||
|
pub command_scope: BTreeMap<ScopeKey, ResolvedScope>,
|
||||||
|
/// The global scope.
|
||||||
|
pub global_scope: BTreeMap<String, ResolvedScope>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolved {
|
||||||
|
/// Resolves the ACL for the given plugin permissions and app capabilities.
|
||||||
|
pub fn resolve(
|
||||||
|
acl: &BTreeMap<String, Manifest>,
|
||||||
|
mut capabilities: BTreeMap<String, Capability>,
|
||||||
|
target: Target,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let mut allowed_commands = BTreeMap::new();
|
||||||
|
let mut denied_commands = BTreeMap::new();
|
||||||
|
|
||||||
|
let mut current_scope_id = 0;
|
||||||
|
let mut command_scope = BTreeMap::new();
|
||||||
|
let mut global_scope: BTreeMap<String, Vec<Scopes>> = BTreeMap::new();
|
||||||
|
|
||||||
|
// resolve commands
|
||||||
|
for capability in capabilities.values_mut().filter(|c| c.is_active(&target)) {
|
||||||
|
with_resolved_permissions(
|
||||||
|
capability,
|
||||||
|
acl,
|
||||||
|
target,
|
||||||
|
|ResolvedPermission {
|
||||||
|
key,
|
||||||
|
commands,
|
||||||
|
scope,
|
||||||
|
#[cfg_attr(not(debug_assertions), allow(unused))]
|
||||||
|
permission_name,
|
||||||
|
}| {
|
||||||
|
if commands.allow.is_empty() && commands.deny.is_empty() {
|
||||||
|
// global scope
|
||||||
|
global_scope.entry(key.to_string()).or_default().push(scope);
|
||||||
|
} else {
|
||||||
|
let scope_id = if scope.allow.is_some() || scope.deny.is_some() {
|
||||||
|
current_scope_id += 1;
|
||||||
|
command_scope.insert(
|
||||||
|
current_scope_id,
|
||||||
|
ResolvedScope {
|
||||||
|
allow: scope.allow.unwrap_or_default(),
|
||||||
|
deny: scope.deny.unwrap_or_default(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Some(current_scope_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
for allowed_command in &commands.allow {
|
||||||
|
resolve_command(
|
||||||
|
&mut allowed_commands,
|
||||||
|
if key == APP_ACL_KEY {
|
||||||
|
allowed_command.to_string()
|
||||||
|
} else if let Some(core_plugin_name) = key.strip_prefix("core:") {
|
||||||
|
format!("plugin:{core_plugin_name}|{allowed_command}")
|
||||||
|
} else {
|
||||||
|
format!("plugin:{key}|{allowed_command}")
|
||||||
|
},
|
||||||
|
capability,
|
||||||
|
scope_id,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
permission_name.to_string(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for denied_command in &commands.deny {
|
||||||
|
resolve_command(
|
||||||
|
&mut denied_commands,
|
||||||
|
if key == APP_ACL_KEY {
|
||||||
|
denied_command.to_string()
|
||||||
|
} else if let Some(core_plugin_name) = key.strip_prefix("core:") {
|
||||||
|
format!("plugin:{core_plugin_name}|{denied_command}")
|
||||||
|
} else {
|
||||||
|
format!("plugin:{key}|{denied_command}")
|
||||||
|
},
|
||||||
|
capability,
|
||||||
|
scope_id,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
permission_name.to_string(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global_scope = global_scope
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, scopes)| {
|
||||||
|
let mut resolved_scope = ResolvedScope {
|
||||||
|
allow: Vec::new(),
|
||||||
|
deny: Vec::new(),
|
||||||
|
};
|
||||||
|
for scope in scopes {
|
||||||
|
if let Some(allow) = scope.allow {
|
||||||
|
resolved_scope.allow.extend(allow);
|
||||||
|
}
|
||||||
|
if let Some(deny) = scope.deny {
|
||||||
|
resolved_scope.deny.extend(deny);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(key, resolved_scope)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let resolved = Self {
|
||||||
|
has_app_acl: has_app_manifest(acl),
|
||||||
|
allowed_commands,
|
||||||
|
denied_commands,
|
||||||
|
command_scope,
|
||||||
|
global_scope,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_glob_patterns(mut raw: Vec<String>) -> Result<Vec<glob::Pattern>, Error> {
|
||||||
|
raw.sort();
|
||||||
|
|
||||||
|
let mut patterns = Vec::new();
|
||||||
|
for pattern in raw {
|
||||||
|
patterns.push(glob::Pattern::new(&pattern)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(patterns)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_command(
|
||||||
|
commands: &mut BTreeMap<String, Vec<ResolvedCommand>>,
|
||||||
|
command: String,
|
||||||
|
capability: &Capability,
|
||||||
|
scope_id: Option<ScopeKey>,
|
||||||
|
#[cfg(debug_assertions)] referenced_by_permission_identifier: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut contexts = Vec::new();
|
||||||
|
if capability.local {
|
||||||
|
contexts.push(ExecutionContext::Local);
|
||||||
|
}
|
||||||
|
if let Some(remote) = &capability.remote {
|
||||||
|
contexts.extend(remote.urls.iter().map(|url| {
|
||||||
|
ExecutionContext::Remote {
|
||||||
|
url: url
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|e| panic!("invalid URL pattern for remote URL {url}: {e}")),
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for context in contexts {
|
||||||
|
let resolved_list = commands.entry(command.clone()).or_default();
|
||||||
|
|
||||||
|
resolved_list.push(ResolvedCommand {
|
||||||
|
context,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
referenced_by: ResolvedCommandReference {
|
||||||
|
capability: capability.identifier.clone(),
|
||||||
|
permission: referenced_by_permission_identifier.clone(),
|
||||||
|
},
|
||||||
|
windows: parse_glob_patterns(capability.windows.clone())?,
|
||||||
|
webviews: parse_glob_patterns(capability.webviews.clone())?,
|
||||||
|
scope_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ResolvedPermission<'a> {
|
||||||
|
key: &'a str,
|
||||||
|
permission_name: &'a str,
|
||||||
|
commands: Commands,
|
||||||
|
scope: Scopes,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over permissions in a capability, resolving permission sets if necessary
|
||||||
|
/// to produce a [`ResolvedPermission`] and calling the provided callback with it.
|
||||||
|
fn with_resolved_permissions<F: FnMut(ResolvedPermission<'_>) -> Result<(), Error>>(
|
||||||
|
capability: &Capability,
|
||||||
|
acl: &BTreeMap<String, Manifest>,
|
||||||
|
target: Target,
|
||||||
|
mut f: F,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
for permission_entry in &capability.permissions {
|
||||||
|
let permission_id = permission_entry.identifier();
|
||||||
|
|
||||||
|
let permissions = get_permissions(permission_id, acl)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| p.permission.is_active(&target));
|
||||||
|
|
||||||
|
for TraversedPermission {
|
||||||
|
key,
|
||||||
|
permission_name,
|
||||||
|
permission,
|
||||||
|
} in permissions
|
||||||
|
{
|
||||||
|
let mut resolved_scope = Scopes::default();
|
||||||
|
let mut commands = Commands::default();
|
||||||
|
|
||||||
|
if let PermissionEntry::ExtendedPermission {
|
||||||
|
identifier: _,
|
||||||
|
scope,
|
||||||
|
} = permission_entry
|
||||||
|
{
|
||||||
|
if let Some(allow) = scope.allow.clone() {
|
||||||
|
resolved_scope
|
||||||
|
.allow
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(allow);
|
||||||
|
}
|
||||||
|
if let Some(deny) = scope.deny.clone() {
|
||||||
|
resolved_scope
|
||||||
|
.deny
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(deny);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(allow) = permission.scope.allow.clone() {
|
||||||
|
resolved_scope
|
||||||
|
.allow
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(allow);
|
||||||
|
}
|
||||||
|
if let Some(deny) = permission.scope.deny.clone() {
|
||||||
|
resolved_scope
|
||||||
|
.deny
|
||||||
|
.get_or_insert_with(Default::default)
|
||||||
|
.extend(deny);
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.allow.extend(permission.commands.allow.clone());
|
||||||
|
commands.deny.extend(permission.commands.deny.clone());
|
||||||
|
|
||||||
|
f(ResolvedPermission {
|
||||||
|
key: &key,
|
||||||
|
permission_name: &permission_name,
|
||||||
|
commands,
|
||||||
|
scope: resolved_scope,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traversed permission
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TraversedPermission<'a> {
|
||||||
|
/// Plugin name without the tauri-plugin- prefix
|
||||||
|
pub key: String,
|
||||||
|
/// Permission's name
|
||||||
|
pub permission_name: String,
|
||||||
|
/// Permission details
|
||||||
|
pub permission: &'a Permission,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand a permissions id based on the ACL to get the associated permissions (e.g. expand some-plugin:default)
|
||||||
|
pub fn get_permissions<'a>(
|
||||||
|
permission_id: &Identifier,
|
||||||
|
acl: &'a BTreeMap<String, Manifest>,
|
||||||
|
) -> Result<Vec<TraversedPermission<'a>>, Error> {
|
||||||
|
let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);
|
||||||
|
let permission_name = permission_id.get_base();
|
||||||
|
|
||||||
|
let manifest = acl.get(key).ok_or_else(|| Error::UnknownManifest {
|
||||||
|
key: display_perm_key(key).to_string(),
|
||||||
|
available: acl.keys().cloned().collect::<Vec<_>>().join(", "),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if permission_name == "default" {
|
||||||
|
manifest
|
||||||
|
.default_permission
|
||||||
|
.as_ref()
|
||||||
|
.map(|default| get_permission_set_permissions(permission_id, acl, manifest, default))
|
||||||
|
.unwrap_or_else(|| Ok(Default::default()))
|
||||||
|
} else if let Some(set) = manifest.permission_sets.get(permission_name) {
|
||||||
|
get_permission_set_permissions(permission_id, acl, manifest, set)
|
||||||
|
} else if let Some(permission) = manifest.permissions.get(permission_name) {
|
||||||
|
Ok(vec![TraversedPermission {
|
||||||
|
key: key.to_string(),
|
||||||
|
permission_name: permission_name.to_string(),
|
||||||
|
permission,
|
||||||
|
}])
|
||||||
|
} else {
|
||||||
|
Err(Error::UnknownPermission {
|
||||||
|
key: display_perm_key(key).to_string(),
|
||||||
|
permission: permission_name.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the permissions from a permission set
|
||||||
|
fn get_permission_set_permissions<'a>(
|
||||||
|
permission_id: &Identifier,
|
||||||
|
acl: &'a BTreeMap<String, Manifest>,
|
||||||
|
manifest: &'a Manifest,
|
||||||
|
set: &'a PermissionSet,
|
||||||
|
) -> Result<Vec<TraversedPermission<'a>>, Error> {
|
||||||
|
let key = permission_id.get_prefix().unwrap_or(APP_ACL_KEY);
|
||||||
|
|
||||||
|
let mut permissions = Vec::new();
|
||||||
|
|
||||||
|
for perm in &set.permissions {
|
||||||
|
// a set could include permissions from other plugins
|
||||||
|
// for example `dialog:default`, could include `fs:default`
|
||||||
|
// in this case `perm = "fs:default"` which is not a permission
|
||||||
|
// in the dialog manifest so we check if `perm` still have a prefix (i.e `fs:`)
|
||||||
|
// and if so, we resolve this prefix from `acl` first before proceeding
|
||||||
|
let id = Identifier::try_from(perm.clone()).expect("invalid identifier in permission set?");
|
||||||
|
let (manifest, permission_id, key, permission_name) =
|
||||||
|
if let Some((new_key, manifest)) = id.get_prefix().and_then(|k| acl.get(k).map(|m| (k, m))) {
|
||||||
|
(manifest, &id, new_key, id.get_base())
|
||||||
|
} else {
|
||||||
|
(manifest, permission_id, key, perm.as_str())
|
||||||
|
};
|
||||||
|
|
||||||
|
if permission_name == "default" {
|
||||||
|
permissions.extend(
|
||||||
|
manifest
|
||||||
|
.default_permission
|
||||||
|
.as_ref()
|
||||||
|
.map(|default| get_permission_set_permissions(permission_id, acl, manifest, default))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
} else if let Some(permission) = manifest.permissions.get(permission_name) {
|
||||||
|
permissions.push(TraversedPermission {
|
||||||
|
key: key.to_string(),
|
||||||
|
permission_name: permission_name.to_string(),
|
||||||
|
permission,
|
||||||
|
});
|
||||||
|
} else if let Some(permission_set) = manifest.permission_sets.get(permission_name) {
|
||||||
|
permissions.extend(get_permission_set_permissions(
|
||||||
|
permission_id,
|
||||||
|
acl,
|
||||||
|
manifest,
|
||||||
|
permission_set,
|
||||||
|
)?);
|
||||||
|
} else {
|
||||||
|
return Err(Error::SetPermissionNotFound {
|
||||||
|
permission: permission_name.to_string(),
|
||||||
|
set: set.identifier.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn display_perm_key(prefix: &str) -> &str {
|
||||||
|
if prefix == APP_ACL_KEY {
|
||||||
|
"app manifest"
|
||||||
|
} else {
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
use std::convert::identity;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{literal_struct, tokens::*};
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
impl ToTokens for ResolvedCommandReference {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let capability = str_lit(&self.capability);
|
||||||
|
let permission = str_lit(&self.permission);
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::resolved::ResolvedCommandReference,
|
||||||
|
capability,
|
||||||
|
permission
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ResolvedCommand {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let referenced_by = &self.referenced_by;
|
||||||
|
|
||||||
|
let context = &self.context;
|
||||||
|
|
||||||
|
let windows = vec_lit(&self.windows, |window| {
|
||||||
|
let w = window.as_str();
|
||||||
|
quote!(#w.parse().unwrap())
|
||||||
|
});
|
||||||
|
let webviews = vec_lit(&self.webviews, |window| {
|
||||||
|
let w = window.as_str();
|
||||||
|
quote!(#w.parse().unwrap())
|
||||||
|
});
|
||||||
|
let scope_id = opt_lit(self.scope_id.as_ref());
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::resolved::ResolvedCommand,
|
||||||
|
context,
|
||||||
|
referenced_by,
|
||||||
|
windows,
|
||||||
|
webviews,
|
||||||
|
scope_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::resolved::ResolvedCommand,
|
||||||
|
context,
|
||||||
|
windows,
|
||||||
|
webviews,
|
||||||
|
scope_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ResolvedScope {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let allow = vec_lit(&self.allow, identity);
|
||||||
|
let deny = vec_lit(&self.deny, identity);
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::resolved::ResolvedScope,
|
||||||
|
allow,
|
||||||
|
deny
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Resolved {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let has_app_acl = self.has_app_acl;
|
||||||
|
|
||||||
|
let allowed_commands = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.allowed_commands,
|
||||||
|
str_lit,
|
||||||
|
|v| vec_lit(v, identity),
|
||||||
|
);
|
||||||
|
|
||||||
|
let denied_commands = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.denied_commands,
|
||||||
|
str_lit,
|
||||||
|
|v| vec_lit(v, identity),
|
||||||
|
);
|
||||||
|
|
||||||
|
let command_scope = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.command_scope,
|
||||||
|
identity,
|
||||||
|
identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
let global_scope = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
&self.global_scope,
|
||||||
|
str_lit,
|
||||||
|
identity,
|
||||||
|
);
|
||||||
|
|
||||||
|
literal_struct!(
|
||||||
|
tokens,
|
||||||
|
::tauri::utils::acl::resolved::Resolved,
|
||||||
|
has_app_acl,
|
||||||
|
allowed_commands,
|
||||||
|
denied_commands,
|
||||||
|
command_scope,
|
||||||
|
global_scope
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::{Identifier, Manifest, Permission, PermissionSet, get_permissions};
|
||||||
|
|
||||||
|
fn manifest<const P: usize, const S: usize>(
|
||||||
|
name: &str,
|
||||||
|
permissions: [&str; P],
|
||||||
|
default_set: Option<&[&str]>,
|
||||||
|
sets: [(&str, &[&str]); S],
|
||||||
|
) -> (String, Manifest) {
|
||||||
|
(
|
||||||
|
name.to_string(),
|
||||||
|
Manifest {
|
||||||
|
default_permission: default_set.map(|perms| PermissionSet {
|
||||||
|
identifier: "default".to_string(),
|
||||||
|
description: "default set".to_string(),
|
||||||
|
permissions: perms.iter().map(|s| s.to_string()).collect(),
|
||||||
|
}),
|
||||||
|
permissions: permissions
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
(
|
||||||
|
p.to_string(),
|
||||||
|
Permission {
|
||||||
|
identifier: p.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
permission_sets: sets
|
||||||
|
.iter()
|
||||||
|
.map(|(s, perms)| {
|
||||||
|
(
|
||||||
|
s.to_string(),
|
||||||
|
PermissionSet {
|
||||||
|
identifier: s.to_string(),
|
||||||
|
description: format!("{s} set"),
|
||||||
|
permissions: perms.iter().map(|s| s.to_string()).collect(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(id: &str) -> Identifier {
|
||||||
|
Identifier::try_from(id.to_string()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolves_permissions_from_other_plugins() {
|
||||||
|
let acl = [
|
||||||
|
manifest(
|
||||||
|
"fs",
|
||||||
|
["read", "write", "rm", "exist"],
|
||||||
|
Some(&["read", "exist"]),
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
manifest(
|
||||||
|
"http",
|
||||||
|
["fetch", "fetch-cancel"],
|
||||||
|
None,
|
||||||
|
[("fetch-with-cancel", &["fetch", "fetch-cancel"])],
|
||||||
|
),
|
||||||
|
manifest(
|
||||||
|
"dialog",
|
||||||
|
["open", "save"],
|
||||||
|
None,
|
||||||
|
[(
|
||||||
|
"extra",
|
||||||
|
&[
|
||||||
|
"save",
|
||||||
|
"fs:default",
|
||||||
|
"fs:write",
|
||||||
|
"http:default",
|
||||||
|
"http:fetch-with-cancel",
|
||||||
|
],
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let permissions = get_permissions(&id("fs:default"), &acl).unwrap();
|
||||||
|
assert_eq!(permissions.len(), 2);
|
||||||
|
assert_eq!(permissions[0].key, "fs");
|
||||||
|
assert_eq!(permissions[0].permission_name, "read");
|
||||||
|
assert_eq!(permissions[1].key, "fs");
|
||||||
|
assert_eq!(permissions[1].permission_name, "exist");
|
||||||
|
|
||||||
|
let permissions = get_permissions(&id("fs:rm"), &acl).unwrap();
|
||||||
|
assert_eq!(permissions.len(), 1);
|
||||||
|
assert_eq!(permissions[0].key, "fs");
|
||||||
|
assert_eq!(permissions[0].permission_name, "rm");
|
||||||
|
|
||||||
|
let permissions = get_permissions(&id("http:fetch-with-cancel"), &acl).unwrap();
|
||||||
|
assert_eq!(permissions.len(), 2);
|
||||||
|
assert_eq!(permissions[0].key, "http");
|
||||||
|
assert_eq!(permissions[0].permission_name, "fetch");
|
||||||
|
assert_eq!(permissions[1].key, "http");
|
||||||
|
assert_eq!(permissions[1].permission_name, "fetch-cancel");
|
||||||
|
|
||||||
|
let permissions = get_permissions(&id("dialog:extra"), &acl).unwrap();
|
||||||
|
assert_eq!(permissions.len(), 6);
|
||||||
|
assert_eq!(permissions[0].key, "dialog");
|
||||||
|
assert_eq!(permissions[0].permission_name, "save");
|
||||||
|
assert_eq!(permissions[1].key, "fs");
|
||||||
|
assert_eq!(permissions[1].permission_name, "read");
|
||||||
|
assert_eq!(permissions[2].key, "fs");
|
||||||
|
assert_eq!(permissions[2].permission_name, "exist");
|
||||||
|
assert_eq!(permissions[3].key, "fs");
|
||||||
|
assert_eq!(permissions[3].permission_name, "write");
|
||||||
|
assert_eq!(permissions[4].key, "http");
|
||||||
|
assert_eq!(permissions[4].permission_name, "fetch");
|
||||||
|
assert_eq!(permissions[5].key, "http");
|
||||||
|
assert_eq!(permissions[5].permission_name, "fetch-cancel");
|
||||||
|
}
|
||||||
|
}
|
||||||
445
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/schema.rs
vendored
Normal file
445
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/schema.rs
vendored
Normal file
|
|
@ -0,0 +1,445 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Schema generation for ACL items.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, btree_map::Values},
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
slice::Iter,
|
||||||
|
};
|
||||||
|
|
||||||
|
use schemars::Schema;
|
||||||
|
|
||||||
|
use super::{Error, PERMISSION_SCHEMAS_FOLDER_NAME};
|
||||||
|
use crate::{platform::Target, write_if_changed};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
PERMISSION_SCHEMA_FILE_NAME, Permission, PermissionSet,
|
||||||
|
capability::CapabilityFile,
|
||||||
|
manifest::{Manifest, PermissionFile},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Capability schema file name.
|
||||||
|
pub const CAPABILITIES_SCHEMA_FILE_NAME: &str = "schema.json";
|
||||||
|
/// Path of the folder where schemas are saved.
|
||||||
|
pub const CAPABILITIES_SCHEMA_FOLDER_PATH: &str = "gen/schemas";
|
||||||
|
|
||||||
|
// TODO: once MSRV is high enough, remove generic and use impl <trait>
|
||||||
|
// see https://github.com/tauri-apps/tauri/commit/b5561d74aee431f93c0c5b0fa6784fc0a956effe#diff-7c31d393f83cae149122e74ad44ac98e7d70ffb45c9e5b0a94ec52881b6f1cebR30-R42
|
||||||
|
/// Permission schema generator trait
|
||||||
|
pub trait PermissionSchemaGenerator<
|
||||||
|
'a,
|
||||||
|
Ps: Iterator<Item = &'a PermissionSet>,
|
||||||
|
P: Iterator<Item = &'a Permission>,
|
||||||
|
>
|
||||||
|
{
|
||||||
|
/// Whether has a default permission set or not.
|
||||||
|
fn has_default_permission_set(&self) -> bool;
|
||||||
|
|
||||||
|
/// Default permission set description if any.
|
||||||
|
fn default_set_description(&self) -> Option<&str>;
|
||||||
|
|
||||||
|
/// Default permission set's permissions if any.
|
||||||
|
fn default_set_permissions(&self) -> Option<&Vec<String>>;
|
||||||
|
|
||||||
|
/// Permissions sets to generate schema for.
|
||||||
|
fn permission_sets(&'a self) -> Ps;
|
||||||
|
|
||||||
|
/// Permissions to generate schema for.
|
||||||
|
fn permissions(&'a self) -> P;
|
||||||
|
|
||||||
|
/// A utility function to generate a schema for a permission identifier
|
||||||
|
fn perm_id_schema(name: Option<&str>, id: &str, description: Option<&str>) -> Schema {
|
||||||
|
let command_name = match name {
|
||||||
|
Some(name) if name == super::APP_ACL_KEY => id.to_string(),
|
||||||
|
Some(name) => format!("{name}:{id}"),
|
||||||
|
_ => id.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut schema = schemars::json_schema!({
|
||||||
|
"type": "string",
|
||||||
|
"const": command_name
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(description) = description {
|
||||||
|
schema.insert(
|
||||||
|
"description".to_string(),
|
||||||
|
serde_json::Value::String(description.to_string()),
|
||||||
|
);
|
||||||
|
// Non-standard, used by vscode for rich hover tooltips
|
||||||
|
schema.insert(
|
||||||
|
"markdownDescription".to_string(),
|
||||||
|
serde_json::Value::String(description.to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate schemas for all possible permissions.
|
||||||
|
fn gen_possible_permission_schemas(&'a self, name: Option<&str>) -> Vec<Schema> {
|
||||||
|
let mut permission_schemas = Vec::new();
|
||||||
|
|
||||||
|
// schema for default set
|
||||||
|
if self.has_default_permission_set() {
|
||||||
|
let description = self.default_set_description().unwrap_or_default();
|
||||||
|
let description = if let Some(permissions) = self.default_set_permissions() {
|
||||||
|
add_permissions_to_description(description, permissions, true)
|
||||||
|
} else {
|
||||||
|
description.to_string()
|
||||||
|
};
|
||||||
|
if !description.is_empty() {
|
||||||
|
let default = Self::perm_id_schema(name, "default", Some(&description));
|
||||||
|
permission_schemas.push(default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema for each permission set
|
||||||
|
for set in self.permission_sets() {
|
||||||
|
let description = add_permissions_to_description(&set.description, &set.permissions, false);
|
||||||
|
let schema = Self::perm_id_schema(name, &set.identifier, Some(&description));
|
||||||
|
permission_schemas.push(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// schema for each permission
|
||||||
|
for perm in self.permissions() {
|
||||||
|
let schema = Self::perm_id_schema(name, &perm.identifier, perm.description.as_deref());
|
||||||
|
permission_schemas.push(schema);
|
||||||
|
}
|
||||||
|
|
||||||
|
permission_schemas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_permissions_to_description(
|
||||||
|
description: &str,
|
||||||
|
permissions: &[String],
|
||||||
|
is_default: bool,
|
||||||
|
) -> String {
|
||||||
|
if permissions.is_empty() {
|
||||||
|
return description.to_string();
|
||||||
|
}
|
||||||
|
let permissions_list = permissions
|
||||||
|
.iter()
|
||||||
|
.map(|permission| format!("- `{permission}`"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let default_permission_set = if is_default {
|
||||||
|
"default permission set"
|
||||||
|
} else {
|
||||||
|
"permission set"
|
||||||
|
};
|
||||||
|
format!("{description}\n#### This {default_permission_set} includes:\n\n{permissions_list}")
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a>
|
||||||
|
PermissionSchemaGenerator<
|
||||||
|
'a,
|
||||||
|
Values<'a, std::string::String, PermissionSet>,
|
||||||
|
Values<'a, std::string::String, Permission>,
|
||||||
|
> for Manifest
|
||||||
|
{
|
||||||
|
fn has_default_permission_set(&self) -> bool {
|
||||||
|
self.default_permission.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_set_description(&self) -> Option<&str> {
|
||||||
|
self
|
||||||
|
.default_permission
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.description.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_set_permissions(&self) -> Option<&Vec<String>> {
|
||||||
|
self.default_permission.as_ref().map(|d| &d.permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permission_sets(&'a self) -> Values<'a, std::string::String, PermissionSet> {
|
||||||
|
self.permission_sets.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&'a self) -> Values<'a, std::string::String, Permission> {
|
||||||
|
self.permissions.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PermissionSchemaGenerator<'a, Iter<'a, PermissionSet>, Iter<'a, Permission>>
|
||||||
|
for PermissionFile
|
||||||
|
{
|
||||||
|
fn has_default_permission_set(&self) -> bool {
|
||||||
|
self.default.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_set_description(&self) -> Option<&str> {
|
||||||
|
self.default.as_ref().and_then(|d| d.description.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_set_permissions(&self) -> Option<&Vec<String>> {
|
||||||
|
self.default.as_ref().map(|d| &d.permissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permission_sets(&'a self) -> Iter<'a, PermissionSet> {
|
||||||
|
self.set.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn permissions(&'a self) -> Iter<'a, Permission> {
|
||||||
|
self.permission.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect and include all possible identifiers in `Identifier` definition in the schema
|
||||||
|
fn extend_identifier_schema(schema: &mut Schema, acl: &BTreeMap<String, Manifest>) {
|
||||||
|
let permission_schemas: Vec<serde_json::Value> = acl
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(name, manifest)| manifest.gen_possible_permission_schemas(Some(name)))
|
||||||
|
.map(serde_json::Value::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(identifier_schema) = schema
|
||||||
|
.pointer_mut("/$defs/Identifier")
|
||||||
|
.and_then(|v| v.as_object_mut())
|
||||||
|
{
|
||||||
|
identifier_schema.insert(
|
||||||
|
"oneOf".to_string(),
|
||||||
|
serde_json::Value::Array(permission_schemas),
|
||||||
|
);
|
||||||
|
identifier_schema.remove("properties");
|
||||||
|
identifier_schema.remove("type");
|
||||||
|
identifier_schema.insert(
|
||||||
|
"description".to_string(),
|
||||||
|
serde_json::Value::String("Permission identifier".to_string()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect permission schemas and its associated scope schema and schema definitions from plugins
|
||||||
|
/// and replace `PermissionEntry` extend object syntax with a new schema that does conditional
|
||||||
|
/// checks to serve the relevant scope schema for the right permissions schema, in a nutshell, it
|
||||||
|
/// will look something like this:
|
||||||
|
/// ```text
|
||||||
|
/// PermissionEntry {
|
||||||
|
/// anyOf {
|
||||||
|
/// String, // default string syntax
|
||||||
|
/// Object { // extended object syntax
|
||||||
|
/// allOf { // JSON allOf is used but actually means anyOf
|
||||||
|
/// {
|
||||||
|
/// "if": "identifier" property anyOf "fs" plugin permission,
|
||||||
|
/// "then": add "allow" and "deny" properties that match "fs" plugin scope schema
|
||||||
|
/// },
|
||||||
|
/// {
|
||||||
|
/// "if": "identifier" property anyOf "http" plugin permission,
|
||||||
|
/// "then": add "allow" and "deny" properties that match "http" plugin scope schema
|
||||||
|
/// },
|
||||||
|
/// ...etc,
|
||||||
|
/// {
|
||||||
|
/// No "if" or "then", just "allow" and "deny" properties with default "#/defs/Value"
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
fn extend_permission_entry_schema(root_schema: &mut Schema, acl: &BTreeMap<String, Manifest>) {
|
||||||
|
const IDENTIFIER: &str = "identifier";
|
||||||
|
const ALLOW: &str = "allow";
|
||||||
|
const DENY: &str = "deny";
|
||||||
|
|
||||||
|
let mut collected_defs: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
|
||||||
|
|
||||||
|
// Scope the mutable borrow of root_schema
|
||||||
|
{
|
||||||
|
let defs = match root_schema.get_mut("$defs").and_then(|v| v.as_object_mut()) {
|
||||||
|
Some(d) => d,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let perm_entry = match defs
|
||||||
|
.get_mut("PermissionEntry")
|
||||||
|
.and_then(|v| v.as_object_mut())
|
||||||
|
{
|
||||||
|
Some(p) => p,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let any_of = match perm_entry.get_mut("anyOf").and_then(|v| v.as_array_mut()) {
|
||||||
|
Some(a) => a,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let extend_perm_entry = match any_of.last_mut().and_then(|v| v.as_object_mut()) {
|
||||||
|
Some(e) => e,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove default properties and save to be added later as a fallback
|
||||||
|
let default_properties = extend_perm_entry
|
||||||
|
.remove("properties")
|
||||||
|
.and_then(|v| match v {
|
||||||
|
serde_json::Value::Object(m) => Some(m),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let default_identifier = default_properties.get(IDENTIFIER).cloned().unwrap();
|
||||||
|
|
||||||
|
let mut all_of: Vec<serde_json::Value> = vec![];
|
||||||
|
|
||||||
|
let schemas = acl.iter().filter_map(|(name, manifest)| {
|
||||||
|
manifest
|
||||||
|
.global_scope_schema()
|
||||||
|
.unwrap_or_else(|e| panic!("invalid JSON schema for plugin {name}: {e}"))
|
||||||
|
.map(|s| (s, manifest.gen_possible_permission_schemas(Some(name))))
|
||||||
|
});
|
||||||
|
|
||||||
|
for ((scope_schema, defs), acl_perm_schema) in schemas {
|
||||||
|
let perm_schema_values: Vec<serde_json::Value> = acl_perm_schema
|
||||||
|
.into_iter()
|
||||||
|
.map(serde_json::Value::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let scope_value = serde_json::Value::from(scope_schema);
|
||||||
|
|
||||||
|
let obj = serde_json::json!({
|
||||||
|
"properties": {
|
||||||
|
IDENTIFIER: default_identifier.clone()
|
||||||
|
},
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
IDENTIFIER: { "anyOf": perm_schema_values }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
ALLOW: scope_value.clone(),
|
||||||
|
DENY: scope_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
all_of.push(obj);
|
||||||
|
collected_defs.extend(defs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add back default properties as a fallback
|
||||||
|
all_of.push(serde_json::json!({
|
||||||
|
"properties": serde_json::Value::Object(default_properties)
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Replace extended PermissionEntry with the new schema
|
||||||
|
extend_perm_entry.insert("allOf".to_string(), serde_json::Value::Array(all_of));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend root schema with definitions collected from plugins
|
||||||
|
if !collected_defs.is_empty() {
|
||||||
|
let root_defs = root_schema
|
||||||
|
.ensure_object()
|
||||||
|
.entry("$defs")
|
||||||
|
.or_insert(serde_json::Value::Object(serde_json::Map::new()))
|
||||||
|
.as_object_mut()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
root_defs.extend(collected_defs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate schema for CapabilityFile with all possible plugins permissions
|
||||||
|
pub fn generate_capability_schema(
|
||||||
|
acl: &BTreeMap<String, Manifest>,
|
||||||
|
target: Target,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let mut schema = schemars::SchemaGenerator::new(schemars::generate::SchemaSettings::draft07())
|
||||||
|
.into_root_schema_for::<CapabilityFile>();
|
||||||
|
|
||||||
|
extend_identifier_schema(&mut schema, acl);
|
||||||
|
extend_permission_entry_schema(&mut schema, acl);
|
||||||
|
|
||||||
|
let schema_str = serde_json::to_string_pretty(&schema).unwrap();
|
||||||
|
|
||||||
|
let out_dir = PathBuf::from(CAPABILITIES_SCHEMA_FOLDER_PATH);
|
||||||
|
fs::create_dir_all(&out_dir)?;
|
||||||
|
|
||||||
|
let schema_path = out_dir.join(format!("{target}-{CAPABILITIES_SCHEMA_FILE_NAME}"));
|
||||||
|
if schema_str != fs::read_to_string(&schema_path).unwrap_or_default() {
|
||||||
|
fs::write(&schema_path, schema_str)?;
|
||||||
|
|
||||||
|
fs::copy(
|
||||||
|
schema_path,
|
||||||
|
out_dir.join(format!(
|
||||||
|
"{}-{CAPABILITIES_SCHEMA_FILE_NAME}",
|
||||||
|
if target.is_desktop() {
|
||||||
|
"desktop"
|
||||||
|
} else {
|
||||||
|
"mobile"
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extend schema with collected permissions from the passed [`PermissionFile`]s.
|
||||||
|
fn extend_permission_file_schema(schema: &mut Schema, permissions: &[PermissionFile]) {
|
||||||
|
// Collect possible permissions
|
||||||
|
let permission_schemas: Vec<serde_json::Value> = permissions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| p.gen_possible_permission_schemas(None))
|
||||||
|
.map(serde_json::Value::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Update the permissions property to reference PermissionKind
|
||||||
|
let updated = if let Some(permissions_obj) = schema
|
||||||
|
.pointer_mut("/$defs/PermissionSet/properties/permissions")
|
||||||
|
.and_then(|v| v.as_object_mut())
|
||||||
|
{
|
||||||
|
permissions_obj.insert(
|
||||||
|
"items".to_string(),
|
||||||
|
serde_json::json!({ "$ref": "#/$defs/PermissionKind" }),
|
||||||
|
);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the new PermissionKind definition
|
||||||
|
if updated {
|
||||||
|
let defs = schema
|
||||||
|
.ensure_object()
|
||||||
|
.entry("$defs")
|
||||||
|
.or_insert(serde_json::Value::Object(serde_json::Map::new()))
|
||||||
|
.as_object_mut()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
defs.insert(
|
||||||
|
"PermissionKind".into(),
|
||||||
|
serde_json::json!({
|
||||||
|
"type": "string",
|
||||||
|
"oneOf": permission_schemas,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate and write a schema based on the format of a [`PermissionFile`].
|
||||||
|
pub fn generate_permissions_schema<P: AsRef<Path>>(
|
||||||
|
permissions: &[PermissionFile],
|
||||||
|
out_dir: P,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut schema = schemars::SchemaGenerator::new(schemars::generate::SchemaSettings::draft07())
|
||||||
|
.into_root_schema_for::<PermissionFile>();
|
||||||
|
|
||||||
|
extend_permission_file_schema(&mut schema, permissions);
|
||||||
|
|
||||||
|
let schema_str = serde_json::to_string_pretty(&schema)?;
|
||||||
|
|
||||||
|
let out_dir = out_dir.as_ref().join(PERMISSION_SCHEMAS_FOLDER_NAME);
|
||||||
|
fs::create_dir_all(&out_dir).map_err(|e| Error::CreateDir(e, out_dir.clone()))?;
|
||||||
|
|
||||||
|
let schema_path = out_dir.join(PERMISSION_SCHEMA_FILE_NAME);
|
||||||
|
write_if_changed(&schema_path, schema_str).map_err(|e| Error::WriteFile(e, schema_path))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
201
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/value.rs
vendored
Normal file
201
apps/tauri/src-tauri/vendor/tauri-utils/src/acl/value.rs
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! A [`Value`] that is used instead of [`toml::Value`] or [`serde_json::Value`]
|
||||||
|
//! to support both formats.
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A valid ACL number.
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Copy, Clone, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Number {
|
||||||
|
/// Represents an [`i64`].
|
||||||
|
Int(i64),
|
||||||
|
|
||||||
|
/// Represents a [`f64`].
|
||||||
|
Float(f64),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for Number {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Self::Int(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<f64> for Number {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: f64) -> Self {
|
||||||
|
Self::Float(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All supported ACL values.
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, PartialOrd)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Value {
|
||||||
|
/// Represents a null JSON value.
|
||||||
|
Null,
|
||||||
|
|
||||||
|
/// Represents a [`bool`].
|
||||||
|
Bool(bool),
|
||||||
|
|
||||||
|
/// Represents a valid ACL [`Number`].
|
||||||
|
Number(Number),
|
||||||
|
|
||||||
|
/// Represents a [`String`].
|
||||||
|
String(String),
|
||||||
|
|
||||||
|
/// Represents a list of other [`Value`]s.
|
||||||
|
List(Vec<Value>),
|
||||||
|
|
||||||
|
/// Represents a map of [`String`] keys to [`Value`]s.
|
||||||
|
Map(BTreeMap<String, Value>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Value> for serde_json::Value {
|
||||||
|
fn from(value: Value) -> Self {
|
||||||
|
match value {
|
||||||
|
Value::Null => serde_json::Value::Null,
|
||||||
|
Value::Bool(b) => serde_json::Value::Bool(b),
|
||||||
|
Value::Number(Number::Float(f)) => {
|
||||||
|
serde_json::Value::Number(serde_json::Number::from_f64(f).unwrap())
|
||||||
|
}
|
||||||
|
Value::Number(Number::Int(i)) => serde_json::Value::Number(i.into()),
|
||||||
|
Value::String(s) => serde_json::Value::String(s),
|
||||||
|
Value::List(list) => serde_json::Value::Array(list.into_iter().map(Into::into).collect()),
|
||||||
|
Value::Map(map) => serde_json::Value::Object(
|
||||||
|
map
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, value.into()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Value> for Value {
|
||||||
|
fn from(value: serde_json::Value) -> Self {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => Value::Null,
|
||||||
|
serde_json::Value::Bool(b) => Value::Bool(b),
|
||||||
|
serde_json::Value::Number(n) => Value::Number(if let Some(f) = n.as_f64() {
|
||||||
|
Number::Float(f)
|
||||||
|
} else if let Some(n) = n.as_u64() {
|
||||||
|
Number::Int(n as i64)
|
||||||
|
} else if let Some(n) = n.as_i64() {
|
||||||
|
Number::Int(n)
|
||||||
|
} else {
|
||||||
|
Number::Int(0)
|
||||||
|
}),
|
||||||
|
serde_json::Value::String(s) => Value::String(s),
|
||||||
|
serde_json::Value::Array(list) => Value::List(list.into_iter().map(Into::into).collect()),
|
||||||
|
serde_json::Value::Object(map) => Value::Map(
|
||||||
|
map
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| (key, value.into()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Value {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
Self::Bool(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Into<Number>> From<T> for Value {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self::Number(value.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for Value {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Value::String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<toml::Value> for Value {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: toml::Value) -> Self {
|
||||||
|
use toml::Value as Toml;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Toml::String(s) => s.into(),
|
||||||
|
Toml::Integer(i) => i.into(),
|
||||||
|
Toml::Float(f) => f.into(),
|
||||||
|
Toml::Boolean(b) => b.into(),
|
||||||
|
Toml::Datetime(d) => d.to_string().into(),
|
||||||
|
Toml::Array(a) => Value::List(a.into_iter().map(Value::from).collect()),
|
||||||
|
Toml::Table(t) => Value::Map(t.into_iter().map(|(k, v)| (k, v.into())).collect()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use std::convert::identity;
|
||||||
|
|
||||||
|
use crate::tokens::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
|
||||||
|
impl ToTokens for Number {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let prefix = quote! { ::tauri::utils::acl::Number };
|
||||||
|
|
||||||
|
tokens.append_all(match self {
|
||||||
|
Self::Int(i) => {
|
||||||
|
quote! { #prefix::Int(#i) }
|
||||||
|
}
|
||||||
|
Self::Float(f) => {
|
||||||
|
quote! { #prefix::Float (#f) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Value {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let prefix = quote! { ::tauri::utils::acl::Value };
|
||||||
|
|
||||||
|
tokens.append_all(match self {
|
||||||
|
Value::Null => quote! { #prefix::Null },
|
||||||
|
Value::Bool(bool) => quote! { #prefix::Bool(#bool) },
|
||||||
|
Value::Number(number) => quote! { #prefix::Number(#number) },
|
||||||
|
Value::String(str) => {
|
||||||
|
let s = str_lit(str);
|
||||||
|
quote! { #prefix::String(#s) }
|
||||||
|
}
|
||||||
|
Value::List(vec) => {
|
||||||
|
let items = vec_lit(vec, identity);
|
||||||
|
quote! { #prefix::List(#items) }
|
||||||
|
}
|
||||||
|
Value::Map(map) => {
|
||||||
|
let map = map_lit(
|
||||||
|
quote! { ::std::collections::BTreeMap },
|
||||||
|
map,
|
||||||
|
str_lit,
|
||||||
|
identity,
|
||||||
|
);
|
||||||
|
quote! { #prefix::Map(#map) }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
212
apps/tauri/src-tauri/vendor/tauri-utils/src/assets.rs
vendored
Normal file
212
apps/tauri/src-tauri/vendor/tauri-utils/src/assets.rs
vendored
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! The Assets module allows you to read files that have been bundled by tauri
|
||||||
|
//! during both compile time and runtime.
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use phf;
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
path::{Component, Path},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The token used for script nonces.
|
||||||
|
pub const SCRIPT_NONCE_TOKEN: &str = "__TAURI_SCRIPT_NONCE__";
|
||||||
|
/// The token used for style nonces.
|
||||||
|
pub const STYLE_NONCE_TOKEN: &str = "__TAURI_STYLE_NONCE__";
|
||||||
|
|
||||||
|
/// Assets iterator.
|
||||||
|
pub type AssetsIter<'a> = dyn Iterator<Item = (Cow<'a, str>, Cow<'a, [u8]>)> + 'a;
|
||||||
|
|
||||||
|
/// Represent an asset file path in a normalized way.
|
||||||
|
///
|
||||||
|
/// The following rules are enforced and added if needed:
|
||||||
|
/// * Unix path component separators
|
||||||
|
/// * Has a root directory
|
||||||
|
/// * No trailing slash - directories are not included in assets
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct AssetKey(String);
|
||||||
|
|
||||||
|
impl From<AssetKey> for String {
|
||||||
|
fn from(key: AssetKey) -> Self {
|
||||||
|
key.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for AssetKey {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: AsRef<Path>> From<P> for AssetKey {
|
||||||
|
fn from(path: P) -> Self {
|
||||||
|
// TODO: change this to utilize `Cow` to prevent allocating an intermediate `PathBuf` when not necessary
|
||||||
|
let path = path.as_ref().to_owned();
|
||||||
|
|
||||||
|
// add in root to mimic how it is used from a server url
|
||||||
|
let path = if path.has_root() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
Path::new(&Component::RootDir).join(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
let buf = if cfg!(windows) {
|
||||||
|
let mut buf = String::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
Component::RootDir => buf.push('/'),
|
||||||
|
Component::CurDir => buf.push_str("./"),
|
||||||
|
Component::ParentDir => buf.push_str("../"),
|
||||||
|
Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()),
|
||||||
|
Component::Normal(s) => {
|
||||||
|
buf.push_str(&s.to_string_lossy());
|
||||||
|
buf.push('/')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the last slash
|
||||||
|
if buf != "/" {
|
||||||
|
buf.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
buf
|
||||||
|
} else {
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
AssetKey(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Content-Security-Policy hash value for a specific directive.
|
||||||
|
/// For more information see [the MDN page](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives).
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CspHash<'a> {
|
||||||
|
/// The `script-src` directive.
|
||||||
|
Script(&'a str),
|
||||||
|
|
||||||
|
/// The `style-src` directive.
|
||||||
|
Style(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CspHash<'_> {
|
||||||
|
/// The Content-Security-Policy directive this hash applies to.
|
||||||
|
pub fn directive(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Script(_) => "script-src",
|
||||||
|
Self::Style(_) => "style-src",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of the Content-Security-Policy hash.
|
||||||
|
pub fn hash(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Script(hash) => hash,
|
||||||
|
Self::Style(hash) => hash,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`Assets`] implementation that only contains compile-time compressed and embedded assets.
|
||||||
|
pub struct EmbeddedAssets {
|
||||||
|
assets: phf::Map<&'static str, &'static [u8]>,
|
||||||
|
// Hashes that must be injected to the CSP of every HTML file.
|
||||||
|
global_hashes: &'static [CspHash<'static>],
|
||||||
|
// Hashes that are associated to the CSP of the HTML file identified by the map key (the HTML asset key).
|
||||||
|
html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporary struct that overrides the Debug formatting for the `assets` field.
|
||||||
|
///
|
||||||
|
/// It reduces the output size compared to the default, as that would format the binary
|
||||||
|
/// data as a slice of numbers like `[65, 66, 67]` for "ABC". This instead shows the length
|
||||||
|
/// of the slice.
|
||||||
|
///
|
||||||
|
/// For example: `{"/index.html": [u8; 1835], "/index.js": [u8; 212]}`
|
||||||
|
struct DebugAssetMap<'a>(&'a phf::Map<&'static str, &'static [u8]>);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for DebugAssetMap<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut map = f.debug_map();
|
||||||
|
for (k, v) in self.0.entries() {
|
||||||
|
map.key(k);
|
||||||
|
map.value(&format_args!("[u8; {}]", v.len()));
|
||||||
|
}
|
||||||
|
map.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for EmbeddedAssets {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("EmbeddedAssets")
|
||||||
|
.field("assets", &DebugAssetMap(&self.assets))
|
||||||
|
.field("global_hashes", &self.global_hashes)
|
||||||
|
.field("html_hashes", &self.html_hashes)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmbeddedAssets {
|
||||||
|
/// Creates a new instance from the given asset map and script hash list.
|
||||||
|
pub const fn new(
|
||||||
|
map: phf::Map<&'static str, &'static [u8]>,
|
||||||
|
global_hashes: &'static [CspHash<'static>],
|
||||||
|
html_hashes: phf::Map<&'static str, &'static [CspHash<'static>]>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
assets: map,
|
||||||
|
global_hashes,
|
||||||
|
html_hashes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an asset by key.
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
|
pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
|
||||||
|
let &(mut asdf) = self.assets.get(key.as_ref())?;
|
||||||
|
// with the exception of extremely small files, output should usually be
|
||||||
|
// at least as large as the compressed version.
|
||||||
|
let mut buf = Vec::with_capacity(asdf.len());
|
||||||
|
brotli::BrotliDecompress(&mut asdf, &mut buf).ok()?;
|
||||||
|
Some(Cow::Owned(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an asset by key.
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
pub fn get(&self, key: &AssetKey) -> Option<Cow<'_, [u8]>> {
|
||||||
|
Some(Cow::Borrowed(self.assets.get(key.as_ref())?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate on the assets.
|
||||||
|
pub fn iter(&self) -> Box<AssetsIter<'_>> {
|
||||||
|
Box::new(
|
||||||
|
self
|
||||||
|
.assets
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, b)| (Cow::Borrowed(*k), Cow::Borrowed(*b))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CSP hashes for the given asset.
|
||||||
|
pub fn csp_hashes(&self, html_path: &AssetKey) -> Box<dyn Iterator<Item = CspHash<'_>> + '_> {
|
||||||
|
Box::new(
|
||||||
|
self
|
||||||
|
.global_hashes
|
||||||
|
.iter()
|
||||||
|
.chain(
|
||||||
|
self
|
||||||
|
.html_hashes
|
||||||
|
.get(html_path.as_ref())
|
||||||
|
.copied()
|
||||||
|
.into_iter()
|
||||||
|
.flatten(),
|
||||||
|
)
|
||||||
|
.copied(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
171
apps/tauri/src-tauri/vendor/tauri-utils/src/build.rs
vendored
Normal file
171
apps/tauri/src-tauri/vendor/tauri-utils/src/build.rs
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Build script utilities.
|
||||||
|
|
||||||
|
/// Link a Swift library.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn link_apple_library(name: &str, source: impl AsRef<std::path::Path>) {
|
||||||
|
if source.as_ref().join("Package.swift").exists() {
|
||||||
|
link_swift_library(name, source);
|
||||||
|
} else {
|
||||||
|
link_xcode_library(name, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Link a Swift library.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn link_swift_library(name: &str, source: impl AsRef<std::path::Path>) {
|
||||||
|
let source = source.as_ref();
|
||||||
|
|
||||||
|
let sdk_root = std::env::var_os("SDKROOT");
|
||||||
|
unsafe {
|
||||||
|
std::env::remove_var("SDKROOT");
|
||||||
|
}
|
||||||
|
|
||||||
|
swift_rs::SwiftLinker::new(
|
||||||
|
&std::env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| "10.13".into()),
|
||||||
|
)
|
||||||
|
.with_ios(&std::env::var("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or_else(|_| "13.0".into()))
|
||||||
|
.with_package(name, source)
|
||||||
|
.link();
|
||||||
|
|
||||||
|
if let Some(root) = sdk_root {
|
||||||
|
unsafe {
|
||||||
|
std::env::set_var("SDKROOT", root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Link a Xcode library.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn link_xcode_library(name: &str, source: impl AsRef<std::path::Path>) {
|
||||||
|
use std::{path::PathBuf, process::Command};
|
||||||
|
|
||||||
|
let source = source.as_ref();
|
||||||
|
let configuration = if std::env::var("DEBUG")
|
||||||
|
.map(|v| v == "true")
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
"Debug"
|
||||||
|
} else {
|
||||||
|
"Release"
|
||||||
|
};
|
||||||
|
|
||||||
|
let (sdk, arch) = match std::env::var("TARGET").unwrap().as_str() {
|
||||||
|
"aarch64-apple-ios" => ("iphoneos", "arm64"),
|
||||||
|
"aarch64-apple-ios-sim" => ("iphonesimulator", "arm64"),
|
||||||
|
"x86_64-apple-ios" => ("iphonesimulator", "x86_64"),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let out_dir = std::env::var_os("OUT_DIR").map(PathBuf::from).unwrap();
|
||||||
|
let derived_data_path = out_dir.join(format!("derivedData-{name}"));
|
||||||
|
|
||||||
|
let status = Command::new("xcodebuild")
|
||||||
|
.arg("build")
|
||||||
|
.arg("-scheme")
|
||||||
|
.arg(name)
|
||||||
|
.arg("-configuration")
|
||||||
|
.arg(configuration)
|
||||||
|
.arg("-sdk")
|
||||||
|
.arg(sdk)
|
||||||
|
.arg("-arch")
|
||||||
|
.arg(arch)
|
||||||
|
.arg("-derivedDataPath")
|
||||||
|
.arg(&derived_data_path)
|
||||||
|
.arg("BUILD_LIBRARY_FOR_DISTRIBUTION=YES")
|
||||||
|
.arg("OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface")
|
||||||
|
.current_dir(source)
|
||||||
|
.env_clear()
|
||||||
|
.env("PATH", std::env::var_os("PATH").unwrap_or_default())
|
||||||
|
.status()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(status.success());
|
||||||
|
|
||||||
|
let lib_out_dir = derived_data_path
|
||||||
|
.join("Build")
|
||||||
|
.join("Products")
|
||||||
|
.join(format!("{configuration}-{sdk}"));
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"cargo::rustc-link-search=framework={}",
|
||||||
|
lib_out_dir.display()
|
||||||
|
);
|
||||||
|
println!("cargo:rerun-if-changed={}", source.display());
|
||||||
|
println!("cargo:rustc-link-search=native={}", lib_out_dir.display());
|
||||||
|
println!("cargo:rustc-link-lib=static={name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the Android manifest by inserting XML content into a specified parent tag.
|
||||||
|
///
|
||||||
|
/// The content is wrapped in auto-generated comments and will replace any existing
|
||||||
|
/// content with the same block identifier.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `block_identifier` - A unique identifier for the block (used in comments)
|
||||||
|
/// * `parent` - The parent XML tag name (e.g., "activity", "application")
|
||||||
|
/// * `insert` - The XML content to insert
|
||||||
|
pub fn update_android_manifest(
|
||||||
|
block_identifier: &str,
|
||||||
|
parent: &str,
|
||||||
|
insert: String,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
use std::{
|
||||||
|
env::var_os,
|
||||||
|
fs::{read_to_string, write},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(project_path) = var_os("TAURI_ANDROID_PROJECT_PATH").map(PathBuf::from) {
|
||||||
|
let manifest_path = project_path.join("app/src/main/AndroidManifest.xml");
|
||||||
|
if !manifest_path.exists() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let manifest = read_to_string(&manifest_path)?;
|
||||||
|
let rewritten = insert_into_xml(&manifest, block_identifier, parent, &insert);
|
||||||
|
if rewritten != manifest {
|
||||||
|
write(&manifest_path, rewritten)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xml_block_comment(id: &str) -> String {
|
||||||
|
format!("<!-- {id}. AUTO-GENERATED. DO NOT REMOVE. -->")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_into_xml(xml: &str, block_identifier: &str, parent_tag: &str, contents: &str) -> String {
|
||||||
|
let block_comment = xml_block_comment(block_identifier);
|
||||||
|
|
||||||
|
let mut rewritten = Vec::new();
|
||||||
|
let mut found_block = false;
|
||||||
|
let parent_closing_tag = format!("</{parent_tag}>");
|
||||||
|
for line in xml.split('\n') {
|
||||||
|
if line.contains(&block_comment) {
|
||||||
|
found_block = !found_block;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// found previous block which should be removed
|
||||||
|
if found_block {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = line.find(&parent_closing_tag) {
|
||||||
|
let indentation = " ".repeat(index + 4);
|
||||||
|
rewritten.push(format!("{indentation}{block_comment}"));
|
||||||
|
for l in contents.split('\n') {
|
||||||
|
rewritten.push(format!("{indentation}{l}"));
|
||||||
|
}
|
||||||
|
rewritten.push(format!("{indentation}{block_comment}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
rewritten.push(line.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
rewritten.join("\n")
|
||||||
|
}
|
||||||
4684
apps/tauri/src-tauri/vendor/tauri-utils/src/config.rs
vendored
Normal file
4684
apps/tauri/src-tauri/vendor/tauri-utils/src/config.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
398
apps/tauri/src-tauri/vendor/tauri-utils/src/config/parse.rs
vendored
Normal file
398
apps/tauri/src-tauri/vendor/tauri-utils/src/config/parse.rs
vendored
Normal file
|
|
@ -0,0 +1,398 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::platform::Target;
|
||||||
|
use json_patch::merge;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// All extensions that are possibly supported, but perhaps not enabled.
|
||||||
|
pub const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];
|
||||||
|
|
||||||
|
/// All configuration formats that are possibly supported, but perhaps not enabled.
|
||||||
|
pub const SUPPORTED_FORMATS: &[ConfigFormat] =
|
||||||
|
&[ConfigFormat::Json, ConfigFormat::Json5, ConfigFormat::Toml];
|
||||||
|
|
||||||
|
/// All configuration formats that are currently enabled.
|
||||||
|
pub const ENABLED_FORMATS: &[ConfigFormat] = &[
|
||||||
|
ConfigFormat::Json,
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
ConfigFormat::Json5,
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
ConfigFormat::Toml,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The available configuration formats.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum ConfigFormat {
|
||||||
|
/// The default JSON (tauri.conf.json) format.
|
||||||
|
Json,
|
||||||
|
/// The JSON5 (tauri.conf.json5) format.
|
||||||
|
Json5,
|
||||||
|
/// The TOML (Tauri.toml file) format.
|
||||||
|
Toml,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigFormat {
|
||||||
|
/// Maps the config format to its file name.
|
||||||
|
pub fn into_file_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Json => "tauri.conf.json",
|
||||||
|
Self::Json5 => "tauri.conf.json5",
|
||||||
|
Self::Toml => "Tauri.toml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_platform_file_name(self, target: Target) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Json => match target {
|
||||||
|
Target::MacOS => "tauri.macos.conf.json",
|
||||||
|
Target::Windows => "tauri.windows.conf.json",
|
||||||
|
Target::Linux => "tauri.linux.conf.json",
|
||||||
|
Target::Android => "tauri.android.conf.json",
|
||||||
|
Target::Ios => "tauri.ios.conf.json",
|
||||||
|
},
|
||||||
|
Self::Json5 => match target {
|
||||||
|
Target::MacOS => "tauri.macos.conf.json5",
|
||||||
|
Target::Windows => "tauri.windows.conf.json5",
|
||||||
|
Target::Linux => "tauri.linux.conf.json5",
|
||||||
|
Target::Android => "tauri.android.conf.json5",
|
||||||
|
Target::Ios => "tauri.ios.conf.json5",
|
||||||
|
},
|
||||||
|
Self::Toml => match target {
|
||||||
|
Target::MacOS => "Tauri.macos.toml",
|
||||||
|
Target::Windows => "Tauri.windows.toml",
|
||||||
|
Target::Linux => "Tauri.linux.toml",
|
||||||
|
Target::Android => "Tauri.android.toml",
|
||||||
|
Target::Ios => "Tauri.ios.toml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents all the errors that can happen while reading the config.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ConfigError {
|
||||||
|
/// Failed to parse from JSON.
|
||||||
|
#[error("unable to parse JSON Tauri config file at {path} because {error}")]
|
||||||
|
FormatJson {
|
||||||
|
/// The path that failed to parse into JSON.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`serde_json::Error`].
|
||||||
|
error: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Failed to parse from JSON5.
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
#[error("unable to parse JSON5 Tauri config file at {path} because {error}")]
|
||||||
|
FormatJson5 {
|
||||||
|
/// The path that failed to parse into JSON5.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`json5::Error`].
|
||||||
|
error: ::json5::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Failed to parse from TOML.
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
#[error("unable to parse toml Tauri config file at {path} because {error}")]
|
||||||
|
FormatToml {
|
||||||
|
/// The path that failed to parse into TOML.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`toml::Error`].
|
||||||
|
error: Box<::toml::de::Error>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Unknown config file name encountered.
|
||||||
|
#[error("unsupported format encountered {0}")]
|
||||||
|
UnsupportedFormat(String),
|
||||||
|
|
||||||
|
/// Known file extension encountered, but corresponding parser is not enabled (cargo features).
|
||||||
|
#[error("supported (but disabled) format encountered {extension} - try enabling `{feature}` ")]
|
||||||
|
DisabledFormat {
|
||||||
|
/// The extension encountered.
|
||||||
|
extension: String,
|
||||||
|
|
||||||
|
/// The cargo feature to enable it.
|
||||||
|
feature: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A generic IO error with context of what caused it.
|
||||||
|
#[error("unable to read Tauri config file at {path} because {error}")]
|
||||||
|
Io {
|
||||||
|
/// The path the IO error occurred on.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The [`std::io::Error`].
|
||||||
|
error: std::io::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if the given folder has a configuration file.
|
||||||
|
pub fn folder_has_configuration_file(target: Target, folder: &Path) -> bool {
|
||||||
|
folder.join(ConfigFormat::Json.into_file_name()).exists()
|
||||||
|
|| folder.join(ConfigFormat::Json5.into_file_name()).exists()
|
||||||
|
|| folder.join(ConfigFormat::Toml.into_file_name()).exists()
|
||||||
|
// platform file names
|
||||||
|
|| folder.join(ConfigFormat::Json.into_platform_file_name(target)).exists()
|
||||||
|
|| folder.join(ConfigFormat::Json5.into_platform_file_name(target)).exists()
|
||||||
|
|| folder.join(ConfigFormat::Toml.into_platform_file_name(target)).exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines if the given file path represents a Tauri configuration file.
|
||||||
|
pub fn is_configuration_file(target: Target, path: &Path) -> bool {
|
||||||
|
path
|
||||||
|
.file_name()
|
||||||
|
.map(|file_name| {
|
||||||
|
file_name == OsStr::new(ConfigFormat::Json.into_file_name())
|
||||||
|
|| file_name == OsStr::new(ConfigFormat::Json5.into_file_name())
|
||||||
|
|| file_name == OsStr::new(ConfigFormat::Toml.into_file_name())
|
||||||
|
// platform file names
|
||||||
|
|| file_name == OsStr::new(ConfigFormat::Json.into_platform_file_name(target))
|
||||||
|
|| file_name == OsStr::new(ConfigFormat::Json5.into_platform_file_name(target))
|
||||||
|
|| file_name == OsStr::new(ConfigFormat::Toml.into_platform_file_name(target))
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the configuration from the given root directory.
|
||||||
|
///
|
||||||
|
/// It first looks for a `tauri.conf.json[5]` or `Tauri.toml` file on the given directory. The file must exist.
|
||||||
|
/// Then it looks for a platform-specific configuration file:
|
||||||
|
/// - `tauri.macos.conf.json[5]` or `Tauri.macos.toml` on macOS
|
||||||
|
/// - `tauri.linux.conf.json[5]` or `Tauri.linux.toml` on Linux
|
||||||
|
/// - `tauri.windows.conf.json[5]` or `Tauri.windows.toml` on Windows
|
||||||
|
/// - `tauri.android.conf.json[5]` or `Tauri.android.toml` on Android
|
||||||
|
/// - `tauri.ios.conf.json[5]` or `Tauri.ios.toml` on iOS
|
||||||
|
/// Merging the configurations using [JSON Merge Patch (RFC 7396)].
|
||||||
|
///
|
||||||
|
/// Returns the raw configuration and used config paths.
|
||||||
|
///
|
||||||
|
/// [JSON Merge Patch (RFC 7396)]: https://datatracker.ietf.org/doc/html/rfc7396.
|
||||||
|
pub fn read_from(target: Target, root_dir: &Path) -> Result<(Value, Vec<PathBuf>), ConfigError> {
|
||||||
|
let (mut config, config_file_path) = parse_value(target, root_dir.join("tauri.conf.json"))?;
|
||||||
|
let mut config_paths = vec![config_file_path];
|
||||||
|
if let Some((platform_config, path)) = read_platform(target, root_dir)? {
|
||||||
|
config_paths.push(path);
|
||||||
|
merge(&mut config, &platform_config);
|
||||||
|
}
|
||||||
|
Ok((config, config_paths))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the platform-specific configuration file from the given root directory if it exists.
|
||||||
|
///
|
||||||
|
/// Check [`read_from`] for more information.
|
||||||
|
pub fn read_platform(
|
||||||
|
target: Target,
|
||||||
|
root_dir: &Path,
|
||||||
|
) -> Result<Option<(Value, PathBuf)>, ConfigError> {
|
||||||
|
let platform_config_path = root_dir.join(ConfigFormat::Json.into_platform_file_name(target));
|
||||||
|
if does_supported_file_name_exist(target, &platform_config_path) {
|
||||||
|
let (platform_config, path): (Value, PathBuf) = parse_value(target, platform_config_path)?;
|
||||||
|
Ok(Some((platform_config, path)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a supported config file exists at path.
|
||||||
|
///
|
||||||
|
/// The passed path is expected to be the path to the "default" configuration format, in this case
|
||||||
|
/// JSON with `.json`.
|
||||||
|
pub fn does_supported_file_name_exist(target: Target, path: impl Into<PathBuf>) -> bool {
|
||||||
|
let path = path.into();
|
||||||
|
let source_file_name = path.file_name().unwrap();
|
||||||
|
let lookup_platform_config = ENABLED_FORMATS
|
||||||
|
.iter()
|
||||||
|
.any(|format| source_file_name == format.into_platform_file_name(target));
|
||||||
|
ENABLED_FORMATS.iter().any(|format| {
|
||||||
|
path
|
||||||
|
.with_file_name(if lookup_platform_config {
|
||||||
|
format.into_platform_file_name(target)
|
||||||
|
} else {
|
||||||
|
format.into_file_name()
|
||||||
|
})
|
||||||
|
.exists()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse the config from path, including alternative formats.
|
||||||
|
///
|
||||||
|
/// Hierarchy:
|
||||||
|
/// 1. Check if `tauri.conf.json` exists
|
||||||
|
/// a. Parse it with `serde_json`
|
||||||
|
/// b. Parse it with `json5` if `serde_json` fails
|
||||||
|
/// c. Return original `serde_json` error if all above steps failed
|
||||||
|
/// 2. Check if `tauri.conf.json5` exists
|
||||||
|
/// a. Parse it with `json5`
|
||||||
|
/// b. Return error if all above steps failed
|
||||||
|
/// 3. Check if `Tauri.json` exists
|
||||||
|
/// a. Parse it with `toml`
|
||||||
|
/// b. Return error if all above steps failed
|
||||||
|
/// 4. Return error if all above steps failed
|
||||||
|
pub fn parse(target: Target, path: impl Into<PathBuf>) -> Result<(Config, PathBuf), ConfigError> {
|
||||||
|
do_parse(target, path.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].
|
||||||
|
pub fn parse_value(
|
||||||
|
target: Target,
|
||||||
|
path: impl Into<PathBuf>,
|
||||||
|
) -> Result<(Value, PathBuf), ConfigError> {
|
||||||
|
do_parse(target, path.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_parse<D: DeserializeOwned>(
|
||||||
|
target: Target,
|
||||||
|
path: PathBuf,
|
||||||
|
) -> Result<(D, PathBuf), ConfigError> {
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let lookup_platform_config = ENABLED_FORMATS
|
||||||
|
.iter()
|
||||||
|
.any(|format| file_name == format.into_platform_file_name(target));
|
||||||
|
|
||||||
|
let json5 = path.with_file_name(if lookup_platform_config {
|
||||||
|
ConfigFormat::Json5.into_platform_file_name(target)
|
||||||
|
} else {
|
||||||
|
ConfigFormat::Json5.into_file_name()
|
||||||
|
});
|
||||||
|
let toml = path.with_file_name(if lookup_platform_config {
|
||||||
|
ConfigFormat::Toml.into_platform_file_name(target)
|
||||||
|
} else {
|
||||||
|
ConfigFormat::Toml.into_file_name()
|
||||||
|
});
|
||||||
|
|
||||||
|
let path_ext = path
|
||||||
|
.extension()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let raw = read_to_string(&path)?;
|
||||||
|
|
||||||
|
// to allow us to easily use the compile-time #[cfg], we always bind
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
let json = do_parse_json(&raw, &path);
|
||||||
|
|
||||||
|
// we also want to support **valid** json5 in the .json extension if the feature is enabled.
|
||||||
|
// if the json5 is not valid the serde_json error for regular json will be returned.
|
||||||
|
// this could be a bit confusing, so we may want to encourage users using json5 to use the
|
||||||
|
// .json5 extension instead of .json
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
let json = {
|
||||||
|
match do_parse_json5(&raw, &path) {
|
||||||
|
json5 @ Ok(_) => json5,
|
||||||
|
|
||||||
|
// assume any errors from json5 in a .json file is because it's not json5
|
||||||
|
Err(_) => json,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
json.map(|j| (j, path))
|
||||||
|
} else if json5.exists() {
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
{
|
||||||
|
let raw = read_to_string(&json5)?;
|
||||||
|
do_parse_json5(&raw, &json5).map(|config| (config, json5))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "config-json5"))]
|
||||||
|
Err(ConfigError::DisabledFormat {
|
||||||
|
extension: ".json5".into(),
|
||||||
|
feature: "config-json5".into(),
|
||||||
|
})
|
||||||
|
} else if toml.exists() {
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
{
|
||||||
|
let raw = read_to_string(&toml)?;
|
||||||
|
do_parse_toml(&raw, &toml).map(|config| (config, toml))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "config-toml"))]
|
||||||
|
Err(ConfigError::DisabledFormat {
|
||||||
|
extension: ".toml".into(),
|
||||||
|
feature: "config-toml".into(),
|
||||||
|
})
|
||||||
|
} else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
|
||||||
|
Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
|
||||||
|
} else {
|
||||||
|
Err(ConfigError::Io {
|
||||||
|
path,
|
||||||
|
error: std::io::ErrorKind::NotFound.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Low-level" helper to parse JSON into a [`Config`].
|
||||||
|
///
|
||||||
|
/// `raw` should be the contents of the file that is represented by `path`.
|
||||||
|
pub fn parse_json(raw: &str, path: &Path) -> Result<Config, ConfigError> {
|
||||||
|
do_parse_json(raw, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Low-level" helper to parse JSON into a JSON [`Value`].
|
||||||
|
///
|
||||||
|
/// `raw` should be the contents of the file that is represented by `path`.
|
||||||
|
pub fn parse_json_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {
|
||||||
|
do_parse_json(raw, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Low-level" helper to parse JSON5 into a [`Config`].
|
||||||
|
///
|
||||||
|
/// `raw` should be the contents of the file that is represented by `path`. This function requires
|
||||||
|
/// the `config-json5` feature to be enabled.
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
pub fn parse_json5(raw: &str, path: &Path) -> Result<Config, ConfigError> {
|
||||||
|
do_parse_json5(raw, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "Low-level" helper to parse JSON5 into a JSON [`Value`].
|
||||||
|
///
|
||||||
|
/// `raw` should be the contents of the file that is represented by `path`. This function requires
|
||||||
|
/// the `config-json5` feature to be enabled.
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
pub fn parse_json5_value(raw: &str, path: &Path) -> Result<Value, ConfigError> {
|
||||||
|
do_parse_json5(raw, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
|
||||||
|
path: path.into(),
|
||||||
|
error: Box::new(error),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].
|
||||||
|
fn read_to_string(path: &Path) -> Result<String, ConfigError> {
|
||||||
|
std::fs::read_to_string(path).map_err(|error| ConfigError::Io {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
3088
apps/tauri/src-tauri/vendor/tauri-utils/src/config_v1/mod.rs
vendored
Normal file
3088
apps/tauri/src-tauri/vendor/tauri-utils/src/config_v1/mod.rs
vendored
Normal file
File diff suppressed because it is too large
Load diff
255
apps/tauri/src-tauri/vendor/tauri-utils/src/config_v1/parse.rs
vendored
Normal file
255
apps/tauri/src-tauri/vendor/tauri-utils/src/config_v1/parse.rs
vendored
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#![allow(clippy::result_large_err)]
|
||||||
|
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// All extensions that are possibly supported, but perhaps not enabled.
|
||||||
|
const EXTENSIONS_SUPPORTED: &[&str] = &["json", "json5", "toml"];
|
||||||
|
|
||||||
|
/// All configuration formats that are currently enabled.
|
||||||
|
const ENABLED_FORMATS: &[ConfigFormat] = &[
|
||||||
|
ConfigFormat::Json,
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
ConfigFormat::Json5,
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
ConfigFormat::Toml,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The available configuration formats.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
enum ConfigFormat {
|
||||||
|
/// The default JSON (tauri.conf.json) format.
|
||||||
|
Json,
|
||||||
|
/// The JSON5 (tauri.conf.json5) format.
|
||||||
|
Json5,
|
||||||
|
/// The TOML (Tauri.toml file) format.
|
||||||
|
Toml,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigFormat {
|
||||||
|
/// Maps the config format to its file name.
|
||||||
|
fn into_file_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Json => "tauri.conf.json",
|
||||||
|
Self::Json5 => "tauri.conf.json5",
|
||||||
|
Self::Toml => "Tauri.toml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_platform_file_name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Json => {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
"tauri.macos.conf.json"
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"tauri.windows.conf.json"
|
||||||
|
} else {
|
||||||
|
"tauri.linux.conf.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Json5 => {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
"tauri.macos.conf.json5"
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"tauri.windows.conf.json5"
|
||||||
|
} else {
|
||||||
|
"tauri.linux.conf.json5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Toml => {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
"Tauri.macos.toml"
|
||||||
|
} else if cfg!(windows) {
|
||||||
|
"Tauri.windows.toml"
|
||||||
|
} else {
|
||||||
|
"Tauri.linux.toml"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents all the errors that can happen while reading the config.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ConfigError {
|
||||||
|
/// Failed to parse from JSON.
|
||||||
|
#[error("unable to parse JSON Tauri config file at {path} because {error}")]
|
||||||
|
FormatJson {
|
||||||
|
/// The path that failed to parse into JSON.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`serde_json::Error`].
|
||||||
|
error: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Failed to parse from JSON5.
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
#[error("unable to parse JSON5 Tauri config file at {path} because {error}")]
|
||||||
|
FormatJson5 {
|
||||||
|
/// The path that failed to parse into JSON5.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`json5::Error`].
|
||||||
|
error: ::json5::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Failed to parse from TOML.
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
#[error("unable to parse toml Tauri config file at {path} because {error}")]
|
||||||
|
FormatToml {
|
||||||
|
/// The path that failed to parse into TOML.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The parsing [`toml::Error`].
|
||||||
|
error: ::toml::de::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Unknown config file name encountered.
|
||||||
|
#[error("unsupported format encountered {0}")]
|
||||||
|
UnsupportedFormat(String),
|
||||||
|
|
||||||
|
/// Known file extension encountered, but corresponding parser is not enabled (cargo features).
|
||||||
|
#[error("supported (but disabled) format encountered {extension} - try enabling `{feature}` ")]
|
||||||
|
DisabledFormat {
|
||||||
|
/// The extension encountered.
|
||||||
|
extension: String,
|
||||||
|
|
||||||
|
/// The cargo feature to enable it.
|
||||||
|
feature: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// A generic IO error with context of what caused it.
|
||||||
|
#[error("unable to read Tauri config file at {path} because {error}")]
|
||||||
|
Io {
|
||||||
|
/// The path the IO error occurred on.
|
||||||
|
path: PathBuf,
|
||||||
|
|
||||||
|
/// The [`std::io::Error`].
|
||||||
|
error: std::io::Error,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`parse`] for specifics, returns a JSON [`Value`] instead of [`Config`].
|
||||||
|
pub fn parse_value(path: impl Into<PathBuf>) -> Result<(Value, PathBuf), ConfigError> {
|
||||||
|
do_parse(path.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_parse<D: DeserializeOwned>(path: PathBuf) -> Result<(D, PathBuf), ConfigError> {
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let lookup_platform_config = ENABLED_FORMATS
|
||||||
|
.iter()
|
||||||
|
.any(|format| file_name == format.into_platform_file_name());
|
||||||
|
|
||||||
|
let json5 = path.with_file_name(if lookup_platform_config {
|
||||||
|
ConfigFormat::Json5.into_platform_file_name()
|
||||||
|
} else {
|
||||||
|
ConfigFormat::Json5.into_file_name()
|
||||||
|
});
|
||||||
|
let toml = path.with_file_name(if lookup_platform_config {
|
||||||
|
ConfigFormat::Toml.into_platform_file_name()
|
||||||
|
} else {
|
||||||
|
ConfigFormat::Toml.into_file_name()
|
||||||
|
});
|
||||||
|
|
||||||
|
let path_ext = path
|
||||||
|
.extension()
|
||||||
|
.map(OsStr::to_string_lossy)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
let raw = read_to_string(&path)?;
|
||||||
|
|
||||||
|
// to allow us to easily use the compile-time #[cfg], we always bind
|
||||||
|
#[allow(clippy::let_and_return)]
|
||||||
|
let json = do_parse_json(&raw, &path);
|
||||||
|
|
||||||
|
// we also want to support **valid** json5 in the .json extension if the feature is enabled.
|
||||||
|
// if the json5 is not valid the serde_json error for regular json will be returned.
|
||||||
|
// this could be a bit confusing, so we may want to encourage users using json5 to use the
|
||||||
|
// .json5 extension instead of .json
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
let json = {
|
||||||
|
match do_parse_json5(&raw, &path) {
|
||||||
|
json5 @ Ok(_) => json5,
|
||||||
|
|
||||||
|
// assume any errors from json5 in a .json file is because it's not json5
|
||||||
|
Err(_) => json,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
json.map(|j| (j, path))
|
||||||
|
} else if json5.exists() {
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
{
|
||||||
|
let raw = read_to_string(&json5)?;
|
||||||
|
do_parse_json5(&raw, &path).map(|config| (config, json5))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "config-json5"))]
|
||||||
|
Err(ConfigError::DisabledFormat {
|
||||||
|
extension: ".json5".into(),
|
||||||
|
feature: "config-json5".into(),
|
||||||
|
})
|
||||||
|
} else if toml.exists() {
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
{
|
||||||
|
let raw = read_to_string(&toml)?;
|
||||||
|
do_parse_toml(&raw, &path).map(|config| (config, toml))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "config-toml"))]
|
||||||
|
Err(ConfigError::DisabledFormat {
|
||||||
|
extension: ".toml".into(),
|
||||||
|
feature: "config-toml".into(),
|
||||||
|
})
|
||||||
|
} else if !EXTENSIONS_SUPPORTED.contains(&path_ext.as_ref()) {
|
||||||
|
Err(ConfigError::UnsupportedFormat(path_ext.to_string()))
|
||||||
|
} else {
|
||||||
|
Err(ConfigError::Io {
|
||||||
|
path,
|
||||||
|
error: std::io::ErrorKind::NotFound.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_parse_json<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
serde_json::from_str(raw).map_err(|error| ConfigError::FormatJson {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "config-json5")]
|
||||||
|
fn do_parse_json5<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
::json5::from_str(raw).map_err(|error| ConfigError::FormatJson5 {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "config-toml")]
|
||||||
|
fn do_parse_toml<D: DeserializeOwned>(raw: &str, path: &Path) -> Result<D, ConfigError> {
|
||||||
|
::toml::from_str(raw).map_err(|error| ConfigError::FormatToml {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to wrap IO errors from [`std::fs::read_to_string`] into a [`ConfigError`].
|
||||||
|
fn read_to_string(path: &Path) -> Result<String, ConfigError> {
|
||||||
|
std::fs::read_to_string(path).map_err(|error| ConfigError::Io {
|
||||||
|
path: path.into(),
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
442
apps/tauri/src-tauri/vendor/tauri-utils/src/html.rs
vendored
Normal file
442
apps/tauri/src-tauri/vendor/tauri-utils/src/html.rs
vendored
Normal file
|
|
@ -0,0 +1,442 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! The module to process HTML in Tauri.
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use html5ever::{
|
||||||
|
LocalName,
|
||||||
|
interface::QualName,
|
||||||
|
namespace_url, ns,
|
||||||
|
serialize::{HtmlSerializer, SerializeOpts, Serializer, TraversalScope},
|
||||||
|
tendril::TendrilSink,
|
||||||
|
};
|
||||||
|
pub use kuchiki::NodeRef;
|
||||||
|
use kuchiki::{Attribute, ExpandedName, NodeData};
|
||||||
|
use serde::Serialize;
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
use serialize_to_javascript::DefaultTemplate;
|
||||||
|
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
use crate::pattern::isolation::IsolationJavascriptCodegen;
|
||||||
|
use crate::{
|
||||||
|
assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
|
||||||
|
config::{DisabledCspModificationKind, PatternKind},
|
||||||
|
};
|
||||||
|
|
||||||
|
// taken from <https://github.com/kuchiki-rs/kuchiki/blob/57ee6920d835315a498e748ba4b07a851ae5e498/src/serializer.rs#L12>
|
||||||
|
fn serialize_node_ref_internal<S: Serializer>(
|
||||||
|
node: &NodeRef,
|
||||||
|
serializer: &mut S,
|
||||||
|
traversal_scope: TraversalScope,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
match (traversal_scope, node.data()) {
|
||||||
|
(ref scope, NodeData::Element(element)) => {
|
||||||
|
if *scope == TraversalScope::IncludeNode {
|
||||||
|
let attrs = element.attributes.borrow();
|
||||||
|
|
||||||
|
// Unfortunately we need to allocate something to hold these &'a QualName
|
||||||
|
let attrs = attrs
|
||||||
|
.map
|
||||||
|
.iter()
|
||||||
|
.map(|(name, attr)| {
|
||||||
|
(
|
||||||
|
QualName::new(attr.prefix.clone(), name.ns.clone(), name.local.clone()),
|
||||||
|
&attr.value,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
serializer.start_elem(
|
||||||
|
element.name.clone(),
|
||||||
|
attrs.iter().map(|&(ref name, value)| (name, &**value)),
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let children = match element.template_contents.as_ref() {
|
||||||
|
Some(template_root) => template_root.children(),
|
||||||
|
None => node.children(),
|
||||||
|
};
|
||||||
|
for child in children {
|
||||||
|
serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
|
||||||
|
}
|
||||||
|
|
||||||
|
if *scope == TraversalScope::IncludeNode {
|
||||||
|
serializer.end_elem(element.name.clone())?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
(_, &NodeData::DocumentFragment) | (_, &NodeData::Document(_)) => {
|
||||||
|
for child in node.children() {
|
||||||
|
serialize_node_ref_internal(&child, serializer, TraversalScope::IncludeNode)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
(TraversalScope::ChildrenOnly(_), _) => Ok(()),
|
||||||
|
|
||||||
|
(TraversalScope::IncludeNode, NodeData::Doctype(doctype)) => {
|
||||||
|
serializer.write_doctype(&doctype.name).map_err(Into::into)
|
||||||
|
}
|
||||||
|
(TraversalScope::IncludeNode, NodeData::Text(text)) => {
|
||||||
|
serializer.write_text(&text.borrow()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
(TraversalScope::IncludeNode, NodeData::Comment(text)) => {
|
||||||
|
serializer.write_comment(&text.borrow()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
(TraversalScope::IncludeNode, NodeData::ProcessingInstruction(contents)) => {
|
||||||
|
let contents = contents.borrow();
|
||||||
|
serializer
|
||||||
|
.write_processing_instruction(&contents.0, &contents.1)
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializes the node to HTML.
|
||||||
|
pub fn serialize_node(node: &NodeRef) -> Vec<u8> {
|
||||||
|
let mut u8_vec = Vec::new();
|
||||||
|
let mut ser = HtmlSerializer::new(
|
||||||
|
&mut u8_vec,
|
||||||
|
SerializeOpts {
|
||||||
|
traversal_scope: TraversalScope::IncludeNode,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
serialize_node_ref_internal(node, &mut ser, TraversalScope::IncludeNode).unwrap();
|
||||||
|
u8_vec
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the given HTML string.
|
||||||
|
pub fn parse(html: String) -> NodeRef {
|
||||||
|
kuchiki::parse_html().one(html).document_node
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_head<F: FnOnce(&NodeRef)>(document: &NodeRef, f: F) {
|
||||||
|
if let Ok(ref node) = document.select_first("head") {
|
||||||
|
f(node.as_node())
|
||||||
|
} else {
|
||||||
|
let node = NodeRef::new_element(
|
||||||
|
QualName::new(None, ns!(html), LocalName::from("head")),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
f(&node);
|
||||||
|
document.prepend(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_nonce(document: &NodeRef, selector: &str, token: &str) {
|
||||||
|
if let Ok(elements) = document.select(selector) {
|
||||||
|
for target in elements {
|
||||||
|
let node = target.as_node();
|
||||||
|
let element = node.as_element().unwrap();
|
||||||
|
|
||||||
|
let mut attrs = element.attributes.borrow_mut();
|
||||||
|
// if the node already has the `nonce` attribute, skip it
|
||||||
|
if attrs.get("nonce").is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attrs.insert("nonce", token.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inject nonce tokens to all scripts and styles.
|
||||||
|
pub fn inject_nonce_token(
|
||||||
|
document: &NodeRef,
|
||||||
|
dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
|
||||||
|
) {
|
||||||
|
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
|
||||||
|
inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
|
||||||
|
}
|
||||||
|
if dangerous_disable_asset_csp_modification.can_modify("style-src") {
|
||||||
|
inject_nonce(document, "style", STYLE_NONCE_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a content security policy to the HTML.
|
||||||
|
pub fn inject_csp(document: &NodeRef, csp: &str) {
|
||||||
|
with_head(document, |head| {
|
||||||
|
head.append(create_csp_meta_tag(csp));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_csp_meta_tag(csp: &str) -> NodeRef {
|
||||||
|
NodeRef::new_element(
|
||||||
|
QualName::new(None, ns!(html), LocalName::from("meta")),
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
ExpandedName::new(ns!(), LocalName::from("http-equiv")),
|
||||||
|
Attribute {
|
||||||
|
prefix: None,
|
||||||
|
value: "Content-Security-Policy".into(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ExpandedName::new(ns!(), LocalName::from("content")),
|
||||||
|
Attribute {
|
||||||
|
prefix: None,
|
||||||
|
value: csp.into(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The shape of the JavaScript Pattern config
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase", tag = "pattern")]
|
||||||
|
pub enum PatternObject {
|
||||||
|
/// Brownfield pattern.
|
||||||
|
Brownfield,
|
||||||
|
/// Isolation pattern. Recommended for security purposes.
|
||||||
|
Isolation {
|
||||||
|
/// Which `IsolationSide` this `PatternObject` is getting injected into
|
||||||
|
side: IsolationSide,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PatternKind> for PatternObject {
|
||||||
|
fn from(pattern_kind: &PatternKind) -> Self {
|
||||||
|
match pattern_kind {
|
||||||
|
PatternKind::Brownfield => Self::Brownfield,
|
||||||
|
PatternKind::Isolation { .. } => Self::Isolation {
|
||||||
|
side: IsolationSide::default(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where the JavaScript is injected to
|
||||||
|
#[derive(Debug, Serialize, Default)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum IsolationSide {
|
||||||
|
/// Original frame, the Brownfield application
|
||||||
|
#[default]
|
||||||
|
Original,
|
||||||
|
/// Secure frame, the isolation security application
|
||||||
|
Secure,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects the Isolation JavaScript to a codegen time document.
|
||||||
|
///
|
||||||
|
/// Note: This function is not considered part of the stable API.
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
pub fn inject_codegen_isolation_script(document: &NodeRef) {
|
||||||
|
with_head(document, |head| {
|
||||||
|
let script = NodeRef::new_element(
|
||||||
|
QualName::new(None, ns!(html), "script".into()),
|
||||||
|
vec![(
|
||||||
|
ExpandedName::new(ns!(), LocalName::from("nonce")),
|
||||||
|
Attribute {
|
||||||
|
prefix: None,
|
||||||
|
value: SCRIPT_NONCE_TOKEN.into(),
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
script.append(NodeRef::new_text(
|
||||||
|
IsolationJavascriptCodegen {}
|
||||||
|
.render_default(&Default::default())
|
||||||
|
.expect("unable to render codegen isolation script template")
|
||||||
|
.into_string(),
|
||||||
|
));
|
||||||
|
|
||||||
|
head.prepend(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporary workaround for Windows not allowing requests
|
||||||
|
///
|
||||||
|
/// Note: this does not prevent path traversal due to the isolation application expectation that it
|
||||||
|
/// is secure.
|
||||||
|
pub fn inline_isolation(document: &NodeRef, dir: &Path) {
|
||||||
|
for script in document
|
||||||
|
.select("script[src]")
|
||||||
|
.expect("unable to parse document for scripts")
|
||||||
|
{
|
||||||
|
let src = {
|
||||||
|
let attributes = script.attributes.borrow();
|
||||||
|
attributes
|
||||||
|
.get(LocalName::from("src"))
|
||||||
|
.expect("script with src attribute has no src value")
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path = PathBuf::from(src);
|
||||||
|
if path.has_root() {
|
||||||
|
path = path
|
||||||
|
.strip_prefix("/")
|
||||||
|
.expect("Tauri \"Isolation\" Pattern only supports relative or absolute (`/`) paths.")
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = std::fs::read_to_string(dir.join(path)).expect("unable to find isolation file");
|
||||||
|
script.as_node().append(NodeRef::new_text(file));
|
||||||
|
|
||||||
|
let mut attributes = script.attributes.borrow_mut();
|
||||||
|
attributes.remove(LocalName::from("src"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Verify this, this is not found in the HTML spec, see https://github.com/tauri-apps/tauri/pull/14265#discussion_r2415396842
|
||||||
|
/// Normalize line endings in script content to match what the browser uses for CSP hashing.
|
||||||
|
///
|
||||||
|
/// According to the HTML spec, browsers normalize:
|
||||||
|
/// - `\r\n` → `\n`
|
||||||
|
/// - `\r` → `\n`
|
||||||
|
pub fn normalize_script_for_csp(input: &[u8]) -> Vec<u8> {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < input.len() {
|
||||||
|
match input[i] {
|
||||||
|
b'\r' => {
|
||||||
|
if i + 1 < input.len() && input[i + 1] == b'\n' {
|
||||||
|
// CRLF → LF
|
||||||
|
output.push(b'\n');
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
// Lone CR → LF
|
||||||
|
output.push(b'\n');
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
output.push(input[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csp() {
|
||||||
|
let htmls = vec![
|
||||||
|
"<html><head></head></html>".to_string(),
|
||||||
|
"<html></html>".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for html in htmls {
|
||||||
|
let document = parse(html);
|
||||||
|
let csp = "csp-string";
|
||||||
|
inject_csp(&document, csp);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_node(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><meta http-equiv="Content-Security-Policy" content="{csp}"></head><body></body></html>"#,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_script_for_csp_test() {
|
||||||
|
let js = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\r// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\r\n\r\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\r\n return payload\r\n}\r\n";
|
||||||
|
let expected = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n return payload\n}\n";
|
||||||
|
|
||||||
|
assert_eq!(normalize_script_for_csp(js.as_bytes()), expected.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_and_serialize_roundtrips() {
|
||||||
|
let htmls = [
|
||||||
|
"<html><head><title>Test</title></head><body><h1>Hello</h1></body></html>",
|
||||||
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
||||||
|
];
|
||||||
|
|
||||||
|
for html in htmls {
|
||||||
|
let parsed = parse(html.to_string());
|
||||||
|
let serialized = serialize_node(&parsed);
|
||||||
|
let result = String::from_utf8(serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_to_scripts() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_node(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><script src="http://example.com/script.js" nonce="{SCRIPT_NONCE_TOKEN}"></script></head><body></body></html>"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_to_styles() {
|
||||||
|
let html = r#"<html><head><style>body { color: red; }</style></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_node(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><style nonce="{STYLE_NONCE_TOKEN}">body {{ color: red; }}</style></head><body></body></html>"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_skips_existing() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js" nonce="existing"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8(serialize_node(&document)).unwrap(), html);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_respects_disabled_modification() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(true));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_node(&document)).unwrap(),
|
||||||
|
r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_isolation_replaces_src_with_content() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut file = tempfile::NamedTempFile::with_suffix_in(".js", &temp_dir).unwrap();
|
||||||
|
file.write_all(b"console.log('test');").unwrap();
|
||||||
|
let file_name = file.path().file_name().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
|
let html =
|
||||||
|
format!(r#"<html><head><script src="/{file_name}"></script></head><body></body></html>"#);
|
||||||
|
let document = parse(html);
|
||||||
|
inline_isolation(&document, temp_dir.path());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_node(&document)).unwrap(),
|
||||||
|
r#"<html><head><script>console.log('test');</script></head><body></body></html>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
335
apps/tauri/src-tauri/vendor/tauri-utils/src/html2.rs
vendored
Normal file
335
apps/tauri/src-tauri/vendor/tauri-utils/src/html2.rs
vendored
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! The module to process HTML in Tauri.
|
||||||
|
//!
|
||||||
|
//! # Stability
|
||||||
|
//!
|
||||||
|
//! This is utility used in Tauri internally and not considered part of the stable API.
|
||||||
|
//! If you use it, note that it may include breaking changes in the future.
|
||||||
|
|
||||||
|
use dom_query::NodeRef;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
|
||||||
|
config::DisabledCspModificationKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency might receive updates in minor releases.
|
||||||
|
pub use dom_query::Document;
|
||||||
|
|
||||||
|
/// Serializes the document to HTML.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
pub fn serialize_doc(document: &Document) -> Vec<u8> {
|
||||||
|
document.html().as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the given HTML string.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
pub fn parse_doc(html: String) -> Document {
|
||||||
|
Document::from(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_head(document: &Document) -> NodeRef<'_> {
|
||||||
|
document.head().unwrap_or_else(|| {
|
||||||
|
let html = document.html_root();
|
||||||
|
let head = document.tree.new_element("head");
|
||||||
|
html.prepend_child(&head);
|
||||||
|
head
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inject_nonce(document: &Document, selector: &str, token: &str) {
|
||||||
|
let elements = document.select(selector);
|
||||||
|
for elem in elements.nodes() {
|
||||||
|
// if the node already has the `nonce` attribute, skip it
|
||||||
|
if elem.attr("nonce").is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
elem.set_attr("nonce", token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inject nonce tokens to all scripts and styles.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
pub fn inject_nonce_token(
|
||||||
|
document: &Document,
|
||||||
|
dangerous_disable_asset_csp_modification: &DisabledCspModificationKind,
|
||||||
|
) {
|
||||||
|
if dangerous_disable_asset_csp_modification.can_modify("script-src") {
|
||||||
|
inject_nonce(document, "script[src^='http']", SCRIPT_NONCE_TOKEN);
|
||||||
|
}
|
||||||
|
if dangerous_disable_asset_csp_modification.can_modify("style-src") {
|
||||||
|
inject_nonce(document, "style", STYLE_NONCE_TOKEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a content security policy to the HTML.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
pub fn inject_csp(document: &Document, csp: &str) {
|
||||||
|
let head = ensure_head(document);
|
||||||
|
let meta_tag = document.tree.new_element("meta");
|
||||||
|
meta_tag.set_attr("http-equiv", "Content-Security-Policy");
|
||||||
|
meta_tag.set_attr("content", csp);
|
||||||
|
head.append_child(&meta_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a content security policy to the HTML.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
pub fn append_script_to_head(document: &Document, script: &str) {
|
||||||
|
let head = ensure_head(document);
|
||||||
|
let script_tag = document.tree.new_element("script");
|
||||||
|
script_tag.set_text(script);
|
||||||
|
head.prepend_child(&script_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects the Isolation JavaScript to a codegen time document.
|
||||||
|
///
|
||||||
|
/// Note: This function is not considered part of the stable API.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
pub fn inject_codegen_isolation_script(document: &Document) {
|
||||||
|
use crate::pattern::isolation::IsolationJavascriptCodegen;
|
||||||
|
use serialize_to_javascript::DefaultTemplate;
|
||||||
|
|
||||||
|
let head = ensure_head(document);
|
||||||
|
|
||||||
|
let script_content = IsolationJavascriptCodegen {}
|
||||||
|
.render_default(&Default::default())
|
||||||
|
.expect("unable to render codegen isolation script template")
|
||||||
|
.into_string();
|
||||||
|
|
||||||
|
let script_tag = document.tree.new_element("script");
|
||||||
|
script_tag.set_attr("nonce", SCRIPT_NONCE_TOKEN);
|
||||||
|
script_tag.set_text(script_content);
|
||||||
|
|
||||||
|
head.prepend_child(&script_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporary workaround for Windows not allowing requests
|
||||||
|
///
|
||||||
|
/// Note: this does not prevent path traversal due to the isolation application expectation that it
|
||||||
|
/// is secure.
|
||||||
|
///
|
||||||
|
/// # Stability
|
||||||
|
///
|
||||||
|
/// This dependency [`dom_query`] for [`Document`] might receive updates in minor releases.
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
pub fn inline_isolation(document: &Document, dir: &std::path::Path) {
|
||||||
|
let scripts = document.select("script[src]");
|
||||||
|
|
||||||
|
for script in scripts.nodes() {
|
||||||
|
let src = match script.attr("src") {
|
||||||
|
Some(s) => s.to_string(),
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut path = std::path::PathBuf::from(src);
|
||||||
|
if path.has_root() {
|
||||||
|
path = path
|
||||||
|
.strip_prefix("/")
|
||||||
|
.expect("Tauri \"Isolation\" Pattern only supports relative or absolute (`/`) paths.")
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = std::fs::read_to_string(dir.join(path)).expect("unable to find isolation file");
|
||||||
|
|
||||||
|
script.set_text(file);
|
||||||
|
script.remove_attr("src");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Verify this, this is not found in the HTML spec, see https://github.com/tauri-apps/tauri/pull/14265#discussion_r2415396842
|
||||||
|
/// Normalize line endings in script content to match what the browser uses for CSP hashing.
|
||||||
|
///
|
||||||
|
/// According to the HTML spec, browsers normalize:
|
||||||
|
/// - `\r\n` → `\n`
|
||||||
|
/// - `\r` → `\n`
|
||||||
|
pub fn normalize_script_for_csp(input: &[u8]) -> Vec<u8> {
|
||||||
|
let mut output = Vec::with_capacity(input.len());
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while i < input.len() {
|
||||||
|
match input[i] {
|
||||||
|
b'\r' => {
|
||||||
|
if i + 1 < input.len() && input[i + 1] == b'\n' {
|
||||||
|
// CRLF → LF
|
||||||
|
output.push(b'\n');
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
// Lone CR → LF
|
||||||
|
output.push(b'\n');
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
output.push(input[i]);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
assets::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn csp() {
|
||||||
|
let htmls = vec![
|
||||||
|
"<html><head></head></html>".to_string(),
|
||||||
|
"<html></html>".to_string(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for html in htmls {
|
||||||
|
let document = parse_doc(html);
|
||||||
|
let csp = "csp-string";
|
||||||
|
inject_csp(&document, csp);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><meta http-equiv="Content-Security-Policy" content="{csp}"></head><body></body></html>"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_script_for_csp_test() {
|
||||||
|
let js = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\r// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\r\n\r\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\r\n return payload\r\n}\r\n";
|
||||||
|
let expected = "// Copyright 2019-2024 Tauri Programme within The Commons Conservancy\n// SPDX-License-Identifier: Apache-2.0\n// SPDX-License-Identifier: MIT\n\nwindow.__TAURI_ISOLATION_HOOK__ = (payload, options) => {\n return payload\n}\n";
|
||||||
|
|
||||||
|
assert_eq!(normalize_script_for_csp(js.as_bytes()), expected.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_and_serialize_roundtrips() {
|
||||||
|
let htmls = [
|
||||||
|
"<html><head><title>Test</title></head><body><h1>Hello</h1></body></html>",
|
||||||
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
||||||
|
];
|
||||||
|
|
||||||
|
for html in htmls {
|
||||||
|
let parsed = parse_doc(html.to_string());
|
||||||
|
let serialized = serialize_doc(&parsed);
|
||||||
|
let result = String::from_utf8(serialized).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_to_scripts() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse_doc(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><script src="http://example.com/script.js" nonce="{SCRIPT_NONCE_TOKEN}"></script></head><body></body></html>"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_to_styles() {
|
||||||
|
let html = r#"<html><head><style>body { color: red; }</style></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse_doc(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
format!(
|
||||||
|
r#"<html><head><style nonce="{STYLE_NONCE_TOKEN}">body {{ color: red; }}</style></head><body></body></html>"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_script_to_head_test() {
|
||||||
|
let html = r#"<html><head></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse_doc(html.to_string());
|
||||||
|
append_script_to_head(&document, r#"console.log('Test')"#);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
format!(r#"<html><head><script>console.log('Test')</script></head><body></body></html>"#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_skips_existing() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js" nonce="existing"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse_doc(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(false));
|
||||||
|
|
||||||
|
assert_eq!(String::from_utf8(serialize_doc(&document)).unwrap(), html);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inject_nonce_respects_disabled_modification() {
|
||||||
|
let html = r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#;
|
||||||
|
|
||||||
|
let document = parse_doc(html.to_string());
|
||||||
|
inject_nonce_token(&document, &config::DisabledCspModificationKind::Flag(true));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
r#"<html><head><script src="http://example.com/script.js"></script></head><body></body></html>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
fn inline_isolation_replaces_src_with_content() {
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let mut file = tempfile::NamedTempFile::with_suffix_in(".js", &temp_dir).unwrap();
|
||||||
|
file.write_all(b"console.log('test');").unwrap();
|
||||||
|
let file_name = file.path().file_name().unwrap().to_str().unwrap();
|
||||||
|
|
||||||
|
let html =
|
||||||
|
format!(r#"<html><head><script src="/{file_name}"></script></head><body></body></html>"#);
|
||||||
|
let document = parse_doc(html);
|
||||||
|
inline_isolation(&document, temp_dir.path());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
String::from_utf8(serialize_doc(&document)).unwrap(),
|
||||||
|
r#"<html><head><script>console.log('test');</script></head><body></body></html>"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
apps/tauri/src-tauri/vendor/tauri-utils/src/io.rs
vendored
Normal file
47
apps/tauri/src-tauri/vendor/tauri-utils/src/io.rs
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! IO helpers.
|
||||||
|
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
/// Read all bytes until a newline (the `0xA` byte) or a carriage return (`\r`) is reached, and append them to the provided buffer.
|
||||||
|
///
|
||||||
|
/// Adapted from <https://doc.rust-lang.org/std/io/trait.BufRead.html#method.read_line>.
|
||||||
|
pub fn read_line<R: BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) -> std::io::Result<usize> {
|
||||||
|
let mut read = 0;
|
||||||
|
loop {
|
||||||
|
let (done, used) = {
|
||||||
|
let available = match r.fill_buf() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
|
||||||
|
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
match memchr::memchr(b'\n', available) {
|
||||||
|
Some(i) => {
|
||||||
|
let end = i + 1;
|
||||||
|
buf.extend_from_slice(&available[..end]);
|
||||||
|
(true, end)
|
||||||
|
}
|
||||||
|
None => match memchr::memchr(b'\r', available) {
|
||||||
|
Some(i) => {
|
||||||
|
let end = i + 1;
|
||||||
|
buf.extend_from_slice(&available[..end]);
|
||||||
|
(true, end)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
buf.extend_from_slice(available);
|
||||||
|
(false, available.len())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.consume(used);
|
||||||
|
read += used;
|
||||||
|
if done || used == 0 {
|
||||||
|
return Ok(read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
404
apps/tauri/src-tauri/vendor/tauri-utils/src/lib.rs
vendored
Normal file
404
apps/tauri/src-tauri/vendor/tauri-utils/src/lib.rs
vendored
Normal file
|
|
@ -0,0 +1,404 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! This crate contains common code that is reused in many places and offers useful utilities like parsing configuration files, detecting platform triples, injecting the CSP, and managing assets.
|
||||||
|
|
||||||
|
#![doc(
|
||||||
|
html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png",
|
||||||
|
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/.github/icon.png"
|
||||||
|
)]
|
||||||
|
#![warn(missing_docs, rust_2018_idioms)]
|
||||||
|
#![allow(clippy::deprecated_semver)]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::Display,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use semver::Version;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
||||||
|
pub mod acl;
|
||||||
|
pub mod assets;
|
||||||
|
pub mod config;
|
||||||
|
pub mod config_v1;
|
||||||
|
#[cfg(feature = "html-manipulation")]
|
||||||
|
pub mod html;
|
||||||
|
#[cfg(feature = "html-manipulation-2")]
|
||||||
|
pub mod html2;
|
||||||
|
pub mod io;
|
||||||
|
pub mod mime_type;
|
||||||
|
pub mod platform;
|
||||||
|
pub mod plugin;
|
||||||
|
/// Prepare application resources and sidecars.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
pub mod resources;
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
pub mod tokens;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
pub mod build;
|
||||||
|
|
||||||
|
/// Application pattern.
|
||||||
|
pub mod pattern;
|
||||||
|
|
||||||
|
/// `tauri::App` package information.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PackageInfo {
|
||||||
|
/// App name
|
||||||
|
pub name: String,
|
||||||
|
/// App version
|
||||||
|
pub version: Version,
|
||||||
|
/// The crate authors.
|
||||||
|
pub authors: &'static str,
|
||||||
|
/// The crate description.
|
||||||
|
pub description: &'static str,
|
||||||
|
/// The crate name.
|
||||||
|
pub crate_name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
|
mod window_effects {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
/// Platform-specific window effects
|
||||||
|
pub enum WindowEffect {
|
||||||
|
/// A default material appropriate for the view's effectiveAppearance. **macOS 10.14-**
|
||||||
|
#[deprecated(
|
||||||
|
since = "macOS 10.14",
|
||||||
|
note = "You should instead choose an appropriate semantic material."
|
||||||
|
)]
|
||||||
|
AppearanceBased,
|
||||||
|
/// **macOS 10.14-**
|
||||||
|
#[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
|
||||||
|
Light,
|
||||||
|
/// **macOS 10.14-**
|
||||||
|
#[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
|
||||||
|
Dark,
|
||||||
|
/// **macOS 10.14-**
|
||||||
|
#[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
|
||||||
|
MediumLight,
|
||||||
|
/// **macOS 10.14-**
|
||||||
|
#[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")]
|
||||||
|
UltraDark,
|
||||||
|
/// **macOS 10.10+**
|
||||||
|
Titlebar,
|
||||||
|
/// **macOS 10.10+**
|
||||||
|
Selection,
|
||||||
|
/// **macOS 10.11+**
|
||||||
|
Menu,
|
||||||
|
/// **macOS 10.11+**
|
||||||
|
Popover,
|
||||||
|
/// **macOS 10.11+**
|
||||||
|
Sidebar,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
HeaderView,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
Sheet,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
WindowBackground,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
HudWindow,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
FullScreenUI,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
Tooltip,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
ContentBackground,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
UnderWindowBackground,
|
||||||
|
/// **macOS 10.14+**
|
||||||
|
UnderPageBackground,
|
||||||
|
/// Mica effect that matches the system dark preference **Windows 11 Only**
|
||||||
|
Mica,
|
||||||
|
/// Mica effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
|
||||||
|
MicaDark,
|
||||||
|
/// Mica effect with light mode **Windows 11 Only**
|
||||||
|
MicaLight,
|
||||||
|
/// Tabbed effect that matches the system dark preference **Windows 11 Only**
|
||||||
|
Tabbed,
|
||||||
|
/// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
|
||||||
|
TabbedDark,
|
||||||
|
/// Tabbed effect with light mode **Windows 11 Only**
|
||||||
|
TabbedLight,
|
||||||
|
/// **Windows 7/10/11(22H1) Only**
|
||||||
|
///
|
||||||
|
/// ## Notes
|
||||||
|
///
|
||||||
|
/// This effect has bad performance when resizing/dragging the window on Windows 11 build 22621.
|
||||||
|
Blur,
|
||||||
|
/// **Windows 10/11 Only**
|
||||||
|
///
|
||||||
|
/// ## Notes
|
||||||
|
///
|
||||||
|
/// This effect has bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000.
|
||||||
|
Acrylic,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Window effect state **macOS only**
|
||||||
|
///
|
||||||
|
/// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum WindowEffectState {
|
||||||
|
/// Make window effect state follow the window's active state
|
||||||
|
FollowsWindowActiveState,
|
||||||
|
/// Make window effect state always active
|
||||||
|
Active,
|
||||||
|
/// Make window effect state always inactive
|
||||||
|
Inactive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use window_effects::{WindowEffect, WindowEffectState};
|
||||||
|
|
||||||
|
/// How the window title bar should be displayed on macOS.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Copy, Default)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum TitleBarStyle {
|
||||||
|
/// A normal title bar.
|
||||||
|
#[default]
|
||||||
|
Visible,
|
||||||
|
/// Makes the title bar transparent, so the window background color is shown instead.
|
||||||
|
///
|
||||||
|
/// Useful if you don't need to have actual HTML under the title bar. This lets you avoid the caveats of using `TitleBarStyle::Overlay`. Will be more useful when Tauri lets you set a custom window background color.
|
||||||
|
Transparent,
|
||||||
|
/// Shows the title bar as a transparent overlay over the window's content.
|
||||||
|
///
|
||||||
|
/// Keep in mind:
|
||||||
|
/// - The height of the title bar is different on different OS versions, which can lead to window the controls and title not being where you don't expect.
|
||||||
|
/// - You need to define a custom drag region to make your window draggable, however due to a limitation you can't drag the window when it's not in focus <https://github.com/tauri-apps/tauri/issues/4316>.
|
||||||
|
/// - The color of the window title depends on the system theme.
|
||||||
|
Overlay,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TitleBarStyle {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.to_string().as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TitleBarStyle {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(match s.to_lowercase().as_str() {
|
||||||
|
"transparent" => Self::Transparent,
|
||||||
|
"overlay" => Self::Overlay,
|
||||||
|
_ => Self::Visible,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TitleBarStyle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Visible => "Visible",
|
||||||
|
Self::Transparent => "Transparent",
|
||||||
|
Self::Overlay => "Overlay",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System theme.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Theme {
|
||||||
|
/// Light theme.
|
||||||
|
Light,
|
||||||
|
/// Dark theme.
|
||||||
|
Dark,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Theme {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(self.to_string().as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Theme {
|
||||||
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(match s.to_lowercase().as_str() {
|
||||||
|
"dark" => Self::Dark,
|
||||||
|
_ => Self::Light,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Theme {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::Light => "light",
|
||||||
|
Self::Dark => "dark",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about environment variables.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct Env {
|
||||||
|
/// The APPIMAGE environment variable.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub appimage: Option<std::ffi::OsString>,
|
||||||
|
/// The APPDIR environment variable.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub appdir: Option<std::ffi::OsString>,
|
||||||
|
/// The command line arguments of the current process.
|
||||||
|
pub args_os: Vec<OsString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::derivable_impls)]
|
||||||
|
impl Default for Env {
|
||||||
|
fn default() -> Self {
|
||||||
|
let args_os = std::env::args_os().collect();
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let env = Self {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
appimage: std::env::var_os("APPIMAGE"),
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
appdir: std::env::var_os("APPDIR"),
|
||||||
|
args_os,
|
||||||
|
};
|
||||||
|
if env.appimage.is_some() || env.appdir.is_some() {
|
||||||
|
// validate that we're actually running on an AppImage
|
||||||
|
// an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`
|
||||||
|
// see <https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501>
|
||||||
|
// note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.
|
||||||
|
let is_temp = std::env::current_exe()
|
||||||
|
.map(|p| {
|
||||||
|
p.display()
|
||||||
|
.to_string()
|
||||||
|
.starts_with(&format!("{}/.mount_", std::env::temp_dir().display()))
|
||||||
|
})
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
if !is_temp {
|
||||||
|
log::warn!(
|
||||||
|
"`APPDIR` or `APPIMAGE` environment variable found but this application was not detected as an AppImage; this might be a security issue."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env
|
||||||
|
}
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
Self { args_os }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result type of `tauri-utils`.
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// The error type of `tauri-utils`.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
/// Target triple architecture error
|
||||||
|
#[error("Unable to determine target-architecture")]
|
||||||
|
Architecture,
|
||||||
|
/// Target triple OS error
|
||||||
|
#[error("Unable to determine target-os")]
|
||||||
|
Os,
|
||||||
|
/// Target triple environment error
|
||||||
|
#[error("Unable to determine target-environment")]
|
||||||
|
Environment,
|
||||||
|
/// Tried to get resource on an unsupported platform
|
||||||
|
#[error("Unsupported platform for reading resources")]
|
||||||
|
UnsupportedPlatform,
|
||||||
|
/// Get parent process error
|
||||||
|
#[error("Could not get parent process")]
|
||||||
|
ParentProcess,
|
||||||
|
/// Get parent process PID error
|
||||||
|
#[error("Could not get parent PID")]
|
||||||
|
ParentPid,
|
||||||
|
/// Get child process error
|
||||||
|
#[error("Could not get child process")]
|
||||||
|
ChildProcess,
|
||||||
|
/// IO error
|
||||||
|
#[error("{0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
/// Invalid pattern.
|
||||||
|
#[error("invalid pattern `{0}`. Expected either `brownfield` or `isolation`.")]
|
||||||
|
InvalidPattern(String),
|
||||||
|
/// Invalid glob pattern.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error("{0}")]
|
||||||
|
GlobPattern(#[from] glob::PatternError),
|
||||||
|
/// Failed to use glob pattern.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error("`{0}`")]
|
||||||
|
Glob(#[from] glob::GlobError),
|
||||||
|
/// Glob pattern did not find any results.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error("glob pattern {0} path not found or didn't match any files.")]
|
||||||
|
GlobPathNotFound(String),
|
||||||
|
/// Error walking directory.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error("{0}")]
|
||||||
|
WalkdirError(#[from] walkdir::Error),
|
||||||
|
/// Not allowed to walk dir.
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error(
|
||||||
|
"could not walk directory `{0}`, try changing `allow_walk` to true on the `ResourcePaths` constructor."
|
||||||
|
)]
|
||||||
|
NotAllowedToWalkDir(std::path::PathBuf),
|
||||||
|
/// Resource path doesn't exist
|
||||||
|
#[cfg(feature = "resources")]
|
||||||
|
#[error("resource path `{0}` doesn't exist")]
|
||||||
|
ResourcePathNotFound(std::path::PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reconstructs a path from its components using the platform separator then converts it to String and removes UNC prefixes on Windows if it exists.
|
||||||
|
pub fn display_path<P: AsRef<Path>>(p: P) -> String {
|
||||||
|
dunce::simplified(&p.as_ref().components().collect::<PathBuf>())
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write the file only if the content of the existing file (if any) is different.
|
||||||
|
///
|
||||||
|
/// This will always write unless the file exists with identical content.
|
||||||
|
pub fn write_if_changed<P, C>(path: P, content: C) -> std::io::Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
C: AsRef<[u8]>,
|
||||||
|
{
|
||||||
|
if let Ok(existing) = std::fs::read(&path)
|
||||||
|
&& existing == content.as_ref()
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::write(path, content)
|
||||||
|
}
|
||||||
154
apps/tauri/src-tauri/vendor/tauri-utils/src/mime_type.rs
vendored
Normal file
154
apps/tauri/src-tauri/vendor/tauri-utils/src/mime_type.rs
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Determine a mime type from a URI or file contents.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
const MIMETYPE_PLAIN: &str = "text/plain";
|
||||||
|
|
||||||
|
/// [Web Compatible MimeTypes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#important_mime_types_for_web_developers)
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum MimeType {
|
||||||
|
Css,
|
||||||
|
Csv,
|
||||||
|
Html,
|
||||||
|
Ico,
|
||||||
|
Js,
|
||||||
|
Json,
|
||||||
|
Jsonld,
|
||||||
|
Mp4,
|
||||||
|
OctetStream,
|
||||||
|
Rtf,
|
||||||
|
Svg,
|
||||||
|
Txt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for MimeType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mime = match self {
|
||||||
|
MimeType::Css => "text/css",
|
||||||
|
MimeType::Csv => "text/csv",
|
||||||
|
MimeType::Html => "text/html",
|
||||||
|
MimeType::Ico => "image/vnd.microsoft.icon",
|
||||||
|
MimeType::Js => "text/javascript",
|
||||||
|
MimeType::Json => "application/json",
|
||||||
|
MimeType::Jsonld => "application/ld+json",
|
||||||
|
MimeType::Mp4 => "video/mp4",
|
||||||
|
MimeType::OctetStream => "application/octet-stream",
|
||||||
|
MimeType::Rtf => "application/rtf",
|
||||||
|
MimeType::Svg => "image/svg+xml",
|
||||||
|
MimeType::Txt => MIMETYPE_PLAIN,
|
||||||
|
};
|
||||||
|
write!(f, "{mime}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MimeType {
|
||||||
|
/// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType.
|
||||||
|
pub fn parse_from_uri(uri: &str) -> MimeType {
|
||||||
|
Self::parse_from_uri_with_fallback(uri, Self::Html)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// parse a URI suffix to convert text/plain mimeType to their actual web compatible mimeType with specified fallback for unknown file extensions.
|
||||||
|
pub fn parse_from_uri_with_fallback(uri: &str, fallback: MimeType) -> MimeType {
|
||||||
|
let suffix = uri.split('.').next_back();
|
||||||
|
match suffix {
|
||||||
|
Some("bin") => Self::OctetStream,
|
||||||
|
Some("css" | "less" | "sass" | "styl") => Self::Css,
|
||||||
|
Some("csv") => Self::Csv,
|
||||||
|
Some("html") => Self::Html,
|
||||||
|
Some("ico") => Self::Ico,
|
||||||
|
Some("js") => Self::Js,
|
||||||
|
Some("json") => Self::Json,
|
||||||
|
Some("jsonld") => Self::Jsonld,
|
||||||
|
Some("mjs") => Self::Js,
|
||||||
|
Some("mp4") => Self::Mp4,
|
||||||
|
Some("rtf") => Self::Rtf,
|
||||||
|
Some("svg") => Self::Svg,
|
||||||
|
Some("txt") => Self::Txt,
|
||||||
|
// Assume HTML when a TLD is found for eg. `wry:://tauri.app` | `wry://hello.com`
|
||||||
|
Some(_) => fallback,
|
||||||
|
// using octet stream according to this:
|
||||||
|
// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types>
|
||||||
|
None => Self::OctetStream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// infer mimetype from content (or) URI if needed.
|
||||||
|
pub fn parse(content: &[u8], uri: &str) -> String {
|
||||||
|
Self::parse_with_fallback(content, uri, Self::Html)
|
||||||
|
}
|
||||||
|
/// infer mimetype from content (or) URI if needed with specified fallback for unknown file extensions.
|
||||||
|
pub fn parse_with_fallback(content: &[u8], uri: &str, fallback: MimeType) -> String {
|
||||||
|
let mime = if uri.ends_with(".svg") {
|
||||||
|
// when reading svg, we can't use `infer`
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
infer::get(content).map(|info| info.mime_type())
|
||||||
|
};
|
||||||
|
|
||||||
|
match mime {
|
||||||
|
Some(mime) if mime == MIMETYPE_PLAIN => {
|
||||||
|
Self::parse_from_uri_with_fallback(uri, fallback).to_string()
|
||||||
|
}
|
||||||
|
None => Self::parse_from_uri_with_fallback(uri, fallback).to_string(),
|
||||||
|
Some(mime) => mime.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_parse_mimetype_from_uri() {
|
||||||
|
let css = MimeType::parse_from_uri(
|
||||||
|
"https://unpkg.com/browse/bootstrap@4.1.0/dist/css/bootstrap-grid.css",
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(css, "text/css".to_string());
|
||||||
|
|
||||||
|
let csv: String = MimeType::parse_from_uri("https://example.com/random.csv").to_string();
|
||||||
|
assert_eq!(csv, "text/csv".to_string());
|
||||||
|
|
||||||
|
let ico: String =
|
||||||
|
MimeType::parse_from_uri("https://icons.duckduckgo.com/ip3/microsoft.com.ico").to_string();
|
||||||
|
assert_eq!(ico, String::from("image/vnd.microsoft.icon"));
|
||||||
|
|
||||||
|
let html: String = MimeType::parse_from_uri("https://tauri.app/index.html").to_string();
|
||||||
|
assert_eq!(html, String::from("text/html"));
|
||||||
|
|
||||||
|
let js: String =
|
||||||
|
MimeType::parse_from_uri("https://unpkg.com/react@17.0.1/umd/react.production.min.js")
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(js, "text/javascript".to_string());
|
||||||
|
|
||||||
|
let json: String =
|
||||||
|
MimeType::parse_from_uri("https://unpkg.com/browse/react@17.0.1/build-info.json").to_string();
|
||||||
|
assert_eq!(json, String::from("application/json"));
|
||||||
|
|
||||||
|
let jsonld: String = MimeType::parse_from_uri("https:/example.com/hello.jsonld").to_string();
|
||||||
|
assert_eq!(jsonld, String::from("application/ld+json"));
|
||||||
|
|
||||||
|
let mjs: String = MimeType::parse_from_uri("https://example.com/bundled.mjs").to_string();
|
||||||
|
assert_eq!(mjs, String::from("text/javascript"));
|
||||||
|
|
||||||
|
let mp4: String = MimeType::parse_from_uri("https://example.com/video.mp4").to_string();
|
||||||
|
assert_eq!(mp4, String::from("video/mp4"));
|
||||||
|
|
||||||
|
let rtf: String = MimeType::parse_from_uri("https://example.com/document.rtf").to_string();
|
||||||
|
assert_eq!(rtf, String::from("application/rtf"));
|
||||||
|
|
||||||
|
let svg: String = MimeType::parse_from_uri("https://example.com/picture.svg").to_string();
|
||||||
|
assert_eq!(svg, String::from("image/svg+xml"));
|
||||||
|
|
||||||
|
let txt: String = MimeType::parse_from_uri("https://example.com/file.txt").to_string();
|
||||||
|
assert_eq!(txt, String::from("text/plain"));
|
||||||
|
|
||||||
|
let custom_scheme = MimeType::parse_from_uri("wry://tauri.app").to_string();
|
||||||
|
assert_eq!(custom_scheme, String::from("text/html"));
|
||||||
|
}
|
||||||
|
}
|
||||||
154
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.js
vendored
Normal file
154
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.js
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT: See ipc.js for the main frame implementation.
|
||||||
|
* main frame -> isolation frame = isolation payload
|
||||||
|
* isolation frame -> main frame = isolation message
|
||||||
|
*/
|
||||||
|
|
||||||
|
;(async function () {
|
||||||
|
/**
|
||||||
|
* Sends the message to the isolation frame.
|
||||||
|
* @param {any} message
|
||||||
|
*/
|
||||||
|
function sendMessage(message) {
|
||||||
|
window.parent.postMessage(message, '*')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string} - The main frame origin.
|
||||||
|
*/
|
||||||
|
const origin = __TEMPLATE_origin__
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Uint8Array} - Injected by Tauri during runtime
|
||||||
|
*/
|
||||||
|
const aesGcmKeyRaw = new Uint8Array(__TEMPLATE_runtime_aes_gcm_key__)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {CryptoKey}
|
||||||
|
*/
|
||||||
|
const aesGcmKey = await window.crypto.subtle.importKey(
|
||||||
|
'raw',
|
||||||
|
aesGcmKeyRaw,
|
||||||
|
'AES-GCM',
|
||||||
|
false,
|
||||||
|
['encrypt']
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} data
|
||||||
|
* @return {Promise<{nonce: number[], payload: number[]}>}
|
||||||
|
*/
|
||||||
|
async function encrypt(payload) {
|
||||||
|
const algorithm = Object.create(null)
|
||||||
|
algorithm.name = 'AES-GCM'
|
||||||
|
algorithm.iv = window.crypto.getRandomValues(new Uint8Array(12))
|
||||||
|
|
||||||
|
const { contentType, data } = __RAW_process_ipc_message_fn__(payload)
|
||||||
|
|
||||||
|
const message =
|
||||||
|
typeof data === 'string'
|
||||||
|
? new TextEncoder().encode(data)
|
||||||
|
: ArrayBuffer.isView(data) || data instanceof ArrayBuffer
|
||||||
|
? data
|
||||||
|
: new Uint8Array(data)
|
||||||
|
|
||||||
|
return window.crypto.subtle
|
||||||
|
.encrypt(algorithm, aesGcmKey, message)
|
||||||
|
.then((payload) => {
|
||||||
|
const result = Object.create(null)
|
||||||
|
result.nonce = Array.from(new Uint8Array(algorithm.iv))
|
||||||
|
result.payload = Array.from(new Uint8Array(payload))
|
||||||
|
result.contentType = contentType
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects if a message event is a valid isolation message.
|
||||||
|
*
|
||||||
|
* @param {MessageEvent<object>} event - a message event that is expected to be an isolation message
|
||||||
|
* @return {boolean} - if the event was a valid isolation message
|
||||||
|
*/
|
||||||
|
function isIsolationMessage(data) {
|
||||||
|
if (typeof data === 'object' && typeof data.payload === 'object') {
|
||||||
|
const keys = data.payload ? Object.keys(data.payload) : []
|
||||||
|
return (
|
||||||
|
keys.length > 0
|
||||||
|
&& keys.every(
|
||||||
|
(key) => key === 'nonce' || key === 'payload' || key === 'contentType'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if a message event is a valid isolation payload.
|
||||||
|
*
|
||||||
|
* @param {MessageEvent<object>} event - a message event that is expected to be an isolation payload
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function isIsolationPayload(data) {
|
||||||
|
return (
|
||||||
|
typeof data === 'object'
|
||||||
|
&& 'callback' in data
|
||||||
|
&& 'error' in data
|
||||||
|
&& !isIsolationMessage(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle incoming payload events.
|
||||||
|
* @param {MessageEvent<any>} event
|
||||||
|
*/
|
||||||
|
async function payloadHandler(event) {
|
||||||
|
if (event.origin !== origin || !isIsolationPayload(event.data)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = event.data
|
||||||
|
|
||||||
|
if (typeof window.__TAURI_ISOLATION_HOOK__ === 'function') {
|
||||||
|
// await even if it's not async so that we can support async ones
|
||||||
|
data = await window.__TAURI_ISOLATION_HOOK__(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = Object.create(null)
|
||||||
|
message.cmd = data.cmd
|
||||||
|
message.callback = data.callback
|
||||||
|
message.error = data.error
|
||||||
|
message.options = data.options
|
||||||
|
message.payload = await encrypt(data.payload)
|
||||||
|
sendMessage(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('message', payloadHandler, false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number} - How many milliseconds to wait between ready checks
|
||||||
|
*/
|
||||||
|
const readyIntervalMs = 50
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until this Isolation context is ready to receive messages, and let the main frame know.
|
||||||
|
*/
|
||||||
|
function waitUntilReady() {
|
||||||
|
// consider either a function or an explicitly set null value as the ready signal
|
||||||
|
if (
|
||||||
|
typeof window.__TAURI_ISOLATION_HOOK__ === 'function'
|
||||||
|
|| window.__TAURI_ISOLATION_HOOK__ === null
|
||||||
|
) {
|
||||||
|
sendMessage('__TAURI_ISOLATION_READY__')
|
||||||
|
} else {
|
||||||
|
setTimeout(waitUntilReady, readyIntervalMs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(waitUntilReady, readyIntervalMs)
|
||||||
|
})()
|
||||||
|
|
||||||
|
document.currentScript?.remove()
|
||||||
171
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.rs
vendored
Normal file
171
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/isolation.rs
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::array::TryFromSliceError;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::string::FromUtf8Error;
|
||||||
|
|
||||||
|
use aes_gcm::aead::Aead;
|
||||||
|
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||||||
|
use getrandom::Error as CsprngError;
|
||||||
|
use serialize_to_javascript::{Template, default_template};
|
||||||
|
|
||||||
|
/// The style for the isolation iframe.
|
||||||
|
pub const IFRAME_STYLE: &str = "#__tauri_isolation__ { display: none !important }";
|
||||||
|
|
||||||
|
/// Errors that can occur during Isolation keys generation.
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
/// Something went wrong with the CSPRNG.
|
||||||
|
#[error("CSPRNG error")]
|
||||||
|
Csprng(#[from] CsprngError),
|
||||||
|
|
||||||
|
/// Something went wrong with decrypting an AES-GCM payload
|
||||||
|
#[error("AES-GCM")]
|
||||||
|
Aes,
|
||||||
|
|
||||||
|
/// Nonce was not 96 bits
|
||||||
|
#[error("Nonce: {0}")]
|
||||||
|
NonceSize(#[from] TryFromSliceError),
|
||||||
|
|
||||||
|
/// Payload was not valid utf8
|
||||||
|
#[error("{0}")]
|
||||||
|
Utf8(#[from] FromUtf8Error),
|
||||||
|
|
||||||
|
/// Invalid json format
|
||||||
|
#[error("{0}")]
|
||||||
|
Json(#[from] serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A formatted AES-GCM cipher instance along with the key used to initialize it.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AesGcmPair {
|
||||||
|
raw: [u8; 32],
|
||||||
|
key: Aes256Gcm,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for AesGcmPair {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "AesGcmPair(...)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AesGcmPair {
|
||||||
|
fn new() -> Result<Self, Error> {
|
||||||
|
let mut raw = [0u8; 32];
|
||||||
|
getrandom::fill(&mut raw)?;
|
||||||
|
let key = aes_gcm::Key::<Aes256Gcm>::from_slice(&raw);
|
||||||
|
Ok(Self {
|
||||||
|
raw,
|
||||||
|
key: Aes256Gcm::new(key),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The raw value used to create the AES-GCM key
|
||||||
|
pub fn raw(&self) -> &[u8; 32] {
|
||||||
|
&self.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The formatted AES-GCM key
|
||||||
|
pub fn key(&self) -> &Aes256Gcm {
|
||||||
|
&self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn encrypt(&self, nonce: &[u8; 12], payload: &[u8]) -> Result<Vec<u8>, Error> {
|
||||||
|
self
|
||||||
|
.key
|
||||||
|
.encrypt(nonce.into(), payload)
|
||||||
|
.map_err(|_| self::Error::Aes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// All cryptographic keys required for Isolation encryption
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Keys {
|
||||||
|
/// AES-GCM key
|
||||||
|
aes_gcm: AesGcmPair,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keys {
|
||||||
|
/// Securely generate required keys for Isolation encryption.
|
||||||
|
pub fn new() -> Result<Self, Error> {
|
||||||
|
AesGcmPair::new().map(|aes_gcm| Self { aes_gcm })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The AES-GCM data (and raw data).
|
||||||
|
pub fn aes_gcm(&self) -> &AesGcmPair {
|
||||||
|
&self.aes_gcm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypts a message using the generated keys.
|
||||||
|
pub fn decrypt(&self, raw: RawIsolationPayload<'_>) -> Result<Vec<u8>, Error> {
|
||||||
|
let RawIsolationPayload { nonce, payload, .. } = raw;
|
||||||
|
let nonce: [u8; 12] = nonce.as_ref().try_into()?;
|
||||||
|
self
|
||||||
|
.aes_gcm
|
||||||
|
.key
|
||||||
|
.decrypt(Nonce::from_slice(&nonce), payload.as_ref())
|
||||||
|
.map_err(|_| self::Error::Aes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Raw representation of
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RawIsolationPayload<'a> {
|
||||||
|
nonce: Cow<'a, [u8]>,
|
||||||
|
payload: Cow<'a, [u8]>,
|
||||||
|
content_type: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RawIsolationPayload<'a> {
|
||||||
|
/// Content type of this payload.
|
||||||
|
pub fn content_type(&self) -> &Cow<'a, str> {
|
||||||
|
&self.content_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a Vec<u8>> for RawIsolationPayload<'a> {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn try_from(value: &'a Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
serde_json::from_slice(value).map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Isolation JavaScript template meant to be injected during codegen.
|
||||||
|
///
|
||||||
|
/// Note: This struct is not considered part of the stable API
|
||||||
|
#[derive(Template)]
|
||||||
|
#[default_template("isolation.js")]
|
||||||
|
pub struct IsolationJavascriptCodegen {
|
||||||
|
// this template intentionally does not include the runtime field
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Isolation JavaScript template meant to be injected during runtime.
|
||||||
|
///
|
||||||
|
/// Note: This struct is not considered part of the stable API
|
||||||
|
#[derive(Template)]
|
||||||
|
#[default_template("isolation.js")]
|
||||||
|
pub struct IsolationJavascriptRuntime<'a> {
|
||||||
|
/// The key used on the Rust backend and the Isolation Javascript
|
||||||
|
pub runtime_aes_gcm_key: &'a [u8; 32],
|
||||||
|
/// The origin the isolation application is expecting messages from.
|
||||||
|
pub origin: String,
|
||||||
|
/// The function that processes the IPC message.
|
||||||
|
#[raw]
|
||||||
|
pub process_ipc_message_fn: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
#[test]
|
||||||
|
fn create_keys() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let _ = super::Keys::new()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
7
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/mod.rs
vendored
Normal file
7
apps/tauri/src-tauri/vendor/tauri-utils/src/pattern/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
/// Handling the Tauri "Isolation" Pattern.
|
||||||
|
#[cfg(feature = "isolation")]
|
||||||
|
pub mod isolation;
|
||||||
434
apps/tauri/src-tauri/vendor/tauri-utils/src/platform.rs
vendored
Normal file
434
apps/tauri/src-tauri/vendor/tauri-utils/src/platform.rs
vendored
Normal file
|
|
@ -0,0 +1,434 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Platform helper functions.
|
||||||
|
|
||||||
|
use std::{fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{Env, PackageInfo, config::BundleType};
|
||||||
|
|
||||||
|
mod starting_binary;
|
||||||
|
|
||||||
|
/// URI prefix of a Tauri asset.
|
||||||
|
///
|
||||||
|
/// This is referenced in the Tauri Android library,
|
||||||
|
/// which resolves these assets to a file descriptor.
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
pub const ANDROID_ASSET_PROTOCOL_URI_PREFIX: &str = "asset://localhost/";
|
||||||
|
|
||||||
|
/// Platform target.
|
||||||
|
#[derive(PartialEq, Eq, Copy, Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Target {
|
||||||
|
/// MacOS.
|
||||||
|
#[serde(rename = "macOS")]
|
||||||
|
MacOS,
|
||||||
|
/// Windows.
|
||||||
|
Windows,
|
||||||
|
/// Linux.
|
||||||
|
Linux,
|
||||||
|
/// Android.
|
||||||
|
Android,
|
||||||
|
/// iOS.
|
||||||
|
#[serde(rename = "iOS")]
|
||||||
|
Ios,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Target {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
Self::MacOS => "macOS",
|
||||||
|
Self::Windows => "windows",
|
||||||
|
Self::Linux => "linux",
|
||||||
|
Self::Android => "android",
|
||||||
|
Self::Ios => "iOS",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target {
|
||||||
|
/// Parses the target from the given target triple.
|
||||||
|
pub fn from_triple(target: &str) -> Self {
|
||||||
|
if target.contains("darwin") {
|
||||||
|
Self::MacOS
|
||||||
|
} else if target.contains("windows") {
|
||||||
|
Self::Windows
|
||||||
|
} else if target.contains("android") {
|
||||||
|
Self::Android
|
||||||
|
} else if target.contains("ios") {
|
||||||
|
Self::Ios
|
||||||
|
} else {
|
||||||
|
Self::Linux
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the current build target.
|
||||||
|
pub fn current() -> Self {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
Self::MacOS
|
||||||
|
} else if cfg!(target_os = "windows") {
|
||||||
|
Self::Windows
|
||||||
|
} else if cfg!(target_os = "ios") {
|
||||||
|
Self::Ios
|
||||||
|
} else if cfg!(target_os = "android") {
|
||||||
|
Self::Android
|
||||||
|
} else {
|
||||||
|
Self::Linux
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the target is mobile or not.
|
||||||
|
pub fn is_mobile(&self) -> bool {
|
||||||
|
matches!(self, Target::Android | Target::Ios)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the target is desktop or not.
|
||||||
|
pub fn is_desktop(&self) -> bool {
|
||||||
|
!self.is_mobile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the currently running binary's path, taking into account security considerations.
|
||||||
|
///
|
||||||
|
/// The path is cached as soon as possible (before even `main` runs) and that value is returned
|
||||||
|
/// repeatedly instead of fetching the path every time. It is possible for the path to not be found,
|
||||||
|
/// or explicitly disabled (see following macOS specific behavior).
|
||||||
|
///
|
||||||
|
/// # Platform-specific behavior
|
||||||
|
///
|
||||||
|
/// On `macOS`, this function will return an error if the original path contained any symlinks
|
||||||
|
/// due to less protection on macOS regarding symlinks. This behavior can be disabled by setting the
|
||||||
|
/// `process-relaunch-dangerous-allow-symlink-macos` feature, although it is *highly discouraged*.
|
||||||
|
///
|
||||||
|
/// # Security
|
||||||
|
///
|
||||||
|
/// If the above platform-specific behavior does **not** take place, this function uses the
|
||||||
|
/// following resolution.
|
||||||
|
///
|
||||||
|
/// We canonicalize the path we received from [`std::env::current_exe`] to resolve any soft links.
|
||||||
|
/// This avoids the usual issue of needing the file to exist at the passed path because a valid
|
||||||
|
/// current executable result for our purpose should always exist. Notably,
|
||||||
|
/// [`std::env::current_exe`] also has a security section that goes over a theoretical attack using
|
||||||
|
/// hard links. Let's cover some specific topics that relate to different ways an attacker might
|
||||||
|
/// try to trick this function into returning the wrong binary path.
|
||||||
|
///
|
||||||
|
/// ## Symlinks ("Soft Links")
|
||||||
|
///
|
||||||
|
/// [`std::path::Path::canonicalize`] is used to resolve symbolic links to the original path,
|
||||||
|
/// including nested symbolic links (`link2 -> link1 -> bin`). On macOS, any results that include
|
||||||
|
/// a symlink are rejected by default due to lesser symlink protections. This can be disabled,
|
||||||
|
/// **although discouraged**, with the `process-relaunch-dangerous-allow-symlink-macos` feature.
|
||||||
|
///
|
||||||
|
/// ## Hard Links
|
||||||
|
///
|
||||||
|
/// A [Hard Link] is a named entry that points to a file in the file system.
|
||||||
|
/// On most systems, this is what you would think of as a "file". The term is
|
||||||
|
/// used on filesystems that allow multiple entries to point to the same file.
|
||||||
|
/// The linked [Hard Link] Wikipedia page provides a decent overview.
|
||||||
|
///
|
||||||
|
/// In short, unless the attacker was able to create the link with elevated
|
||||||
|
/// permissions, it should generally not be possible for them to hard link
|
||||||
|
/// to a file they do not have permissions to - with exception to possible
|
||||||
|
/// operating system exploits.
|
||||||
|
///
|
||||||
|
/// There are also some platform-specific information about this below.
|
||||||
|
///
|
||||||
|
/// ### Windows
|
||||||
|
///
|
||||||
|
/// Windows requires a permission to be set for the user to create a symlink
|
||||||
|
/// or a hard link, regardless of ownership status of the target. Elevated
|
||||||
|
/// permissions users have the ability to create them.
|
||||||
|
///
|
||||||
|
/// ### macOS
|
||||||
|
///
|
||||||
|
/// macOS allows for the creation of symlinks and hard links to any file.
|
||||||
|
/// Accessing through those links will fail if the user who owns the links
|
||||||
|
/// does not have the proper permissions on the original file.
|
||||||
|
///
|
||||||
|
/// ### Linux
|
||||||
|
///
|
||||||
|
/// Linux allows for the creation of symlinks to any file. Accessing the
|
||||||
|
/// symlink will fail if the user who owns the symlink does not have the
|
||||||
|
/// proper permissions on the original file.
|
||||||
|
///
|
||||||
|
/// Linux additionally provides a kernel hardening feature since version
|
||||||
|
/// 3.6 (30 September 2012). Most distributions since then have enabled
|
||||||
|
/// the protection (setting `fs.protected_hardlinks = 1`) by default, which
|
||||||
|
/// means that a vast majority of desktop Linux users should have it enabled.
|
||||||
|
/// **The feature prevents the creation of hardlinks that the user does not own
|
||||||
|
/// or have read/write access to.** [See the patch that enabled this].
|
||||||
|
///
|
||||||
|
/// [Hard Link]: https://en.wikipedia.org/wiki/Hard_link
|
||||||
|
/// [See the patch that enabled this]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7
|
||||||
|
pub fn current_exe() -> std::io::Result<PathBuf> {
|
||||||
|
self::starting_binary::STARTING_BINARY.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to determine the current target triple.
|
||||||
|
///
|
||||||
|
/// Returns a target triple (e.g. `x86_64-unknown-linux-gnu` or `i686-pc-windows-msvc`) or an
|
||||||
|
/// `Error::Config` if the current config cannot be determined or is not some combination of the
|
||||||
|
/// following values:
|
||||||
|
/// `linux, mac, windows` -- `i686, x86, armv7` -- `gnu, musl, msvc`
|
||||||
|
///
|
||||||
|
/// * Errors:
|
||||||
|
/// * Unexpected system config
|
||||||
|
pub fn target_triple() -> crate::Result<String> {
|
||||||
|
let arch = if cfg!(target_arch = "x86") {
|
||||||
|
"i686"
|
||||||
|
} else if cfg!(target_arch = "x86_64") {
|
||||||
|
"x86_64"
|
||||||
|
} else if cfg!(target_arch = "arm") {
|
||||||
|
"armv7"
|
||||||
|
} else if cfg!(target_arch = "aarch64") {
|
||||||
|
"aarch64"
|
||||||
|
} else if cfg!(target_arch = "riscv64") {
|
||||||
|
"riscv64"
|
||||||
|
} else {
|
||||||
|
return Err(crate::Error::Architecture);
|
||||||
|
};
|
||||||
|
|
||||||
|
let os = if cfg!(target_os = "linux") {
|
||||||
|
"unknown-linux"
|
||||||
|
} else if cfg!(target_os = "macos") {
|
||||||
|
"apple-darwin"
|
||||||
|
} else if cfg!(target_os = "windows") {
|
||||||
|
"pc-windows"
|
||||||
|
} else if cfg!(target_os = "freebsd") {
|
||||||
|
"unknown-freebsd"
|
||||||
|
} else {
|
||||||
|
return Err(crate::Error::Os);
|
||||||
|
};
|
||||||
|
|
||||||
|
let os = if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
|
||||||
|
String::from(os)
|
||||||
|
} else {
|
||||||
|
let env = if cfg!(target_env = "gnu") {
|
||||||
|
"gnu"
|
||||||
|
} else if cfg!(target_env = "musl") {
|
||||||
|
"musl"
|
||||||
|
} else if cfg!(target_env = "msvc") {
|
||||||
|
"msvc"
|
||||||
|
} else {
|
||||||
|
return Err(crate::Error::Environment);
|
||||||
|
};
|
||||||
|
|
||||||
|
format!("{os}-{env}")
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!("{arch}-{os}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(not(test), not(target_os = "android")))]
|
||||||
|
fn is_cargo_output_directory(path: &std::path::Path) -> bool {
|
||||||
|
path.join(".cargo-lock").exists()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
const CARGO_OUTPUT_DIRECTORIES: &[&str] = &["debug", "release", "custom-profile"];
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn is_cargo_output_directory(path: &std::path::Path) -> bool {
|
||||||
|
let Some(last_component) = path.components().next_back() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
CARGO_OUTPUT_DIRECTORIES
|
||||||
|
.iter()
|
||||||
|
.any(|dirname| &last_component.as_os_str() == dirname)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the resource directory of the current environment.
|
||||||
|
///
|
||||||
|
/// ## Platform-specific
|
||||||
|
///
|
||||||
|
/// - **Windows:** Resolves to the directory that contains the main executable.
|
||||||
|
/// - **Linux:** When running in an AppImage, the `APPDIR` variable will be set to
|
||||||
|
/// the mounted location of the app, and the resource dir will be `${APPDIR}/usr/lib/${exe_name}`.
|
||||||
|
/// If not running in an AppImage, the path is `/usr/lib/${exe_name}`.
|
||||||
|
/// When running the app from `src-tauri/target/(debug|release)/`, the path is `${exe_dir}/../lib/${exe_name}`.
|
||||||
|
/// - **macOS:** Resolves to `${exe_dir}/../Resources` (inside .app).
|
||||||
|
/// - **iOS:** Resolves to `${exe_dir}/assets`.
|
||||||
|
/// - **Android:** Currently the resources are stored in the APK as assets so it's not a normal file system path,
|
||||||
|
/// we return a special URI prefix `asset://localhost/` here that can be used with the [file system plugin](https://tauri.app/plugin/file-system/),
|
||||||
|
/// with that, you can read the files through [`FsExt::fs`](https://docs.rs/tauri-plugin-fs/latest/tauri_plugin_fs/trait.FsExt.html#tymethod.fs)
|
||||||
|
/// like this: `app.fs().read_to_string(app.path().resource_dir().unwrap().join("resource"));`
|
||||||
|
pub fn resource_dir(package_info: &PackageInfo, env: &Env) -> crate::Result<PathBuf> {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
return resource_dir_android(package_info, env);
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
{
|
||||||
|
let exe = current_exe()?;
|
||||||
|
resource_dir_from(exe, package_info, env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
fn resource_dir_android(_package_info: &PackageInfo, _env: &Env) -> crate::Result<PathBuf> {
|
||||||
|
Ok(PathBuf::from(ANDROID_ASSET_PROTOCOL_URI_PREFIX))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn resource_dir_from<P: AsRef<std::path::Path>>(
|
||||||
|
exe: P,
|
||||||
|
package_info: &PackageInfo,
|
||||||
|
env: &Env,
|
||||||
|
) -> crate::Result<PathBuf> {
|
||||||
|
let exe_dir = exe.as_ref().parent().expect("failed to get exe directory");
|
||||||
|
let curr_dir = exe_dir.display().to_string();
|
||||||
|
|
||||||
|
let parts: Vec<&str> = curr_dir.split(std::path::MAIN_SEPARATOR).collect();
|
||||||
|
let len = parts.len();
|
||||||
|
|
||||||
|
// Check if running from the Cargo output directory, which means it's an executable in a development machine
|
||||||
|
// We check if the binary is inside a `target` folder which can be either `target/$profile` or `target/$triple/$profile`
|
||||||
|
// and see if there's a .cargo-lock file along the executable
|
||||||
|
// This ensures the check is safer so it doesn't affect apps in production
|
||||||
|
// Windows also includes the resources in the executable folder so we check that too
|
||||||
|
if cfg!(target_os = "windows")
|
||||||
|
|| ((len >= 2 && parts[len - 2] == "target") || (len >= 3 && parts[len - 3] == "target"))
|
||||||
|
&& is_cargo_output_directory(exe_dir)
|
||||||
|
{
|
||||||
|
return Ok(exe_dir.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut, unused_assignments)]
|
||||||
|
let mut res = Err(crate::Error::UnsupportedPlatform);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// (canonicalize checks for existence, so there's no need for an extra check)
|
||||||
|
res = if let Ok(bundle_dir) = exe_dir
|
||||||
|
.join(format!("../lib/{}", package_info.name))
|
||||||
|
.canonicalize()
|
||||||
|
{
|
||||||
|
Ok(bundle_dir)
|
||||||
|
} else if let Some(appdir) = &env.appdir {
|
||||||
|
let appdir: &std::path::Path = appdir.as_ref();
|
||||||
|
Ok(PathBuf::from(format!(
|
||||||
|
"{}/usr/lib/{}",
|
||||||
|
appdir.display(),
|
||||||
|
package_info.name
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
// running bundle
|
||||||
|
Ok(PathBuf::from(format!("/usr/lib/{}", package_info.name)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
res = exe_dir
|
||||||
|
.join("../Resources")
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(Into::into);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
{
|
||||||
|
res = exe_dir.join("assets").canonicalize().map_err(Into::into);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable holding the type of bundle the executable is stored in. This is modified by binary
|
||||||
|
// patching during build
|
||||||
|
#[used]
|
||||||
|
// Marked as `mut` because it could get optimized away without it,
|
||||||
|
// see https://github.com/tauri-apps/tauri/pull/13812
|
||||||
|
static mut __TAURI_BUNDLE_TYPE: &str = "__TAURI_BUNDLE_TYPE_VAR_UNK";
|
||||||
|
|
||||||
|
/// Get the type of the bundle current binary is packaged in.
|
||||||
|
/// If the bundle type is unknown, it returns [`Option::None`].
|
||||||
|
pub fn bundle_type() -> Option<BundleType> {
|
||||||
|
unsafe {
|
||||||
|
match __TAURI_BUNDLE_TYPE {
|
||||||
|
"__TAURI_BUNDLE_TYPE_VAR_DEB" => Some(BundleType::Deb),
|
||||||
|
"__TAURI_BUNDLE_TYPE_VAR_RPM" => Some(BundleType::Rpm),
|
||||||
|
"__TAURI_BUNDLE_TYPE_VAR_APP" => Some(BundleType::AppImage),
|
||||||
|
"__TAURI_BUNDLE_TYPE_VAR_MSI" => Some(BundleType::Msi),
|
||||||
|
"__TAURI_BUNDLE_TYPE_VAR_NSS" => Some(BundleType::Nsis),
|
||||||
|
_ => {
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
Some(BundleType::App)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, TokenStreamExt, quote};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl ToTokens for Target {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let prefix = quote! { ::tauri::utils::platform::Target };
|
||||||
|
|
||||||
|
tokens.append_all(match self {
|
||||||
|
Self::MacOS => quote! { #prefix::MacOS },
|
||||||
|
Self::Linux => quote! { #prefix::Linux },
|
||||||
|
Self::Windows => quote! { #prefix::Windows },
|
||||||
|
Self::Android => quote! { #prefix::Android },
|
||||||
|
Self::Ios => quote! { #prefix::Ios },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::{Env, PackageInfo};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
fn resolve_resource_dir() {
|
||||||
|
let package_info = PackageInfo {
|
||||||
|
name: "MyApp".into(),
|
||||||
|
version: "1.0.0".parse().unwrap(),
|
||||||
|
authors: "",
|
||||||
|
description: "",
|
||||||
|
crate_name: "my-app",
|
||||||
|
};
|
||||||
|
let env = Env::default();
|
||||||
|
|
||||||
|
let path = PathBuf::from("/path/to/target/aarch64-apple-darwin/debug/app");
|
||||||
|
let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
|
||||||
|
assert_eq!(resource_dir, path.parent().unwrap());
|
||||||
|
|
||||||
|
let path = PathBuf::from("/path/to/target/custom-profile/app");
|
||||||
|
let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
|
||||||
|
assert_eq!(resource_dir, path.parent().unwrap());
|
||||||
|
|
||||||
|
let path = PathBuf::from("/path/to/target/release/app");
|
||||||
|
let resource_dir = super::resource_dir_from(&path, &package_info, &env).unwrap();
|
||||||
|
assert_eq!(resource_dir, path.parent().unwrap());
|
||||||
|
|
||||||
|
let path = PathBuf::from("/path/to/target/unknown-profile/app");
|
||||||
|
#[allow(clippy::needless_borrows_for_generic_args)]
|
||||||
|
let resource_dir = super::resource_dir_from(&path, &package_info, &env);
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
assert!(resource_dir.is_err());
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
assert_eq!(resource_dir.unwrap(), PathBuf::from("/usr/lib/MyApp"));
|
||||||
|
#[cfg(windows)]
|
||||||
|
assert_eq!(resource_dir.unwrap(), path.parent().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
83
apps/tauri/src-tauri/vendor/tauri-utils/src/platform/starting_binary.rs
vendored
Normal file
83
apps/tauri/src-tauri/vendor/tauri-utils/src/platform/starting_binary.rs
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use ctor::ctor;
|
||||||
|
use std::{
|
||||||
|
io::{Error, ErrorKind, Result},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A cached version of the current binary using [`ctor`] to cache it before even `main` runs.
|
||||||
|
#[ctor]
|
||||||
|
#[used]
|
||||||
|
pub(super) static STARTING_BINARY: StartingBinary = unsafe { StartingBinary::new() };
|
||||||
|
|
||||||
|
/// Represents a binary path that was cached when the program was loaded.
|
||||||
|
pub(super) struct StartingBinary(std::io::Result<PathBuf>);
|
||||||
|
|
||||||
|
impl StartingBinary {
|
||||||
|
/// Find the starting executable as safely as possible.
|
||||||
|
fn new() -> Self {
|
||||||
|
// see notes on current_exe() for security implications
|
||||||
|
let dangerous_path = match std::env::current_exe() {
|
||||||
|
Ok(dangerous_path) => dangerous_path,
|
||||||
|
error @ Err(_) => return Self(error),
|
||||||
|
};
|
||||||
|
|
||||||
|
// note: this only checks symlinks on problematic platforms, see implementation below
|
||||||
|
if let Some(symlink) = Self::has_symlink(&dangerous_path) {
|
||||||
|
return Self(Err(Error::new(
|
||||||
|
ErrorKind::InvalidData,
|
||||||
|
format!(
|
||||||
|
"StartingBinary found current_exe() that contains a symlink on a non-allowed platform: {}",
|
||||||
|
symlink.display()
|
||||||
|
),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// we canonicalize the path to resolve any symlinks to the real exe path
|
||||||
|
Self(dangerous_path.canonicalize())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A clone of the [`PathBuf`] found to be the starting path.
|
||||||
|
///
|
||||||
|
/// Because [`Error`] is not clone-able, it is recreated instead.
|
||||||
|
pub(super) fn cloned(&self) -> Result<PathBuf> {
|
||||||
|
// false positive
|
||||||
|
#[allow(clippy::useless_asref)]
|
||||||
|
self
|
||||||
|
.0
|
||||||
|
.as_ref()
|
||||||
|
.map(Clone::clone)
|
||||||
|
.map_err(|e| Error::new(e.kind(), e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We only care about checking this on macOS currently, as it has the least symlink protections.
|
||||||
|
#[cfg(any(
|
||||||
|
not(target_os = "macos"),
|
||||||
|
feature = "process-relaunch-dangerous-allow-symlink-macos"
|
||||||
|
))]
|
||||||
|
fn has_symlink(_: &Path) -> Option<&Path> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We only care about checking this on macOS currently, as it has the least symlink protections.
|
||||||
|
#[cfg(all(
|
||||||
|
target_os = "macos",
|
||||||
|
not(feature = "process-relaunch-dangerous-allow-symlink-macos")
|
||||||
|
))]
|
||||||
|
fn has_symlink(path: &Path) -> Option<&Path> {
|
||||||
|
path.ancestors().find(|ancestor| {
|
||||||
|
matches!(
|
||||||
|
ancestor
|
||||||
|
.symlink_metadata()
|
||||||
|
.as_ref()
|
||||||
|
.map(std::fs::Metadata::file_type)
|
||||||
|
.as_ref()
|
||||||
|
.map(std::fs::FileType::is_symlink),
|
||||||
|
Ok(true)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
88
apps/tauri/src-tauri/vendor/tauri-utils/src/plugin.rs
vendored
Normal file
88
apps/tauri/src-tauri/vendor/tauri-utils/src/plugin.rs
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Compile-time and runtime types for Tauri plugins.
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
pub use build::*;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "build", feature = "build-2"))]
|
||||||
|
mod build {
|
||||||
|
use std::{
|
||||||
|
env::vars_os,
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
const GLOBAL_API_SCRIPT_PATH_KEY: &str = "GLOBAL_API_SCRIPT_PATH";
|
||||||
|
/// Known file name of the file that contains an array with the path of all API scripts defined with [`define_global_api_script_path`].
|
||||||
|
pub const GLOBAL_API_SCRIPT_FILE_LIST_PATH: &str = "__global-api-script.js";
|
||||||
|
|
||||||
|
/// Defines the path to the global API script using Cargo instructions.
|
||||||
|
pub fn define_global_api_script_path(path: &Path) {
|
||||||
|
println!(
|
||||||
|
"cargo:{GLOBAL_API_SCRIPT_PATH_KEY}={}",
|
||||||
|
path
|
||||||
|
.canonicalize()
|
||||||
|
.expect("failed to canonicalize global API script path")
|
||||||
|
.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects the path of all the global API scripts defined with [`define_global_api_script_path`]
|
||||||
|
/// and saves them to the out dir with filename [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`].
|
||||||
|
///
|
||||||
|
/// `tauri_global_scripts` is only used in Tauri's monorepo for the examples to work
|
||||||
|
/// since they don't have a build script to run `tauri-build` and pull in the deps env vars
|
||||||
|
pub fn save_global_api_scripts_paths(out_dir: &Path, mut tauri_global_scripts: Option<PathBuf>) {
|
||||||
|
let mut scripts = Vec::new();
|
||||||
|
|
||||||
|
for (key, value) in vars_os() {
|
||||||
|
let key = key.to_string_lossy();
|
||||||
|
|
||||||
|
if key == format!("DEP_TAURI_{GLOBAL_API_SCRIPT_PATH_KEY}") {
|
||||||
|
tauri_global_scripts = Some(PathBuf::from(value));
|
||||||
|
} else if key.starts_with("DEP_") && key.ends_with(GLOBAL_API_SCRIPT_PATH_KEY) {
|
||||||
|
let script_path = PathBuf::from(value);
|
||||||
|
scripts.push(script_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tauri_global_scripts) = tauri_global_scripts {
|
||||||
|
scripts.insert(0, tauri_global_scripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(
|
||||||
|
out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH),
|
||||||
|
serde_json::to_string(&scripts).expect("failed to serialize global API script paths"),
|
||||||
|
)
|
||||||
|
.expect("failed to write global API script");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read global api scripts from [`GLOBAL_API_SCRIPT_FILE_LIST_PATH`]
|
||||||
|
pub fn read_global_api_scripts(out_dir: &Path) -> Option<Vec<String>> {
|
||||||
|
let global_scripts_path = out_dir.join(GLOBAL_API_SCRIPT_FILE_LIST_PATH);
|
||||||
|
if !global_scripts_path.exists() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global_scripts_str = fs::read_to_string(global_scripts_path)
|
||||||
|
.expect("failed to read plugin global API script paths");
|
||||||
|
let global_scripts = serde_json::from_str::<Vec<PathBuf>>(&global_scripts_str)
|
||||||
|
.expect("failed to parse plugin global API script paths");
|
||||||
|
|
||||||
|
Some(
|
||||||
|
global_scripts
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| {
|
||||||
|
fs::read_to_string(&p).unwrap_or_else(|e| {
|
||||||
|
panic!(
|
||||||
|
"failed to read plugin global API script {}: {e}",
|
||||||
|
p.display()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
643
apps/tauri/src-tauri/vendor/tauri-utils/src/resources.rs
vendored
Normal file
643
apps/tauri/src-tauri/vendor/tauri-utils/src/resources.rs
vendored
Normal file
|
|
@ -0,0 +1,643 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use crate::platform::Target as TargetPlatform;
|
||||||
|
|
||||||
|
/// Given a path (absolute or relative) to a resource file, returns the
|
||||||
|
/// relative path from the bundle resources directory where that resource
|
||||||
|
/// should be stored.
|
||||||
|
pub fn resource_relpath(path: &Path) -> PathBuf {
|
||||||
|
let mut dest = PathBuf::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(_) => {}
|
||||||
|
Component::RootDir => dest.push("_root_"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => dest.push("_up_"),
|
||||||
|
Component::Normal(string) => dest.push(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(path: &Path) -> PathBuf {
|
||||||
|
let mut dest = PathBuf::new();
|
||||||
|
for component in path.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(_) => {}
|
||||||
|
Component::RootDir => dest.push("/"),
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => dest.push(".."),
|
||||||
|
Component::Normal(string) => dest.push(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dest
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the external binaries to bundle, adding the target triple suffix to each of them.
|
||||||
|
pub fn external_binaries(
|
||||||
|
external_binaries: &[String],
|
||||||
|
target_triple: &str,
|
||||||
|
target_platform: &TargetPlatform,
|
||||||
|
) -> Vec<String> {
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
for curr_path in external_binaries {
|
||||||
|
let extension = if matches!(target_platform, TargetPlatform::Windows) {
|
||||||
|
".exe"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
paths.push(format!("{curr_path}-{target_triple}{extension}"));
|
||||||
|
}
|
||||||
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information for a resource.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Resource {
|
||||||
|
path: PathBuf,
|
||||||
|
target: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource {
|
||||||
|
/// The path of the resource.
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The target location of the resource.
|
||||||
|
pub fn target(&self) -> &Path {
|
||||||
|
&self.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PatternIter<'a> {
|
||||||
|
Slice(std::slice::Iter<'a, String>),
|
||||||
|
Map(std::collections::hash_map::Iter<'a, String, String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper to iterate through resources.
|
||||||
|
pub struct ResourcePaths<'a> {
|
||||||
|
iter: ResourcePathsIter<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ResourcePaths<'a> {
|
||||||
|
/// Creates a new ResourcePaths from a slice of patterns to iterate
|
||||||
|
pub fn new(patterns: &'a [String], allow_walk: bool) -> ResourcePaths<'a> {
|
||||||
|
ResourcePaths {
|
||||||
|
iter: ResourcePathsIter {
|
||||||
|
pattern_iter: PatternIter::Slice(patterns.iter()),
|
||||||
|
allow_walk,
|
||||||
|
current_dest: None,
|
||||||
|
current_iter: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new ResourcePaths from a slice of patterns to iterate
|
||||||
|
pub fn from_map(patterns: &'a HashMap<String, String>, allow_walk: bool) -> ResourcePaths<'a> {
|
||||||
|
ResourcePaths {
|
||||||
|
iter: ResourcePathsIter {
|
||||||
|
pattern_iter: PatternIter::Map(patterns.iter()),
|
||||||
|
allow_walk,
|
||||||
|
current_dest: None,
|
||||||
|
current_iter: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the resource iterator that yields the source and target paths.
|
||||||
|
/// Needed when using [`Self::from_map`].
|
||||||
|
pub fn iter(self) -> ResourcePathsIter<'a> {
|
||||||
|
self.iter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterator of a [`ResourcePaths`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ResourcePathsIter<'a> {
|
||||||
|
/// the patterns to iterate.
|
||||||
|
pattern_iter: PatternIter<'a>,
|
||||||
|
/// whether the resource paths allows directories or not.
|
||||||
|
allow_walk: bool,
|
||||||
|
|
||||||
|
/// The value of map when [`Self::pattern_iter`] is a [`PatternIter::Map`],
|
||||||
|
/// used for determining [`Resource::target`]
|
||||||
|
current_dest: Option<PathBuf>,
|
||||||
|
/// The iter for the current pattern. The cycle goes like this:
|
||||||
|
/// [`ResourcePaths::next`] -> [`Self::next`] -> [`Self::pattern_iter::next`] -> [`Self::current_iter::next`]
|
||||||
|
current_iter: Option<ResourcePathsInnerIter>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ResourcePathsInnerIter {
|
||||||
|
Walk {
|
||||||
|
iter: walkdir::IntoIter,
|
||||||
|
/// The key of map when [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Map`],
|
||||||
|
/// used for determining [`Resource::target`]
|
||||||
|
current_pattern: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
Glob {
|
||||||
|
iter: glob::Paths,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ResourcePathsInnerIter {
|
||||||
|
type Item = crate::Result<PathBuf>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<crate::Result<PathBuf>> {
|
||||||
|
match self {
|
||||||
|
ResourcePathsInnerIter::Walk { iter, .. } => Some(
|
||||||
|
iter
|
||||||
|
.next()?
|
||||||
|
.map(|entry| entry.into_path())
|
||||||
|
.map_err(Into::into),
|
||||||
|
),
|
||||||
|
ResourcePathsInnerIter::Glob { iter } => Some(iter.next()?.map_err(Into::into)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourcePathsIter<'_> {
|
||||||
|
fn next_current_iter(&mut self) -> Option<crate::Result<Resource>> {
|
||||||
|
let current_iter = self.current_iter.as_mut().unwrap();
|
||||||
|
let entry = current_iter.next()?;
|
||||||
|
|
||||||
|
Some(match entry {
|
||||||
|
Ok(entry) => {
|
||||||
|
// Skip directories
|
||||||
|
if entry.is_dir() {
|
||||||
|
self.next_current_iter()?
|
||||||
|
} else {
|
||||||
|
self.resource_from_path(normalize(&entry))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => Err(error),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resource_from_path(&self, path: PathBuf) -> crate::Result<Resource> {
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(crate::Error::ResourcePathNotFound(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Resource {
|
||||||
|
target: if let Some(dest) = &self.current_dest {
|
||||||
|
match &self.current_iter {
|
||||||
|
Some(current_iter) => match current_iter {
|
||||||
|
// if processing a directory, preserve directory structure under current_dest
|
||||||
|
ResourcePathsInnerIter::Walk {
|
||||||
|
current_pattern, ..
|
||||||
|
} => {
|
||||||
|
if let Some(pattern) = current_pattern {
|
||||||
|
dest.join(path.strip_prefix(pattern).unwrap_or(&path))
|
||||||
|
} else {
|
||||||
|
dest.join(&path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if processing a glob and current_dest is not empty
|
||||||
|
// we put all globbed paths under current_dest
|
||||||
|
// preserving the file name as it is
|
||||||
|
ResourcePathsInnerIter::Glob { .. } => dest.join(path.file_name().unwrap()),
|
||||||
|
},
|
||||||
|
None => dest.clone(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If [`ResourcePathsIter::pattern_iter`] is a [`PatternIter::Slice`]
|
||||||
|
resource_relpath(&path)
|
||||||
|
},
|
||||||
|
path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_pattern(&mut self) -> Option<crate::Result<Resource>> {
|
||||||
|
self.current_dest = None;
|
||||||
|
|
||||||
|
let pattern = match &mut self.pattern_iter {
|
||||||
|
PatternIter::Slice(iter) => iter.next()?,
|
||||||
|
PatternIter::Map(iter) => {
|
||||||
|
let (pattern, dest) = iter.next()?;
|
||||||
|
self.current_dest = Some(resource_relpath(Path::new(dest)));
|
||||||
|
pattern
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if pattern.contains('*') {
|
||||||
|
self.current_iter = match glob::glob(pattern) {
|
||||||
|
Ok(glob) => Some(ResourcePathsInnerIter::Glob { iter: glob }),
|
||||||
|
Err(error) => return Some(Err(error.into())),
|
||||||
|
};
|
||||||
|
match self.next_current_iter() {
|
||||||
|
Some(r) => return Some(r),
|
||||||
|
None => {
|
||||||
|
self.current_iter = None;
|
||||||
|
return Some(Err(crate::Error::GlobPathNotFound(pattern.clone())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let path = normalize(Path::new(pattern));
|
||||||
|
if path.is_dir() {
|
||||||
|
if !self.allow_walk {
|
||||||
|
return Some(Err(crate::Error::NotAllowedToWalkDir(path)));
|
||||||
|
}
|
||||||
|
self.current_iter = Some(ResourcePathsInnerIter::Walk {
|
||||||
|
iter: WalkDir::new(&path).into_iter(),
|
||||||
|
current_pattern: if matches!(self.pattern_iter, PatternIter::Map(_)) {
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Some(self.resource_from_path(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_current_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ResourcePaths<'_> {
|
||||||
|
type Item = crate::Result<PathBuf>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<crate::Result<PathBuf>> {
|
||||||
|
self.iter.next().map(|r| r.map(|res| res.path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ResourcePathsIter<'_> {
|
||||||
|
type Item = crate::Result<Resource>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<crate::Result<Resource>> {
|
||||||
|
if self.current_iter.is_some() {
|
||||||
|
match self.next_current_iter() {
|
||||||
|
Some(r) => return Some(r),
|
||||||
|
None => self.current_iter = None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next_pattern()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
impl PartialEq for Resource {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.path == other.path && self.target == other.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_resources(resources: &[(&str, &str)]) -> Vec<Resource> {
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.map(|(path, target)| Resource {
|
||||||
|
path: Path::new(path).components().collect(),
|
||||||
|
target: Path::new(target).components().collect(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_test_dirs() {
|
||||||
|
let mut random = [0; 1];
|
||||||
|
getrandom::fill(&mut random).unwrap();
|
||||||
|
|
||||||
|
let temp = std::env::temp_dir();
|
||||||
|
let temp = temp.join(format!("tauri_resource_paths_iter_test_{}", random[0]));
|
||||||
|
|
||||||
|
let _ = fs::remove_dir_all(&temp);
|
||||||
|
fs::create_dir_all(&temp).unwrap();
|
||||||
|
|
||||||
|
std::env::set_current_dir(&temp).unwrap();
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
"src-tauri/tauri.conf.json",
|
||||||
|
"src-tauri/some-other-json.json",
|
||||||
|
"src-tauri/Cargo.toml",
|
||||||
|
"src-tauri/Tauri.toml",
|
||||||
|
"src-tauri/build.rs",
|
||||||
|
"src-tauri/some-folder/some-file.txt",
|
||||||
|
"src/assets/javascript.svg",
|
||||||
|
"src/assets/tauri.svg",
|
||||||
|
"src/assets/rust.svg",
|
||||||
|
"src/assets/lang/en.json",
|
||||||
|
"src/assets/lang/ar.json",
|
||||||
|
"src/sounds/lang/es.wav",
|
||||||
|
"src/sounds/lang/fr.wav",
|
||||||
|
"src/textures/ground/earth.tex",
|
||||||
|
"src/textures/ground/sand.tex",
|
||||||
|
"src/textures/water.tex",
|
||||||
|
"src/textures/fire.tex",
|
||||||
|
"src/tiles/sky/grey.tile",
|
||||||
|
"src/tiles/sky/yellow.tile",
|
||||||
|
"src/tiles/grass.tile",
|
||||||
|
"src/tiles/stones.tile",
|
||||||
|
"src/index.html",
|
||||||
|
"src/style.css",
|
||||||
|
"src/script.js",
|
||||||
|
"src/dir/another-dir/file1.txt",
|
||||||
|
"src/dir/another-dir2/file2.txt",
|
||||||
|
];
|
||||||
|
|
||||||
|
for path in paths {
|
||||||
|
let path = Path::new(path);
|
||||||
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
fs::write(path, "").unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resources_map(literal: &[(&str, &str)]) -> HashMap<String, String> {
|
||||||
|
literal
|
||||||
|
.iter()
|
||||||
|
.map(|(from, to)| (from.to_string(), to.to_string()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial_test::serial(resources)]
|
||||||
|
fn resource_paths_iter_slice_allow_walk() {
|
||||||
|
setup_test_dirs();
|
||||||
|
|
||||||
|
let dir = std::env::current_dir().unwrap().join("src-tauri");
|
||||||
|
let _ = std::env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let resources = ResourcePaths::new(
|
||||||
|
&[
|
||||||
|
"../src/script.js".into(),
|
||||||
|
"../src/assets".into(),
|
||||||
|
"../src/index.html".into(),
|
||||||
|
"../src/sounds".into(),
|
||||||
|
// Should be the same as `../src/textures/` or `../src/textures`
|
||||||
|
"../src/textures/**/*".into(),
|
||||||
|
"*.toml".into(),
|
||||||
|
"*.conf.json".into(),
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected = expected_resources(&[
|
||||||
|
// From `../src/script.js`
|
||||||
|
("../src/script.js", "_up_/src/script.js"),
|
||||||
|
// From `../src/assets`
|
||||||
|
(
|
||||||
|
"../src/assets/javascript.svg",
|
||||||
|
"_up_/src/assets/javascript.svg",
|
||||||
|
),
|
||||||
|
("../src/assets/tauri.svg", "_up_/src/assets/tauri.svg"),
|
||||||
|
("../src/assets/rust.svg", "_up_/src/assets/rust.svg"),
|
||||||
|
("../src/assets/lang/en.json", "_up_/src/assets/lang/en.json"),
|
||||||
|
("../src/assets/lang/ar.json", "_up_/src/assets/lang/ar.json"),
|
||||||
|
// From `../src/index.html`
|
||||||
|
("../src/index.html", "_up_/src/index.html"),
|
||||||
|
// From `../src/sounds`
|
||||||
|
("../src/sounds/lang/es.wav", "_up_/src/sounds/lang/es.wav"),
|
||||||
|
("../src/sounds/lang/fr.wav", "_up_/src/sounds/lang/fr.wav"),
|
||||||
|
// From `../src/textures/**/*`
|
||||||
|
(
|
||||||
|
"../src/textures/ground/earth.tex",
|
||||||
|
"_up_/src/textures/ground/earth.tex",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"../src/textures/ground/sand.tex",
|
||||||
|
"_up_/src/textures/ground/sand.tex",
|
||||||
|
),
|
||||||
|
("../src/textures/water.tex", "_up_/src/textures/water.tex"),
|
||||||
|
("../src/textures/fire.tex", "_up_/src/textures/fire.tex"),
|
||||||
|
// From `*.toml`
|
||||||
|
("Cargo.toml", "Cargo.toml"),
|
||||||
|
("Tauri.toml", "Tauri.toml"),
|
||||||
|
// From `*.conf.json`
|
||||||
|
("tauri.conf.json", "tauri.conf.json"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(resources.len(), expected.len());
|
||||||
|
for resource in expected {
|
||||||
|
if !resources.contains(&resource) {
|
||||||
|
panic!("{resource:?} was expected but not found in {resources:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial_test::serial(resources)]
|
||||||
|
fn resource_paths_iter_slice_no_walk() {
|
||||||
|
setup_test_dirs();
|
||||||
|
|
||||||
|
let dir = std::env::current_dir().unwrap().join("src-tauri");
|
||||||
|
let _ = std::env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let resources = ResourcePaths::new(
|
||||||
|
&[
|
||||||
|
"../src/script.js".into(),
|
||||||
|
"../src/assets".into(),
|
||||||
|
"../src/index.html".into(),
|
||||||
|
"../src/sounds".into(),
|
||||||
|
"*.toml".into(),
|
||||||
|
"*.conf.json".into(),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected = expected_resources(&[
|
||||||
|
("../src/script.js", "_up_/src/script.js"),
|
||||||
|
("../src/index.html", "_up_/src/index.html"),
|
||||||
|
("Cargo.toml", "Cargo.toml"),
|
||||||
|
("Tauri.toml", "Tauri.toml"),
|
||||||
|
("tauri.conf.json", "tauri.conf.json"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(resources.len(), expected.len());
|
||||||
|
for resource in expected {
|
||||||
|
if !resources.contains(&resource) {
|
||||||
|
panic!("{resource:?} was expected but not found in {resources:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial_test::serial(resources)]
|
||||||
|
fn resource_paths_iter_map_allow_walk() {
|
||||||
|
setup_test_dirs();
|
||||||
|
|
||||||
|
let dir = std::env::current_dir().unwrap().join("src-tauri");
|
||||||
|
let _ = std::env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let resources = ResourcePaths::from_map(
|
||||||
|
&resources_map(&[
|
||||||
|
("../src/script.js", "main.js"),
|
||||||
|
("../src/assets", ""),
|
||||||
|
("../src/index.html", "frontend/index.html"),
|
||||||
|
("../src/sounds", "voices"),
|
||||||
|
("../src/textures/*", "textures"),
|
||||||
|
("../src/tiles/**/*", "tiles"),
|
||||||
|
("*.toml", ""),
|
||||||
|
("*.conf.json", "json"),
|
||||||
|
("./some-folder/", "some-target-folder/"),
|
||||||
|
("../non-existent-file", "asd"), // invalid case
|
||||||
|
("../non/*", "asd"), // invalid case
|
||||||
|
]),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected = expected_resources(&[
|
||||||
|
("../src/script.js", "main.js"),
|
||||||
|
("../src/assets/javascript.svg", "javascript.svg"),
|
||||||
|
("../src/assets/tauri.svg", "tauri.svg"),
|
||||||
|
("../src/assets/rust.svg", "rust.svg"),
|
||||||
|
("../src/assets/lang/en.json", "lang/en.json"),
|
||||||
|
("../src/assets/lang/ar.json", "lang/ar.json"),
|
||||||
|
("../src/index.html", "frontend/index.html"),
|
||||||
|
("../src/sounds/lang/es.wav", "voices/lang/es.wav"),
|
||||||
|
("../src/sounds/lang/fr.wav", "voices/lang/fr.wav"),
|
||||||
|
("../src/textures/water.tex", "textures/water.tex"),
|
||||||
|
("../src/textures/fire.tex", "textures/fire.tex"),
|
||||||
|
("../src/tiles/grass.tile", "tiles/grass.tile"),
|
||||||
|
("../src/tiles/stones.tile", "tiles/stones.tile"),
|
||||||
|
("../src/tiles/sky/grey.tile", "tiles/grey.tile"),
|
||||||
|
("../src/tiles/sky/yellow.tile", "tiles/yellow.tile"),
|
||||||
|
("Cargo.toml", "Cargo.toml"),
|
||||||
|
("Tauri.toml", "Tauri.toml"),
|
||||||
|
("tauri.conf.json", "json/tauri.conf.json"),
|
||||||
|
(
|
||||||
|
"some-folder/some-file.txt",
|
||||||
|
"some-target-folder/some-file.txt",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(resources.len(), expected.len());
|
||||||
|
for resource in expected {
|
||||||
|
if !resources.contains(&resource) {
|
||||||
|
panic!("{resource:?} was expected but not found in {resources:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial_test::serial(resources)]
|
||||||
|
fn resource_paths_iter_map_no_walk() {
|
||||||
|
setup_test_dirs();
|
||||||
|
|
||||||
|
let dir = std::env::current_dir().unwrap().join("src-tauri");
|
||||||
|
let _ = std::env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let resources = ResourcePaths::from_map(
|
||||||
|
&resources_map(&[
|
||||||
|
("../src/script.js", "main.js"),
|
||||||
|
("../src/assets", ""),
|
||||||
|
("../src/index.html", "frontend/index.html"),
|
||||||
|
("../src/sounds", "voices"),
|
||||||
|
("*.toml", ""),
|
||||||
|
("*.conf.json", "json"),
|
||||||
|
]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let expected = expected_resources(&[
|
||||||
|
("../src/script.js", "main.js"),
|
||||||
|
("../src/index.html", "frontend/index.html"),
|
||||||
|
("Cargo.toml", "Cargo.toml"),
|
||||||
|
("Tauri.toml", "Tauri.toml"),
|
||||||
|
("tauri.conf.json", "json/tauri.conf.json"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(resources.len(), expected.len());
|
||||||
|
for resource in expected {
|
||||||
|
if !resources.contains(&resource) {
|
||||||
|
panic!("{resource:?} was expected but not found in {resources:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial_test::serial(resources)]
|
||||||
|
fn resource_paths_errors() {
|
||||||
|
setup_test_dirs();
|
||||||
|
|
||||||
|
let dir = std::env::current_dir().unwrap().join("src-tauri");
|
||||||
|
let _ = std::env::set_current_dir(dir);
|
||||||
|
|
||||||
|
let resources = ResourcePaths::from_map(
|
||||||
|
&resources_map(&[
|
||||||
|
("../non-existent-file", "file"),
|
||||||
|
("../non-existent-dir", "dir"),
|
||||||
|
// exists but not allowed to walk
|
||||||
|
("../src", "dir2"),
|
||||||
|
// doesn't exist but it is a glob and will return an error
|
||||||
|
("../non-existent-glob-dir/*", "glob"),
|
||||||
|
// exists but only contains directories and will not produce any values
|
||||||
|
("../src/dir/*", "dir3"),
|
||||||
|
]),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(resources.len(), 5);
|
||||||
|
|
||||||
|
assert!(resources.iter().all(|r| r.is_err()));
|
||||||
|
|
||||||
|
// hashmap order is not guaranteed so we check the error variant exists and how many
|
||||||
|
assert!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.any(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_))))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r, Err(crate::Error::ResourcePathNotFound(_))))
|
||||||
|
.count(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.any(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_))))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r, Err(crate::Error::NotAllowedToWalkDir(_))))
|
||||||
|
.count(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.any(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_))))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resources
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r, Err(crate::Error::GlobPathNotFound(_))))
|
||||||
|
.count(),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
175
apps/tauri/src-tauri/vendor/tauri-utils/src/tokens.rs
vendored
Normal file
175
apps/tauri/src-tauri/vendor/tauri-utils/src/tokens.rs
vendored
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
//! Utilities to implement [`ToTokens`] for a type.
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{ToTokens, quote};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// Write a `TokenStream` of the `$struct`'s fields to the `$tokens`.
|
||||||
|
///
|
||||||
|
/// All fields must represent a binding of the same name that implements `ToTokens`.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! literal_struct {
|
||||||
|
($tokens:ident, $struct:path, $($field:ident),+) => {
|
||||||
|
$tokens.append_all(quote! {
|
||||||
|
$struct {
|
||||||
|
$($field: #$field),+
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `String` constructor `TokenStream`.
|
||||||
|
///
|
||||||
|
/// e.g. `"Hello World"` -> `String::from("Hello World")`.
|
||||||
|
/// This takes a `&String` to reduce casting all the `&String` -> `&str` manually.
|
||||||
|
pub fn str_lit(s: impl AsRef<str>) -> TokenStream {
|
||||||
|
let s = s.as_ref();
|
||||||
|
quote! { #s.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an `Option` constructor `TokenStream`.
|
||||||
|
pub fn opt_lit(item: Option<&impl ToTokens>) -> TokenStream {
|
||||||
|
match item {
|
||||||
|
None => quote! { ::core::option::Option::None },
|
||||||
|
Some(item) => quote! { ::core::option::Option::Some(#item) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an `Option` constructor `TokenStream` over an owned [`ToTokens`] impl type.
|
||||||
|
pub fn opt_lit_owned(item: Option<impl ToTokens>) -> TokenStream {
|
||||||
|
match item {
|
||||||
|
None => quote! { ::core::option::Option::None },
|
||||||
|
Some(item) => quote! { ::core::option::Option::Some(#item) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to combine an `opt_lit` with `str_lit`.
|
||||||
|
pub fn opt_str_lit(item: Option<impl AsRef<str>>) -> TokenStream {
|
||||||
|
opt_lit(item.map(str_lit).as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to combine an `opt_lit` with a list of `str_lit`
|
||||||
|
pub fn opt_vec_lit<Raw, Tokens>(
|
||||||
|
item: Option<impl IntoIterator<Item = Raw>>,
|
||||||
|
map: impl Fn(Raw) -> Tokens,
|
||||||
|
) -> TokenStream
|
||||||
|
where
|
||||||
|
Tokens: ToTokens,
|
||||||
|
{
|
||||||
|
opt_lit(item.map(|list| vec_lit(list, map)).as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `Vec` constructor, mapping items with a function that spits out `TokenStream`s.
|
||||||
|
pub fn vec_lit<Raw, Tokens>(
|
||||||
|
list: impl IntoIterator<Item = Raw>,
|
||||||
|
map: impl Fn(Raw) -> Tokens,
|
||||||
|
) -> TokenStream
|
||||||
|
where
|
||||||
|
Tokens: ToTokens,
|
||||||
|
{
|
||||||
|
let items = list.into_iter().map(map);
|
||||||
|
quote! { vec![#(#items),*] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `PathBuf` constructor `TokenStream`.
|
||||||
|
///
|
||||||
|
/// e.g. `"Hello World" -> String::from("Hello World").
|
||||||
|
pub fn path_buf_lit(s: impl AsRef<Path>) -> TokenStream {
|
||||||
|
let s = s.as_ref().to_string_lossy().into_owned();
|
||||||
|
quote! { ::std::path::PathBuf::from(#s) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `Url` constructor `TokenStream`.
|
||||||
|
pub fn url_lit(url: &Url) -> TokenStream {
|
||||||
|
let url = url.as_str();
|
||||||
|
quote! { #url.parse().unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a map constructor, mapping keys and values with other `TokenStream`s.
|
||||||
|
///
|
||||||
|
/// This function is pretty generic because the types of keys AND values get transformed.
|
||||||
|
pub fn map_lit<Map, Key, Value, TokenStreamKey, TokenStreamValue, FuncKey, FuncValue>(
|
||||||
|
map_type: TokenStream,
|
||||||
|
map: Map,
|
||||||
|
map_key: FuncKey,
|
||||||
|
map_value: FuncValue,
|
||||||
|
) -> TokenStream
|
||||||
|
where
|
||||||
|
<Map as IntoIterator>::IntoIter: ExactSizeIterator,
|
||||||
|
Map: IntoIterator<Item = (Key, Value)>,
|
||||||
|
TokenStreamKey: ToTokens,
|
||||||
|
TokenStreamValue: ToTokens,
|
||||||
|
FuncKey: Fn(Key) -> TokenStreamKey,
|
||||||
|
FuncValue: Fn(Value) -> TokenStreamValue,
|
||||||
|
{
|
||||||
|
let ident = quote::format_ident!("map");
|
||||||
|
let map = map.into_iter();
|
||||||
|
|
||||||
|
if map.len() > 0 {
|
||||||
|
let items = map.map(|(key, value)| {
|
||||||
|
let key = map_key(key);
|
||||||
|
let value = map_value(value);
|
||||||
|
quote! { #ident.insert(#key, #value); }
|
||||||
|
});
|
||||||
|
|
||||||
|
quote! {{
|
||||||
|
let mut #ident = #map_type::new();
|
||||||
|
#(#items)*
|
||||||
|
#ident
|
||||||
|
}}
|
||||||
|
} else {
|
||||||
|
quote! { #map_type::new() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `serde_json::Value` variant `TokenStream` for a number
|
||||||
|
pub fn json_value_number_lit(num: &serde_json::Number) -> TokenStream {
|
||||||
|
// See <https://docs.rs/serde_json/1/serde_json/struct.Number.html> for guarantees
|
||||||
|
let prefix = quote! { ::serde_json::Value };
|
||||||
|
if num.is_u64() {
|
||||||
|
// guaranteed u64
|
||||||
|
let num = num.as_u64().unwrap();
|
||||||
|
quote! { #prefix::Number(#num.into()) }
|
||||||
|
} else if num.is_i64() {
|
||||||
|
// guaranteed i64
|
||||||
|
let num = num.as_i64().unwrap();
|
||||||
|
quote! { #prefix::Number(#num.into()) }
|
||||||
|
} else if num.is_f64() {
|
||||||
|
// guaranteed f64
|
||||||
|
let num = num.as_f64().unwrap();
|
||||||
|
quote! { #prefix::Number(::serde_json::Number::from_f64(#num).unwrap(/* safe to unwrap, guaranteed f64 */)) }
|
||||||
|
} else {
|
||||||
|
// invalid number
|
||||||
|
quote! { #prefix::Null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `serde_json::Value` constructor `TokenStream`
|
||||||
|
pub fn json_value_lit(jv: &JsonValue) -> TokenStream {
|
||||||
|
let prefix = quote! { ::serde_json::Value };
|
||||||
|
|
||||||
|
match jv {
|
||||||
|
JsonValue::Null => quote! { #prefix::Null },
|
||||||
|
JsonValue::Bool(bool) => quote! { #prefix::Bool(#bool) },
|
||||||
|
JsonValue::Number(number) => json_value_number_lit(number),
|
||||||
|
JsonValue::String(str) => {
|
||||||
|
let s = str_lit(str);
|
||||||
|
quote! { #prefix::String(#s) }
|
||||||
|
}
|
||||||
|
JsonValue::Array(vec) => {
|
||||||
|
let items = vec.iter().map(json_value_lit);
|
||||||
|
quote! { #prefix::Array(vec![#(#items),*]) }
|
||||||
|
}
|
||||||
|
JsonValue::Object(map) => {
|
||||||
|
let map = map_lit(quote! { ::serde_json::Map }, map, str_lit, json_value_lit);
|
||||||
|
quote! { #prefix::Object(#map) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,10 +13,10 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@noble/curves": "^2.0.1",
|
"@noble/curves": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/call": "workspace:*",
|
"@tensamin/call": "workspace:*",
|
||||||
"@tensamin/chat": "workspace:*",
|
"@tensamin/chat": "workspace:*",
|
||||||
|
|
@ -33,17 +33,16 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"comlink": "^4.4.2",
|
"comlink": "^4.4.2",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"shadcn": "^4.0.7",
|
"shadcn": "^4.6.0",
|
||||||
"sonner": "^1.0.0",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwind-scrollbar-hide": "^4.0.0",
|
"tailwind-scrollbar-hide": "^4.0.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -51,11 +50,11 @@
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~6.0.3",
|
||||||
"typescript-eslint": "^8.57.0",
|
"typescript-eslint": "^8.57.0",
|
||||||
"vite": "^7.3.1"
|
"vite": "^8.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,11 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
<Button
|
<Button
|
||||||
disabled={callState !== "closed"}
|
disabled={callState !== "closed"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void joinCall(id, currentCalls[0].secret, currentCalls[0].id);
|
void joinCall(
|
||||||
|
id,
|
||||||
|
currentCalls[0].call_secret,
|
||||||
|
currentCalls[0].call_id,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
className="w-9 h-9 aspect-square rounded-lg"
|
className="w-9 h-9 aspect-square rounded-lg"
|
||||||
>
|
>
|
||||||
|
|
@ -105,13 +109,13 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{currentCalls.map((call) => (
|
{currentCalls.map((call) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
value={call.id}
|
value={call.call_id}
|
||||||
key={call.id}
|
key={call.call_id}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
void joinCall(id, call.secret, call.id);
|
void joinCall(id, call.call_secret, call.call_id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{displayCallId(call.id)}
|
{displayCallId(call.call_id)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,6 @@ export default function Screen(props: { children: React.ReactNode }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true;
|
let active = true;
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
setError("");
|
|
||||||
setErrorDescription("");
|
|
||||||
|
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const id = await load("user_id");
|
const id = await load("user_id");
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
import { defineConfig, type Plugin } from "vite";
|
import { defineConfig, type Plugin } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tsconfigPaths from "vite-tsconfig-paths";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
const host = process.env.TAURI_DEV_HOST;
|
||||||
|
|
@ -54,6 +53,7 @@ function deepFilterAssetHeaders(rootDir: string): Plugin {
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
resolve: {
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
alias: [
|
alias: [
|
||||||
{
|
{
|
||||||
find: "@codemirror/commands",
|
find: "@codemirror/commands",
|
||||||
|
|
@ -102,7 +102,6 @@ export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
deepFilterAssetHeaders(resolve(appDir, "public")),
|
deepFilterAssetHeaders(resolve(appDir, "public")),
|
||||||
react(),
|
react(),
|
||||||
tsconfigPaths(),
|
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
],
|
],
|
||||||
worker: {
|
worker: {
|
||||||
|
|
|
||||||
357
bun.lock
357
bun.lock
|
|
@ -14,14 +14,14 @@
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/parser": "^8.58.1",
|
"@typescript-eslint/parser": "^8.59.1",
|
||||||
"eslint": "^10.2.0",
|
"eslint": "^10.2.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.5.0",
|
||||||
"jsonc-parser": "^3.3.1",
|
"jsonc-parser": "^3.3.1",
|
||||||
"prettier": "^3.8.2",
|
"prettier": "^3.8.3",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.3",
|
||||||
"typescript-eslint": "^8.58.1",
|
"typescript-eslint": "^8.59.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"apps/tauri": {
|
"apps/tauri": {
|
||||||
|
|
@ -34,12 +34,12 @@
|
||||||
"@tauri-apps/plugin-opener": "^2",
|
"@tauri-apps/plugin-opener": "^2",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"lucide-react": "^1.7.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2",
|
"@tauri-apps/cli": "^2.11.0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"apps/web": {
|
"apps/web": {
|
||||||
|
|
@ -47,10 +47,10 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource-variable/inter": "^5.2.8",
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@noble/curves": "^2.0.1",
|
"@noble/curves": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.4",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/call": "workspace:*",
|
"@tensamin/call": "workspace:*",
|
||||||
"@tensamin/chat": "workspace:*",
|
"@tensamin/chat": "workspace:*",
|
||||||
|
|
@ -67,17 +67,16 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"comlink": "^4.4.2",
|
"comlink": "^4.4.2",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"shadcn": "^4.0.7",
|
"shadcn": "^4.6.0",
|
||||||
"sonner": "^1.0.0",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwind-scrollbar-hide": "^4.0.0",
|
"tailwind-scrollbar-hide": "^4.0.0",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.4",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -85,12 +84,12 @@
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"eslint": "^10.0.3",
|
"eslint": "^10.0.3",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.4.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~6.0.3",
|
||||||
"typescript-eslint": "^8.57.0",
|
"typescript-eslint": "^8.57.0",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/call": {
|
"packages/call": {
|
||||||
|
|
@ -98,9 +97,9 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livekit/components-react": "^2.9.20",
|
"@livekit/components-react": "^2.9.20",
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.100.7",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
"@tensamin/markdown": "workspace:*",
|
||||||
|
|
@ -111,8 +110,8 @@
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"deepfilternet3-noise-filter": "^1.2.1",
|
"deepfilternet3-noise-filter": "^1.2.1",
|
||||||
"livekit-client": "^2.18.1",
|
"livekit-client": "^2.18.8",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
|
|
@ -135,7 +134,7 @@
|
||||||
"@tensamin/ttp": "workspace:*",
|
"@tensamin/ttp": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
|
|
@ -158,7 +157,7 @@
|
||||||
"@codemirror/commands": "^6.10.2",
|
"@codemirror/commands": "^6.10.2",
|
||||||
"@codemirror/lang-markdown": "^6.5.0",
|
"@codemirror/lang-markdown": "^6.5.0",
|
||||||
"@codemirror/state": "^6.5.4",
|
"@codemirror/state": "^6.5.4",
|
||||||
"@codemirror/view": "^6.39.15",
|
"@codemirror/view": "^6.41.1",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
|
@ -185,10 +184,10 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"sonner": "^1.0.0",
|
"sonner": "^2.0.7",
|
||||||
"zod": "^4.3.6",
|
"zod": "^4.3.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -255,7 +254,7 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz",
|
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.15.tar.gz",
|
||||||
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
@ -307,10 +306,6 @@
|
||||||
|
|
||||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
|
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
|
||||||
|
|
||||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
|
||||||
|
|
||||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
||||||
|
|
||||||
"@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
|
"@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
|
||||||
|
|
@ -347,7 +342,7 @@
|
||||||
|
|
||||||
"@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="],
|
"@codemirror/state": ["@codemirror/state@6.6.0", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ=="],
|
||||||
|
|
||||||
"@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
"@codemirror/view": ["@codemirror/view@6.41.1", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-ToDnWKbBnke+ZLrP6vgTTDScGi5H37YYuZGniQaBzxMVdtCxMrslsmtnOvbPZk4RX9bvkQqnWR/WS/35tJA0qg=="],
|
||||||
|
|
||||||
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
||||||
|
|
||||||
|
|
@ -355,57 +350,11 @@
|
||||||
|
|
||||||
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
|
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
|
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
|
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
|
|
||||||
|
|
||||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
|
|
||||||
|
|
||||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
|
|
||||||
|
|
||||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
|
|
||||||
|
|
||||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
|
|
||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
|
||||||
|
|
||||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||||
|
|
||||||
|
|
@ -483,7 +432,7 @@
|
||||||
|
|
||||||
"@livekit/mutex": ["@livekit/mutex@1.1.1", "", {}, "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw=="],
|
"@livekit/mutex": ["@livekit/mutex@1.1.1", "", {}, "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw=="],
|
||||||
|
|
||||||
"@livekit/protocol": ["@livekit/protocol@1.44.0", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-/vfhDUGcUKO8Q43r6i+5FrDhl5oZjm/X3U4x2Iciqvgn5C8qbj+57YPcWSJ1kyIZm5Cm6AV2nAPjMm3ETD/iyg=="],
|
"@livekit/protocol": ["@livekit/protocol@1.45.3", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-WmMxBTsy4dRBqcrswFwUUlgq3Z0nnhOqKR6tX749Rb/PcB1yBMUtrHxZvcsS6qi3/5+86zHeVG+exmu1sZqfJg=="],
|
||||||
|
|
||||||
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
||||||
|
|
||||||
|
|
@ -491,11 +440,13 @@
|
||||||
|
|
||||||
"@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="],
|
"@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="],
|
||||||
|
|
||||||
|
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||||
|
|
||||||
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
"@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="],
|
||||||
|
|
||||||
"@noble/curves": ["@noble/curves@2.0.1", "", { "dependencies": { "@noble/hashes": "2.0.1" } }, "sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw=="],
|
"@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="],
|
||||||
|
|
||||||
"@noble/hashes": ["@noble/hashes@2.0.1", "", {}, "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw=="],
|
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
|
||||||
|
|
||||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||||
|
|
||||||
|
|
@ -509,6 +460,8 @@
|
||||||
|
|
||||||
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
|
"@open-draft/until": ["@open-draft/until@2.1.0", "", {}, "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg=="],
|
||||||
|
|
||||||
|
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
|
||||||
|
|
||||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||||
|
|
@ -545,57 +498,37 @@
|
||||||
|
|
||||||
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="],
|
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="],
|
||||||
|
|
||||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
|
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="],
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
|
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
|
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="],
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
|
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
|
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="],
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
|
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
|
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
|
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
|
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
|
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
|
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
|
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
|
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
|
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
|
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="],
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
|
|
||||||
|
|
||||||
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
||||||
|
|
||||||
|
|
@ -607,79 +540,79 @@
|
||||||
|
|
||||||
"@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="],
|
"@tabby_ai/hijri-converter": ["@tabby_ai/hijri-converter@1.0.5", "", {}, "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ=="],
|
||||||
|
|
||||||
"@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
|
"@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
|
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="],
|
||||||
|
|
||||||
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
|
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="],
|
||||||
|
|
||||||
"@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
|
"@tanstack/history": ["@tanstack/history@1.161.6", "", {}, "sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg=="],
|
||||||
|
|
||||||
"@tanstack/query-core": ["@tanstack/query-core@5.99.0", "", {}, "sha512-3Jv3WQG0BCcH7G+7lf/bP8QyBfJOXeY+T08Rin3GZ1bshvwlbPt7NrDHMEzGdKIOmOzvIQmxjk28YEQX60k7pQ=="],
|
"@tanstack/query-core": ["@tanstack/query-core@5.100.7", "", {}, "sha512-5R7i6ENJLhVeeJrrUz7jKBXUXv/BJrxf9FQJSkR13bPrb3zOcE8A0Z0PxYCcsKPOsiIlTibrBL/zZbtUO1TFyQ=="],
|
||||||
|
|
||||||
"@tanstack/react-query": ["@tanstack/react-query@5.99.0", "", { "dependencies": { "@tanstack/query-core": "5.99.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-OY2bCqPemT1LlqJ8Y2CUau4KELnIhhG9Ol3ZndPbdnB095pRbPo1cHuXTndg8iIwtoHTgwZjyaDnQ0xD0mYwAw=="],
|
"@tanstack/react-query": ["@tanstack/react-query@5.100.7", "", { "dependencies": { "@tanstack/query-core": "5.100.7" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-LoISYWz8dOOuQbeIctF8K6yi42TWtR1WPGpwGuRUpF3u79JVVIg/PVR0MQdIA0VSHqD/ydf/b7PhKTkg3I4fLQ=="],
|
||||||
|
|
||||||
"@tanstack/react-router": ["@tanstack/react-router@1.168.18", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.168.14", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-RmBptS3/qtkGhvG/u41JWOgxz1FIWybBz7iBTgLUIoFkqOj6NE4XlhUOsP2fabxACtbZdJnpvCWcJFWpWGIngw=="],
|
"@tanstack/react-router": ["@tanstack/react-router@1.169.1", "", { "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", "@tanstack/router-core": "1.169.1", "isbot": "^5.1.22" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-MBtQKSvac3OCcsSa6oBpDrrN90IV47I6Gtv05NxhbFVh+gVjtqvs6HSU4XM9+y5sHZPgS+35eArflX4vM8GEnQ=="],
|
||||||
|
|
||||||
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
|
"@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="],
|
||||||
|
|
||||||
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.23", "", { "dependencies": { "@tanstack/virtual-core": "3.13.23" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XnMRnHQ23piOVj2bzJqHrRrLg4r+F86fuBcwteKfbIjJrtGxb4z7tIvPVAe4B+4UVwo9G4Giuz5fmapcrnZ0OQ=="],
|
"@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.24", "", { "dependencies": { "@tanstack/virtual-core": "3.14.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg=="],
|
||||||
|
|
||||||
"@tanstack/router-core": ["@tanstack/router-core@1.168.14", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-UhCJtjNrd5wcTmhgB2HyUP0+Rj1M7BD4dS11YsF9x6VC2KH/eqxzs/vK+nN5f+cOhPOLZdmLkWMW+WGmacZ8HA=="],
|
"@tanstack/router-core": ["@tanstack/router-core@1.169.1", "", { "dependencies": { "@tanstack/history": "1.161.6", "cookie-es": "^3.0.0", "seroval": "^1.5.0", "seroval-plugins": "^1.5.0" }, "bin": { "intent": "bin/intent.js" } }, "sha512-x+2gIGKTTE1qAn7tLieGfrB5ciOviDmmi2ox9fAWUubRV+yTU5ruGFXocoCIWF+lB+SOtnHjo2E9BLSWyYoEmA=="],
|
||||||
|
|
||||||
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
|
"@tanstack/store": ["@tanstack/store@0.9.3", "", {}, "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw=="],
|
||||||
|
|
||||||
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.23", "", {}, "sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg=="],
|
"@tanstack/virtual-core": ["@tanstack/virtual-core@3.14.0", "", {}, "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q=="],
|
||||||
|
|
||||||
"@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
"@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli": ["@tauri-apps/cli@2.10.1", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.1", "@tauri-apps/cli-darwin-x64": "2.10.1", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", "@tauri-apps/cli-linux-arm64-musl": "2.10.1", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-gnu": "2.10.1", "@tauri-apps/cli-linux-x64-musl": "2.10.1", "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", "@tauri-apps/cli-win32-x64-msvc": "2.10.1" }, "bin": { "tauri": "tauri.js" } }, "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g=="],
|
"@tauri-apps/cli": ["@tauri-apps/cli@2.11.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.11.0", "@tauri-apps/cli-darwin-x64": "2.11.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.0", "@tauri-apps/cli-linux-arm64-gnu": "2.11.0", "@tauri-apps/cli-linux-arm64-musl": "2.11.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.11.0", "@tauri-apps/cli-linux-x64-gnu": "2.11.0", "@tauri-apps/cli-linux-x64-musl": "2.11.0", "@tauri-apps/cli-win32-arm64-msvc": "2.11.0", "@tauri-apps/cli-win32-ia32-msvc": "2.11.0", "@tauri-apps/cli-win32-x64-msvc": "2.11.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-W5Wbuqsb2pHFPTj4TaRNKTj5rwXhDShPiLSY9T18y4ouSR/NNCptAEFxFsBtyNRgL6Vs1a/q9LzfqqYzEwC+Jw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ=="],
|
"@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.11.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UfMeDNlgIP252rm/KSTuu8yHatPua5TjtUEUf+jyIzVwBNcIl7Ywkdpfj+e5jVVg3EfCTp+4gwuL1dNpgF8clg=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw=="],
|
"@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.11.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-lY1+aPlgyMN7vgjtCdQ3+WODfZkebAcxnrCrO0HjqDpKSXieDkrJbimqeaoM4RwhTSrCLRHfVYiYrfE5E131tg=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w=="],
|
"@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.11.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5uCP0AusgN3NrKC8EpkuJwjek1k8pEffBdugJSpXPey/QGbPEb8vZ542n/giJ2mZPjMSllDkdhG2QIDpBY4PpQ=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA=="],
|
"@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-loDPqtRHMSbIcrH2VBd4GgHoQlF7jJnrZj7MxA2lj1cixS/jEgMAPFqj83U6Wvjete4HfYplbE/gCpSFifA9jw=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg=="],
|
"@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.11.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DtSE8ZBlB9H+L+eHkfZ3myt00EVEyAB3e41juEHoE2qT88fgVlJvyrwa9SZYc/xTwCS9TnmK+R84tpg+ZsAg7Q=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.1", "", { "os": "linux", "cpu": "none" }, "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw=="],
|
"@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.11.0", "", { "os": "linux", "cpu": "none" }, "sha512-5QdgS4LD+kntClI1aj2JmwjW38LosNXxwCe8viIHEwqYIWuMPdNEIau6/cLogI38Yzx9DnfCPRfEWLyI+5li8Q=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw=="],
|
"@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5UynPXo3Zq9khjVdAbD+YogeLltdVUeOah2ioSIM3tu6H7wY9vMy6rgGJhv9r5R8ZXmk9GttMippdqYJWrnLnA=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.1", "", { "os": "linux", "cpu": "x64" }, "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ=="],
|
"@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.11.0", "", { "os": "linux", "cpu": "x64" }, "sha512-CNz7fHbApz1Zyhhq73jtGn9JqgNEV/lIWnTnUo6h6ujw+mHsTmkLszvJSM8W6JBaDjNpTTFr/RSNoVL5FMwcTg=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg=="],
|
"@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.11.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-K+br+VXZ+Xx0n/9FdWohpW5Ugq+2FQUpJScqcPl1hTxXfh3fgjYgt4qA2NgrjlJo+zZPNrmUMl+NLvm0ufEqBQ=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw=="],
|
"@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.11.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-OFV+s3MLZnd75zl0ZAFU5riMpGK4waUEA8ZDuijDsnkU0btz/gHhqh5jVlOn8thyvgdtT3Xyoxqo099MMifH3g=="],
|
||||||
|
|
||||||
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg=="],
|
"@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.11.0", "", { "os": "win32", "cpu": "x64" }, "sha512-AeDTWBd2cOZ6TX133BWsoo+LutG9o0JRcgjMsIfLE13ZugpgCMv/2dJbUiBGeRvbPOGin5A3aYmsArPVV6ZSHQ=="],
|
||||||
|
|
||||||
"@tauri-apps/plugin-barcode-scanner": ["@tauri-apps/plugin-barcode-scanner@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-uXvyMI8UgQjSrGxzTU5isNoQarMGRxFmTmb4TsgiWZHf/g7LsIyAQCwoFShjax0fXCK5mdVKDOvlkfOr21fo6g=="],
|
"@tauri-apps/plugin-barcode-scanner": ["@tauri-apps/plugin-barcode-scanner@2.4.4", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-uXvyMI8UgQjSrGxzTU5isNoQarMGRxFmTmb4TsgiWZHf/g7LsIyAQCwoFShjax0fXCK5mdVKDOvlkfOr21fo6g=="],
|
||||||
|
|
||||||
|
|
@ -707,7 +640,7 @@
|
||||||
|
|
||||||
"@tensamin/ttp": ["@tensamin/ttp@workspace:packages/ttp"],
|
"@tensamin/ttp": ["@tensamin/ttp@workspace:packages/ttp"],
|
||||||
|
|
||||||
"@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.58.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.2", "typescript-eslint": "^8.58.1", "zod": "^4.3.6" } }, "sha512-ib5E8n9IfMzai+ucOXwTNMtu4j6m1RSHANGbFVJxP1VwJDtqIVMqpg/NtTKdRNXQYM1hK5WAScfl6r+aeyaj4g=="],
|
"@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.15.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.59.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.3", "typescript-eslint": "^8.59.1", "zod": "^4.4.1" } }, "sha512-KzDZ/p63dZd5q67oZakWs12rO8Mc2deqnoLM9P+fiDSrly0jNpAiTSWhe5bd5TS/uyoqk5opp2Ge047JS8q/hA=="],
|
||||||
|
|
||||||
"@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-Eh/ko+4x4gF/25JyB38PNN2n/UVUIR6yxxZTpyZPx4maL00992pGmFt62kb1ejJQoooEpEfSo/ALtQDF5cjdoA=="],
|
"@tensamin/ui": ["@tensamin/ui@https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz", { "dependencies": { "@base-ui/react": "^1.3.0", "@fontsource-variable/inter": "^5.2.6", "@tauri-apps/api": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "embla-carousel-react": "^8.6.0", "input-otp": "^1.4.2", "lucide-react": "^1.8.0", "next-themes": "^0.4.6", "react-day-picker": "^9.14.0", "react-resizable-panels": "^4.10.0", "recharts": "3.8.0", "shadcn": "^3.5.0", "sonner": "^2.0.7", "tailwind-merge": "^3.5.0", "tw-animate-css": "^1.3.0", "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.2.0", "react-dom": "^19.2.0" } }, "sha512-Eh/ko+4x4gF/25JyB38PNN2n/UVUIR6yxxZTpyZPx4maL00992pGmFt62kb1ejJQoooEpEfSo/ALtQDF5cjdoA=="],
|
||||||
|
|
||||||
|
|
@ -717,13 +650,7 @@
|
||||||
|
|
||||||
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
|
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
|
||||||
|
|
||||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
|
||||||
|
|
||||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
|
||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
|
||||||
|
|
||||||
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||||
|
|
||||||
|
|
@ -765,27 +692,27 @@
|
||||||
|
|
||||||
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
|
"@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/type-utils": "8.58.1", "@typescript-eslint/utils": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ=="],
|
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/type-utils": "8.59.1", "@typescript-eslint/utils": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag=="],
|
||||||
|
|
||||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw=="],
|
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA=="],
|
||||||
|
|
||||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.1", "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g=="],
|
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.1", "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg=="],
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1" } }, "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w=="],
|
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1" } }, "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg=="],
|
||||||
|
|
||||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw=="],
|
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA=="],
|
||||||
|
|
||||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w=="],
|
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w=="],
|
||||||
|
|
||||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="],
|
"@typescript-eslint/types": ["@typescript-eslint/types@8.59.1", "", {}, "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.1", "@typescript-eslint/tsconfig-utils": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg=="],
|
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.1", "@typescript-eslint/tsconfig-utils": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g=="],
|
||||||
|
|
||||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ=="],
|
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.1", "@typescript-eslint/types": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA=="],
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ=="],
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.1", "", { "dependencies": { "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg=="],
|
||||||
|
|
||||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
|
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="],
|
||||||
|
|
||||||
"@webtransport-bun/webtransport": ["@webtransport-bun/webtransport@0.3.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-/OX/TCgBD64n/0BjMNytObq5NK2pAM0KOoXBEw49l3sTmbVXNTPP1ZRPIdIyV43agM+mKtZBaV3Er3k3YoB6sw=="],
|
"@webtransport-bun/webtransport": ["@webtransport-bun/webtransport@0.3.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-/OX/TCgBD64n/0BjMNytObq5NK2pAM0KOoXBEw49l3sTmbVXNTPP1ZRPIdIyV43agM+mKtZBaV3Er3k3YoB6sw=="],
|
||||||
|
|
||||||
|
|
@ -981,17 +908,15 @@
|
||||||
|
|
||||||
"es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
|
"es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||||
|
|
||||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||||
|
|
||||||
"eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="],
|
"eslint": ["eslint@10.2.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q=="],
|
||||||
|
|
||||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
|
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
|
||||||
|
|
||||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||||
|
|
||||||
|
|
@ -1093,8 +1018,6 @@
|
||||||
|
|
||||||
"globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="],
|
"globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="],
|
||||||
|
|
||||||
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
|
|
||||||
|
|
||||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
@ -1235,7 +1158,7 @@
|
||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
"livekit-client": ["livekit-client@2.18.1", "", { "dependencies": { "@livekit/mutex": "1.1.1", "@livekit/protocol": "1.44.0", "events": "^3.3.0", "jose": "^6.1.0", "loglevel": "^1.9.2", "sdp-transform": "^2.15.0", "tslib": "2.8.1", "typed-emitter": "^2.1.0", "webrtc-adapter": "^9.0.1" }, "peerDependencies": { "@types/dom-mediacapture-record": "^1" } }, "sha512-nGjuEEV1mVN01EcAMwGIwG3J1gpBMqwn2V4R6W/8zz9Rah1CaAohIm6AMLG7BdctQpyeh34dfAOLfDodIsWyYA=="],
|
"livekit-client": ["livekit-client@2.18.8", "", { "dependencies": { "@livekit/mutex": "1.1.1", "@livekit/protocol": "1.45.3", "events": "^3.3.0", "jose": "^6.1.0", "loglevel": "^1.9.2", "sdp-transform": "^2.15.0", "tslib": "2.8.1", "typed-emitter": "^2.1.0", "webrtc-adapter": "9.0.5" }, "peerDependencies": { "@types/dom-mediacapture-record": "^1" } }, "sha512-E+bSpnBVng/1xG4RfL1Q51dHUpBwL14Wix4sR5bS0djEzKMEtrxcUyhWLltdwQ0USf1t0PaxW6WL4oVb2s4Fsw=="],
|
||||||
|
|
||||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||||
|
|
||||||
|
|
@ -1247,7 +1170,7 @@
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||||
|
|
||||||
"lucide-react": ["lucide-react@0.564.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JJ8GVTQqFwuliifD48U6+h7DXEHdkhJ/E87kksGByII3qHxtPciVb8T8woQONHBQgHVOl7rSMrrip3SeVNy7Fg=="],
|
"lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
|
@ -1353,7 +1276,7 @@
|
||||||
|
|
||||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
|
"postcss": ["postcss@8.5.13", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag=="],
|
||||||
|
|
||||||
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
"postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||||
|
|
||||||
|
|
@ -1361,7 +1284,7 @@
|
||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
"prettier": ["prettier@3.8.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q=="],
|
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
|
||||||
|
|
||||||
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
"pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
||||||
|
|
||||||
|
|
@ -1391,8 +1314,6 @@
|
||||||
|
|
||||||
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
||||||
|
|
||||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
|
||||||
|
|
||||||
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
|
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
|
||||||
|
|
||||||
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||||
|
|
@ -1425,7 +1346,7 @@
|
||||||
|
|
||||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||||
|
|
||||||
"rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
|
"rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="],
|
||||||
|
|
||||||
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||||
|
|
||||||
|
|
@ -1457,7 +1378,7 @@
|
||||||
|
|
||||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||||
|
|
||||||
"shadcn": ["shadcn@4.2.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-ZDuV340itidaUd4Gi1BxQX+Y7Ush6BHp6URZBM2RyxUUBZ6yFtOWIr4nVY+Ro+YRSpo82v7JrsmtcU5xoBCMJQ=="],
|
"shadcn": ["shadcn@4.6.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-4XeMwFf8ZZxmqQQp+U+Nsq2M+cY4Da8Joo/EaMdHVc4uVuWSTJoeidlZ3gDjyxXCjYB1FLcxYwR4lYQAH8emOg=="],
|
||||||
|
|
||||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
|
@ -1507,7 +1428,7 @@
|
||||||
|
|
||||||
"tailwind-scrollbar-hide": ["tailwind-scrollbar-hide@4.0.0", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20" } }, "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ=="],
|
"tailwind-scrollbar-hide": ["tailwind-scrollbar-hide@4.0.0", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20" } }, "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
|
"tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="],
|
||||||
|
|
||||||
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||||
|
|
||||||
|
|
@ -1531,8 +1452,6 @@
|
||||||
|
|
||||||
"ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
|
"ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
|
||||||
|
|
||||||
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
|
|
||||||
|
|
||||||
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
|
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
@ -1547,9 +1466,9 @@
|
||||||
|
|
||||||
"typed-emitter": ["typed-emitter@2.1.0", "", { "optionalDependencies": { "rxjs": "*" } }, "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA=="],
|
"typed-emitter": ["typed-emitter@2.1.0", "", { "optionalDependencies": { "rxjs": "*" } }, "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA=="],
|
||||||
|
|
||||||
"typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
|
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
"typescript-eslint": ["typescript-eslint@8.58.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg=="],
|
"typescript-eslint": ["typescript-eslint@8.59.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.1", "@typescript-eslint/parser": "8.59.1", "@typescript-eslint/typescript-estree": "8.59.1", "@typescript-eslint/utils": "8.59.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ=="],
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||||
|
|
||||||
|
|
@ -1583,15 +1502,13 @@
|
||||||
|
|
||||||
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
|
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
|
||||||
|
|
||||||
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
"vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="],
|
||||||
|
|
||||||
"vite-tsconfig-paths": ["vite-tsconfig-paths@6.1.1", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg=="],
|
|
||||||
|
|
||||||
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
"w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="],
|
||||||
|
|
||||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||||
|
|
||||||
"webrtc-adapter": ["webrtc-adapter@9.0.4", "", { "dependencies": { "sdp": "^3.2.0" } }, "sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw=="],
|
"webrtc-adapter": ["webrtc-adapter@9.0.5", "", { "dependencies": { "sdp": "^3.2.0" } }, "sha512-U9vjByy/sK2OMXu5mmfuZFKTMIUQe34c0JXRO+oDrxJTsntdYT2iIFwYMOV7HhMTuktcZLGf2W1N/OcSf9ssWg=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
|
@ -1621,7 +1538,7 @@
|
||||||
|
|
||||||
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
|
"yoctocolors-cjs": ["yoctocolors-cjs@2.1.3", "", {}, "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw=="],
|
||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod": ["zod@4.4.1", "", {}, "sha512-a6ENMBBGZBsnlSebQ/eKCguSBeGKSf4O7BPnqVPmYGtpBYI7VSqoVqw+QcB7kPRjbqPwhYTpFbVj/RqNz/CT0Q=="],
|
||||||
|
|
||||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||||
|
|
||||||
|
|
@ -1635,6 +1552,20 @@
|
||||||
|
|
||||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||||
|
|
||||||
|
"@codemirror/autocomplete/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/commands/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/lang-html/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/lang-javascript/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/lang-markdown/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/language/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
|
"@codemirror/lint/@codemirror/view": ["@codemirror/view@6.41.0", "", { "dependencies": { "@codemirror/state": "^6.6.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA=="],
|
||||||
|
|
||||||
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
"@dotenvx/dotenvx/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||||
|
|
||||||
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||||
|
|
@ -1649,6 +1580,8 @@
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
"@modelcontextprotocol/sdk/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
@ -1661,25 +1594,25 @@
|
||||||
|
|
||||||
"@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
|
"@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ=="],
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@tensamin/notifications/@tauri-apps/api": ["@tauri-apps/api@2.11.0", "", {}, "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA=="],
|
"@tauri-apps/plugin-barcode-scanner/@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tensamin/shared/sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
|
"@tauri-apps/plugin-deep-link/@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tensamin/tauri/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
"@tauri-apps/plugin-opener/@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tensamin/tauth/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
"@tensamin/ui/@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="],
|
||||||
|
|
||||||
"@tensamin/ui/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
"@tensamin/ui/lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="],
|
||||||
|
|
||||||
|
|
@ -1687,10 +1620,6 @@
|
||||||
|
|
||||||
"@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
"@tensamin/ui/shadcn": ["shadcn@3.8.5", "", { "dependencies": { "@antfu/ni": "^25.0.0", "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "https-proxy-agent": "^7.0.6", "kleur": "^4.1.5", "msw": "^2.10.4", "node-fetch": "^3.3.2", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-jPRx44e+eyeV7xwY3BLJXcfrks00+M0h5BGB9l6DdcBW4BpAj4x3lVmVy0TXPEs2iHEisxejr62sZAAw6B1EVA=="],
|
||||||
|
|
||||||
"@tensamin/web/sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="],
|
|
||||||
|
|
||||||
"@tensamin/web/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
"ajv-formats/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||||
|
|
@ -1719,6 +1648,8 @@
|
||||||
|
|
||||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||||
|
|
||||||
|
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="],
|
||||||
|
|
||||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||||
|
|
||||||
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
@ -1745,6 +1676,8 @@
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
|
"@tensamin/ui/shadcn/postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="],
|
||||||
|
|
||||||
"@tensamin/ui/shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@tensamin/ui/shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
"ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
|
||||||
177
licenses/@tauri-apps_api@2.11.0/LICENSE_APACHE-2.0
Normal file
177
licenses/@tauri-apps_api@2.11.0/LICENSE_APACHE-2.0
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) Alec Larson
|
Copyright (c) 2017 - Present Tauri Apps Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
177
licenses/@tauri-apps_cli@2.11.0/LICENSE_APACHE-2.0
Normal file
177
licenses/@tauri-apps_cli@2.11.0/LICENSE_APACHE-2.0
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
21
licenses/@tauri-apps_cli@2.11.0/LICENSE_MIT
Normal file
21
licenses/@tauri-apps_cli@2.11.0/LICENSE_MIT
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 - Present Tauri Apps Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -29,13 +29,13 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/@codemirror_state@6.6.0`
|
- Folder: `licenses/@codemirror_state@6.6.0`
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/state`
|
- Source package dir: `packages/markdown/node_modules/@codemirror/state`
|
||||||
|
|
||||||
## @codemirror/view@6.41.0
|
## @codemirror/view@6.41.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Repository: git+https://github.com/codemirror/view.git
|
- Repository: git+https://code.haverbeke.berlin/codemirror/view.git
|
||||||
- Description: DOM view component for the CodeMirror code editor
|
- Description: DOM view component for the CodeMirror code editor
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@codemirror_view@6.41.0`
|
- Folder: `licenses/@codemirror_view@6.41.1`
|
||||||
- Source package dir: `packages/markdown/node_modules/@codemirror/view`
|
- Source package dir: `packages/markdown/node_modules/@codemirror/view`
|
||||||
|
|
||||||
## @eslint/js@10.0.1
|
## @eslint/js@10.0.1
|
||||||
|
|
@ -66,74 +66,74 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/@livekit_components-react@2.9.20`
|
- Folder: `licenses/@livekit_components-react@2.9.20`
|
||||||
- Source package dir: `packages/call/node_modules/@livekit/components-react`
|
- Source package dir: `packages/call/node_modules/@livekit/components-react`
|
||||||
|
|
||||||
## @noble/curves@2.0.1
|
## @noble/curves@2.2.0
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://paulmillr.com/noble/
|
- Homepage: https://paulmillr.com/noble/
|
||||||
- Repository: git+https://github.com/paulmillr/noble-curves.git
|
- Repository: git+https://github.com/paulmillr/noble-curves.git
|
||||||
- Description: Audited & minimal JS implementation of elliptic curve cryptography
|
- Description: Audited & minimal JS implementation of elliptic curve cryptography
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@noble_curves@2.0.1`
|
- Folder: `licenses/@noble_curves@2.2.0`
|
||||||
- Source package dir: `apps/web/node_modules/@noble/curves`
|
- Source package dir: `apps/web/node_modules/@noble/curves`
|
||||||
|
|
||||||
## @tailwindcss/vite@4.2.2
|
## @tailwindcss/vite@4.2.4
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://tailwindcss.com
|
- Homepage: https://tailwindcss.com
|
||||||
- Repository: https://github.com/tailwindlabs/tailwindcss.git
|
- Repository: https://github.com/tailwindlabs/tailwindcss.git
|
||||||
- Description: A utility-first CSS framework for rapidly building custom user interfaces.
|
- Description: A utility-first CSS framework for rapidly building custom user interfaces.
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@tailwindcss_vite@4.2.2`
|
- Folder: `licenses/@tailwindcss_vite@4.2.4`
|
||||||
- Source package dir: `apps/web/node_modules/@tailwindcss/vite`
|
- Source package dir: `apps/web/node_modules/@tailwindcss/vite`
|
||||||
|
|
||||||
## @tanstack/react-query@5.99.0
|
## @tanstack/react-query@5.100.7
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://tanstack.com/query
|
- Homepage: https://tanstack.com/query
|
||||||
- Repository: git+https://github.com/TanStack/query.git
|
- Repository: git+https://github.com/TanStack/query.git
|
||||||
- Description: Hooks for managing, caching and syncing asynchronous and remote data in React
|
- Description: Hooks for managing, caching and syncing asynchronous and remote data in React
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@tanstack_react-query@5.99.0`
|
- Folder: `licenses/@tanstack_react-query@5.100.7`
|
||||||
- Source package dir: `packages/call/node_modules/@tanstack/react-query`
|
- Source package dir: `packages/call/node_modules/@tanstack/react-query`
|
||||||
|
|
||||||
## @tanstack/react-router@1.168.18
|
## @tanstack/react-router@1.169.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://tanstack.com/router
|
- Homepage: https://tanstack.com/router
|
||||||
- Repository: git+https://github.com/TanStack/router.git
|
- Repository: git+https://github.com/TanStack/router.git
|
||||||
- Description: Modern and scalable routing for React applications
|
- Description: Modern and scalable routing for React applications
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@tanstack_react-router@1.168.18`
|
- Folder: `licenses/@tanstack_react-router@1.169.1`
|
||||||
- Source package dir: `apps/web/node_modules/@tanstack/react-router`
|
- Source package dir: `apps/web/node_modules/@tanstack/react-router`
|
||||||
|
|
||||||
## @tanstack/react-virtual@3.13.23
|
## @tanstack/react-virtual@3.13.24
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://tanstack.com/virtual
|
- Homepage: https://tanstack.com/virtual
|
||||||
- Repository: git+https://github.com/TanStack/virtual.git
|
- Repository: git+https://github.com/TanStack/virtual.git
|
||||||
- Description: Headless UI for virtualizing scrollable elements in React
|
- Description: Headless UI for virtualizing scrollable elements in React
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@tanstack_react-virtual@3.13.23`
|
- Folder: `licenses/@tanstack_react-virtual@3.13.24`
|
||||||
- Source package dir: `apps/web/node_modules/@tanstack/react-virtual`
|
- Source package dir: `apps/web/node_modules/@tanstack/react-virtual`
|
||||||
|
|
||||||
## @tauri-apps/api@2.10.1
|
## @tauri-apps/api@2.11.0
|
||||||
|
|
||||||
- License: Apache-2.0 OR MIT
|
- License: Apache-2.0 OR MIT
|
||||||
- Homepage: https://github.com/tauri-apps/tauri#readme
|
- Homepage: https://github.com/tauri-apps/tauri#readme
|
||||||
- Repository: git+https://github.com/tauri-apps/tauri.git
|
- Repository: git+https://github.com/tauri-apps/tauri.git
|
||||||
- Description: Tauri API definitions
|
- Description: Tauri API definitions
|
||||||
- Included files: LICENSE_MIT, LICENSE_APACHE-2.0
|
- Included files: LICENSE_MIT, LICENSE_APACHE-2.0
|
||||||
- Folder: `licenses/@tauri-apps_api@2.10.1`
|
- Folder: `licenses/@tauri-apps_api@2.11.0`
|
||||||
- Source package dir: `apps/tauri/node_modules/@tauri-apps/api`
|
- Source package dir: `apps/tauri/node_modules/@tauri-apps/api`
|
||||||
|
|
||||||
## @tauri-apps/cli@2.10.1
|
## @tauri-apps/cli@2.11.0
|
||||||
|
|
||||||
- License: Apache-2.0 OR MIT
|
- License: Apache-2.0 OR MIT
|
||||||
- Homepage: https://github.com/tauri-apps/tauri#readme
|
- Homepage: https://github.com/tauri-apps/tauri#readme
|
||||||
- Repository: git+https://github.com/tauri-apps/tauri.git
|
- Repository: git+https://github.com/tauri-apps/tauri.git
|
||||||
- Description: Command line interface for building Tauri apps
|
- Description: Command line interface for building Tauri apps
|
||||||
- Included files: LICENSE_MIT, LICENSE_APACHE-2.0
|
- Included files: LICENSE_MIT, LICENSE_APACHE-2.0
|
||||||
- Folder: `licenses/@tauri-apps_cli@2.10.1`
|
- Folder: `licenses/@tauri-apps_cli@2.11.0`
|
||||||
- Source package dir: `apps/tauri/node_modules/@tauri-apps/cli`
|
- Source package dir: `apps/tauri/node_modules/@tauri-apps/cli`
|
||||||
|
|
||||||
## @tauri-apps/plugin-barcode-scanner@2.4.4
|
## @tauri-apps/plugin-barcode-scanner@2.4.4
|
||||||
|
|
@ -163,11 +163,11 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/@tauri-apps_plugin-opener@2.5.3`
|
- Folder: `licenses/@tauri-apps_plugin-opener@2.5.3`
|
||||||
- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener`
|
- Source package dir: `apps/tauri/node_modules/@tauri-apps/plugin-opener`
|
||||||
|
|
||||||
## @tensamin/ttp-core@0.0.13
|
## @tensamin/ttp-core@0.0.15
|
||||||
|
|
||||||
- License: UNKNOWN
|
- License: UNKNOWN
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@tensamin_ttp-core@0.0.13`
|
- Folder: `licenses/@tensamin_ttp-core@0.0.15`
|
||||||
- Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core`
|
- Source package dir: `packages/ttp/node_modules/@tensamin/ttp-core`
|
||||||
|
|
||||||
## @tensamin/ui@0.0.30
|
## @tensamin/ui@0.0.30
|
||||||
|
|
@ -217,24 +217,24 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/@types_react-dom@19.2.3`
|
- Folder: `licenses/@types_react-dom@19.2.3`
|
||||||
- Source package dir: `apps/web/node_modules/@types/react-dom`
|
- Source package dir: `apps/web/node_modules/@types/react-dom`
|
||||||
|
|
||||||
## @typescript-eslint/parser@8.58.1
|
## @typescript-eslint/parser@8.59.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://typescript-eslint.io/packages/parser
|
- Homepage: https://typescript-eslint.io/packages/parser
|
||||||
- Repository: https://github.com/typescript-eslint/typescript-eslint.git
|
- Repository: https://github.com/typescript-eslint/typescript-eslint.git
|
||||||
- Description: An ESLint custom parser which leverages TypeScript ESTree
|
- Description: An ESLint custom parser which leverages TypeScript ESTree
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@typescript-eslint_parser@8.58.1`
|
- Folder: `licenses/@typescript-eslint_parser@8.59.1`
|
||||||
- Source package dir: `node_modules/@typescript-eslint/parser`
|
- Source package dir: `node_modules/@typescript-eslint/parser`
|
||||||
|
|
||||||
## @vitejs/plugin-react@5.2.0
|
## @vitejs/plugin-react@6.0.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme
|
- Homepage: https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme
|
||||||
- Repository: git+https://github.com/vitejs/vite-plugin-react.git
|
- Repository: git+https://github.com/vitejs/vite-plugin-react.git
|
||||||
- Description: The default Vite plugin for React projects
|
- Description: The default Vite plugin for React projects
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/@vitejs_plugin-react@5.2.0`
|
- Folder: `licenses/@vitejs_plugin-react@6.0.1`
|
||||||
- Source package dir: `apps/web/node_modules/@vitejs/plugin-react`
|
- Source package dir: `apps/web/node_modules/@vitejs/plugin-react`
|
||||||
|
|
||||||
## class-variance-authority@0.7.1
|
## class-variance-authority@0.7.1
|
||||||
|
|
@ -275,24 +275,24 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/deepfilternet3-noise-filter@1.2.1`
|
- Folder: `licenses/deepfilternet3-noise-filter@1.2.1`
|
||||||
- Source package dir: `packages/call/node_modules/deepfilternet3-noise-filter`
|
- Source package dir: `packages/call/node_modules/deepfilternet3-noise-filter`
|
||||||
|
|
||||||
## eslint@10.2.0
|
## eslint@10.2.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://eslint.org
|
- Homepage: https://eslint.org
|
||||||
- Repository: eslint/eslint
|
- Repository: eslint/eslint
|
||||||
- Description: An AST-based pattern checker for JavaScript.
|
- Description: An AST-based pattern checker for JavaScript.
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/eslint@10.2.0`
|
- Folder: `licenses/eslint@10.2.1`
|
||||||
- Source package dir: `apps/web/node_modules/eslint`
|
- Source package dir: `apps/web/node_modules/eslint`
|
||||||
|
|
||||||
## eslint-plugin-react-hooks@7.0.1
|
## eslint-plugin-react-hooks@7.1.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://react.dev/
|
- Homepage: https://react.dev/
|
||||||
- Repository: https://github.com/facebook/react.git
|
- Repository: https://github.com/facebook/react.git
|
||||||
- Description: ESLint rules for React Hooks
|
- Description: ESLint rules for React Hooks
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/eslint-plugin-react-hooks@7.0.1`
|
- Folder: `licenses/eslint-plugin-react-hooks@7.1.1`
|
||||||
- Source package dir: `node_modules/eslint-plugin-react-hooks`
|
- Source package dir: `node_modules/eslint-plugin-react-hooks`
|
||||||
|
|
||||||
## framer-motion@12.38.0
|
## framer-motion@12.38.0
|
||||||
|
|
@ -322,33 +322,33 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/jsonc-parser@3.3.1`
|
- Folder: `licenses/jsonc-parser@3.3.1`
|
||||||
- Source package dir: `node_modules/jsonc-parser`
|
- Source package dir: `node_modules/jsonc-parser`
|
||||||
|
|
||||||
## livekit-client@2.18.1
|
## livekit-client@2.18.8
|
||||||
|
|
||||||
- License: Apache-2.0
|
- License: Apache-2.0
|
||||||
- Repository: git@github.com:livekit/client-sdk-js.git
|
- Repository: git@github.com:livekit/client-sdk-js.git
|
||||||
- Description: JavaScript/TypeScript client SDK for LiveKit
|
- Description: JavaScript/TypeScript client SDK for LiveKit
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/livekit-client@2.18.1`
|
- Folder: `licenses/livekit-client@2.18.8`
|
||||||
- Source package dir: `packages/call/node_modules/livekit-client`
|
- Source package dir: `packages/call/node_modules/livekit-client`
|
||||||
|
|
||||||
## lucide-react@1.8.0
|
## lucide-react@1.14.0
|
||||||
|
|
||||||
- License: ISC
|
- License: ISC
|
||||||
- Homepage: https://lucide.dev
|
- Homepage: https://lucide.dev
|
||||||
- Repository: https://github.com/lucide-icons/lucide.git
|
- Repository: https://github.com/lucide-icons/lucide.git
|
||||||
- Description: A Lucide icon library package for React applications.
|
- Description: A Lucide icon library package for React applications.
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/lucide-react@1.8.0`
|
- Folder: `licenses/lucide-react@1.14.0`
|
||||||
- Source package dir: `apps/tauri/node_modules/lucide-react`
|
- Source package dir: `apps/tauri/node_modules/lucide-react`
|
||||||
|
|
||||||
## prettier@3.8.2
|
## prettier@3.8.3
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://prettier.io
|
- Homepage: https://prettier.io
|
||||||
- Repository: prettier/prettier
|
- Repository: prettier/prettier
|
||||||
- Description: Prettier is an opinionated code formatter
|
- Description: Prettier is an opinionated code formatter
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/prettier@3.8.2`
|
- Folder: `licenses/prettier@3.8.3`
|
||||||
- Source package dir: `node_modules/prettier`
|
- Source package dir: `node_modules/prettier`
|
||||||
|
|
||||||
## qrcode@1.5.4
|
## qrcode@1.5.4
|
||||||
|
|
@ -391,23 +391,23 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/recharts@3.8.1`
|
- Folder: `licenses/recharts@3.8.1`
|
||||||
- Source package dir: `packages/call/node_modules/recharts`
|
- Source package dir: `packages/call/node_modules/recharts`
|
||||||
|
|
||||||
## shadcn@4.2.0
|
## shadcn@4.6.0
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Repository: https://github.com/shadcn-ui/ui.git
|
- Repository: https://github.com/shadcn-ui/ui.git
|
||||||
- Description: Add components to your apps.
|
- Description: Add components to your apps.
|
||||||
- Included files: LICENSE.md
|
- Included files: LICENSE.md
|
||||||
- Folder: `licenses/shadcn@4.2.0`
|
- Folder: `licenses/shadcn@4.6.0`
|
||||||
- Source package dir: `apps/web/node_modules/shadcn`
|
- Source package dir: `apps/web/node_modules/shadcn`
|
||||||
|
|
||||||
## sonner@1.7.4
|
## sonner@2.0.7
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://sonner.emilkowal.ski/
|
- Homepage: https://sonner.emilkowal.ski/
|
||||||
- Repository: git+https://github.com/emilkowalski/sonner.git
|
- Repository: git+https://github.com/emilkowalski/sonner.git
|
||||||
- Description: An opinionated toast component for React.
|
- Description: An opinionated toast component for React.
|
||||||
- Included files: LICENSE.md
|
- Included files: LICENSE.md
|
||||||
- Folder: `licenses/sonner@1.7.4`
|
- Folder: `licenses/sonner@2.0.7`
|
||||||
- Source package dir: `apps/web/node_modules/sonner`
|
- Source package dir: `apps/web/node_modules/sonner`
|
||||||
|
|
||||||
## tailwind-merge@3.5.0
|
## tailwind-merge@3.5.0
|
||||||
|
|
@ -430,14 +430,14 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/tailwind-scrollbar-hide@4.0.0`
|
- Folder: `licenses/tailwind-scrollbar-hide@4.0.0`
|
||||||
- Source package dir: `apps/web/node_modules/tailwind-scrollbar-hide`
|
- Source package dir: `apps/web/node_modules/tailwind-scrollbar-hide`
|
||||||
|
|
||||||
## tailwindcss@4.2.2
|
## tailwindcss@4.2.4
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://tailwindcss.com
|
- Homepage: https://tailwindcss.com
|
||||||
- Repository: https://github.com/tailwindlabs/tailwindcss.git
|
- Repository: https://github.com/tailwindlabs/tailwindcss.git
|
||||||
- Description: A utility-first CSS framework for rapidly building custom user interfaces.
|
- Description: A utility-first CSS framework for rapidly building custom user interfaces.
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/tailwindcss@4.2.2`
|
- Folder: `licenses/tailwindcss@4.2.4`
|
||||||
- Source package dir: `apps/web/node_modules/tailwindcss`
|
- Source package dir: `apps/web/node_modules/tailwindcss`
|
||||||
|
|
||||||
## tw-animate-css@1.4.0
|
## tw-animate-css@1.4.0
|
||||||
|
|
@ -450,53 +450,44 @@ Generated from bun.lock and installed packages in workspace node_modules folders
|
||||||
- Folder: `licenses/tw-animate-css@1.4.0`
|
- Folder: `licenses/tw-animate-css@1.4.0`
|
||||||
- Source package dir: `apps/web/node_modules/tw-animate-css`
|
- Source package dir: `apps/web/node_modules/tw-animate-css`
|
||||||
|
|
||||||
## typescript@5.9.3
|
## typescript@6.0.3
|
||||||
|
|
||||||
- License: Apache-2.0
|
- License: Apache-2.0
|
||||||
- Homepage: https://www.typescriptlang.org/
|
- Homepage: https://www.typescriptlang.org/
|
||||||
- Repository: https://github.com/microsoft/TypeScript.git
|
- Repository: https://github.com/microsoft/TypeScript.git
|
||||||
- Description: TypeScript is a language for application scale JavaScript development
|
- Description: TypeScript is a language for application scale JavaScript development
|
||||||
- Included files: LICENSE.txt
|
- Included files: LICENSE.txt
|
||||||
- Folder: `licenses/typescript@5.9.3`
|
- Folder: `licenses/typescript@6.0.3`
|
||||||
- Source package dir: `apps/web/node_modules/typescript`
|
- Source package dir: `apps/web/node_modules/typescript`
|
||||||
|
|
||||||
## typescript-eslint@8.58.1
|
## typescript-eslint@8.59.1
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://typescript-eslint.io/packages/typescript-eslint
|
- Homepage: https://typescript-eslint.io/packages/typescript-eslint
|
||||||
- Repository: https://github.com/typescript-eslint/typescript-eslint.git
|
- Repository: https://github.com/typescript-eslint/typescript-eslint.git
|
||||||
- Description: Tooling which enables you to use TypeScript with ESLint
|
- Description: Tooling which enables you to use TypeScript with ESLint
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/typescript-eslint@8.58.1`
|
- Folder: `licenses/typescript-eslint@8.59.1`
|
||||||
- Source package dir: `apps/web/node_modules/typescript-eslint`
|
- Source package dir: `apps/web/node_modules/typescript-eslint`
|
||||||
|
|
||||||
## vite@7.3.2
|
## vite@8.0.10
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://vite.dev
|
- Homepage: https://vite.dev
|
||||||
- Repository: git+https://github.com/vitejs/vite.git
|
- Repository: git+https://github.com/vitejs/vite.git
|
||||||
- Description: Native-ESM powered web dev build tool
|
- Description: Native-ESM powered web dev build tool
|
||||||
- Included files: LICENSE.md
|
- Included files: LICENSE.md
|
||||||
- Folder: `licenses/vite@7.3.2`
|
- Folder: `licenses/vite@8.0.10`
|
||||||
- Source package dir: `apps/web/node_modules/vite`
|
- Source package dir: `apps/web/node_modules/vite`
|
||||||
|
|
||||||
## vite-tsconfig-paths@6.1.1
|
## zod@4.4.1
|
||||||
|
|
||||||
- License: MIT
|
|
||||||
- Repository: aleclarson/vite-tsconfig-paths
|
|
||||||
- Description: Vite resolver for TypeScript compilerOptions.paths
|
|
||||||
- Included files: LICENSE
|
|
||||||
- Folder: `licenses/vite-tsconfig-paths@6.1.1`
|
|
||||||
- Source package dir: `apps/web/node_modules/vite-tsconfig-paths`
|
|
||||||
|
|
||||||
## zod@4.3.6
|
|
||||||
|
|
||||||
- License: MIT
|
- License: MIT
|
||||||
- Homepage: https://zod.dev
|
- Homepage: https://zod.dev
|
||||||
- Repository: git+https://github.com/colinhacks/zod.git
|
- Repository: git+https://github.com/colinhacks/zod.git
|
||||||
- Description: TypeScript-first schema declaration and validation library with static type inference
|
- Description: TypeScript-first schema declaration and validation library with static type inference
|
||||||
- Included files: LICENSE
|
- Included files: LICENSE
|
||||||
- Folder: `licenses/zod@4.3.6`
|
- Folder: `licenses/zod@4.4.1`
|
||||||
- Source package dir: `apps/web/node_modules/zod`
|
- Source package dir: `apps/web/node_modules/zod`
|
||||||
|
|
||||||
## zustand@5.0.12
|
## zustand@5.0.12
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"specVersion": "1.5",
|
"specVersion": "1.5",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2026-05-01T13:23:05.336Z",
|
"timestamp": "2026-05-01T17:38:04.763Z",
|
||||||
"tools": [
|
"tools": [
|
||||||
{
|
{
|
||||||
"vendor": "OpenAI",
|
"vendor": "OpenAI",
|
||||||
|
|
@ -111,10 +111,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40codemirror/view@6.41.0",
|
"bomRef": "pkg:npm/%40codemirror/view@6.41.1",
|
||||||
"name": "@codemirror/view",
|
"name": "@codemirror/view",
|
||||||
"version": "6.41.0",
|
"version": "6.41.1",
|
||||||
"purl": "pkg:npm/%40codemirror/view@6.41.0",
|
"purl": "pkg:npm/%40codemirror/view@6.41.1",
|
||||||
"description": "DOM view component for the CodeMirror code editor",
|
"description": "DOM view component for the CodeMirror code editor",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -126,13 +126,13 @@
|
||||||
"externalReferences": [
|
"externalReferences": [
|
||||||
{
|
{
|
||||||
"type": "vcs",
|
"type": "vcs",
|
||||||
"url": "git+https://github.com/codemirror/view.git"
|
"url": "git+https://code.haverbeke.berlin/codemirror/view.git"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@codemirror_view@6.41.0"
|
"value": "licenses/@codemirror_view@6.41.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -242,10 +242,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40noble/curves@2.0.1",
|
"bomRef": "pkg:npm/%40noble/curves@2.2.0",
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "2.0.1",
|
"version": "2.2.0",
|
||||||
"purl": "pkg:npm/%40noble/curves@2.0.1",
|
"purl": "pkg:npm/%40noble/curves@2.2.0",
|
||||||
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
"description": "Audited & minimal JS implementation of elliptic curve cryptography",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -267,7 +267,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@noble_curves@2.0.1"
|
"value": "licenses/@noble_curves@2.2.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -277,10 +277,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tailwindcss/vite@4.2.2",
|
"bomRef": "pkg:npm/%40tailwindcss/vite@4.2.4",
|
||||||
"name": "@tailwindcss/vite",
|
"name": "@tailwindcss/vite",
|
||||||
"version": "4.2.2",
|
"version": "4.2.4",
|
||||||
"purl": "pkg:npm/%40tailwindcss/vite@4.2.2",
|
"purl": "pkg:npm/%40tailwindcss/vite@4.2.4",
|
||||||
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -302,7 +302,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tailwindcss_vite@4.2.2"
|
"value": "licenses/@tailwindcss_vite@4.2.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -312,10 +312,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tanstack/react-query@5.99.0",
|
"bomRef": "pkg:npm/%40tanstack/react-query@5.100.7",
|
||||||
"name": "@tanstack/react-query",
|
"name": "@tanstack/react-query",
|
||||||
"version": "5.99.0",
|
"version": "5.100.7",
|
||||||
"purl": "pkg:npm/%40tanstack/react-query@5.99.0",
|
"purl": "pkg:npm/%40tanstack/react-query@5.100.7",
|
||||||
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
"description": "Hooks for managing, caching and syncing asynchronous and remote data in React",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tanstack_react-query@5.99.0"
|
"value": "licenses/@tanstack_react-query@5.100.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -347,10 +347,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tanstack/react-router@1.168.18",
|
"bomRef": "pkg:npm/%40tanstack/react-router@1.169.1",
|
||||||
"name": "@tanstack/react-router",
|
"name": "@tanstack/react-router",
|
||||||
"version": "1.168.18",
|
"version": "1.169.1",
|
||||||
"purl": "pkg:npm/%40tanstack/react-router@1.168.18",
|
"purl": "pkg:npm/%40tanstack/react-router@1.169.1",
|
||||||
"description": "Modern and scalable routing for React applications",
|
"description": "Modern and scalable routing for React applications",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -372,7 +372,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tanstack_react-router@1.168.18"
|
"value": "licenses/@tanstack_react-router@1.169.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -382,10 +382,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tanstack/react-virtual@3.13.23",
|
"bomRef": "pkg:npm/%40tanstack/react-virtual@3.13.24",
|
||||||
"name": "@tanstack/react-virtual",
|
"name": "@tanstack/react-virtual",
|
||||||
"version": "3.13.23",
|
"version": "3.13.24",
|
||||||
"purl": "pkg:npm/%40tanstack/react-virtual@3.13.23",
|
"purl": "pkg:npm/%40tanstack/react-virtual@3.13.24",
|
||||||
"description": "Headless UI for virtualizing scrollable elements in React",
|
"description": "Headless UI for virtualizing scrollable elements in React",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -407,7 +407,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tanstack_react-virtual@3.13.23"
|
"value": "licenses/@tanstack_react-virtual@3.13.24"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -417,10 +417,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tauri-apps/api@2.10.1",
|
"bomRef": "pkg:npm/%40tauri-apps/api@2.11.0",
|
||||||
"name": "@tauri-apps/api",
|
"name": "@tauri-apps/api",
|
||||||
"version": "2.10.1",
|
"version": "2.11.0",
|
||||||
"purl": "pkg:npm/%40tauri-apps/api@2.10.1",
|
"purl": "pkg:npm/%40tauri-apps/api@2.11.0",
|
||||||
"description": "Tauri API definitions",
|
"description": "Tauri API definitions",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -442,7 +442,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tauri-apps_api@2.10.1"
|
"value": "licenses/@tauri-apps_api@2.11.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -452,10 +452,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tauri-apps/cli@2.10.1",
|
"bomRef": "pkg:npm/%40tauri-apps/cli@2.11.0",
|
||||||
"name": "@tauri-apps/cli",
|
"name": "@tauri-apps/cli",
|
||||||
"version": "2.10.1",
|
"version": "2.11.0",
|
||||||
"purl": "pkg:npm/%40tauri-apps/cli@2.10.1",
|
"purl": "pkg:npm/%40tauri-apps/cli@2.11.0",
|
||||||
"description": "Command line interface for building Tauri apps",
|
"description": "Command line interface for building Tauri apps",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -477,7 +477,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tauri-apps_cli@2.10.1"
|
"value": "licenses/@tauri-apps_cli@2.11.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -580,15 +580,15 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.13",
|
"bomRef": "pkg:npm/%40tensamin/ttp-core@0.0.15",
|
||||||
"name": "@tensamin/ttp-core",
|
"name": "@tensamin/ttp-core",
|
||||||
"version": "0.0.13",
|
"version": "0.0.15",
|
||||||
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.13",
|
"purl": "pkg:npm/%40tensamin/ttp-core@0.0.15",
|
||||||
"externalReferences": [],
|
"externalReferences": [],
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@tensamin_ttp-core@0.0.13"
|
"value": "licenses/@tensamin_ttp-core@0.0.15"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -756,10 +756,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40typescript-eslint/parser@8.58.1",
|
"bomRef": "pkg:npm/%40typescript-eslint/parser@8.59.1",
|
||||||
"name": "@typescript-eslint/parser",
|
"name": "@typescript-eslint/parser",
|
||||||
"version": "8.58.1",
|
"version": "8.59.1",
|
||||||
"purl": "pkg:npm/%40typescript-eslint/parser@8.58.1",
|
"purl": "pkg:npm/%40typescript-eslint/parser@8.59.1",
|
||||||
"description": "An ESLint custom parser which leverages TypeScript ESTree",
|
"description": "An ESLint custom parser which leverages TypeScript ESTree",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -781,7 +781,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@typescript-eslint_parser@8.58.1"
|
"value": "licenses/@typescript-eslint_parser@8.59.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -791,10 +791,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/%40vitejs/plugin-react@5.2.0",
|
"bomRef": "pkg:npm/%40vitejs/plugin-react@6.0.1",
|
||||||
"name": "@vitejs/plugin-react",
|
"name": "@vitejs/plugin-react",
|
||||||
"version": "5.2.0",
|
"version": "6.0.1",
|
||||||
"purl": "pkg:npm/%40vitejs/plugin-react@5.2.0",
|
"purl": "pkg:npm/%40vitejs/plugin-react@6.0.1",
|
||||||
"description": "The default Vite plugin for React projects",
|
"description": "The default Vite plugin for React projects",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -816,7 +816,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/@vitejs_plugin-react@5.2.0"
|
"value": "licenses/@vitejs_plugin-react@6.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -958,10 +958,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/eslint@10.2.0",
|
"bomRef": "pkg:npm/eslint@10.2.1",
|
||||||
"name": "eslint",
|
"name": "eslint",
|
||||||
"version": "10.2.0",
|
"version": "10.2.1",
|
||||||
"purl": "pkg:npm/eslint@10.2.0",
|
"purl": "pkg:npm/eslint@10.2.1",
|
||||||
"description": "An AST-based pattern checker for JavaScript.",
|
"description": "An AST-based pattern checker for JavaScript.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -983,7 +983,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/eslint@10.2.0"
|
"value": "licenses/eslint@10.2.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -993,10 +993,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/eslint-plugin-react-hooks@7.0.1",
|
"bomRef": "pkg:npm/eslint-plugin-react-hooks@7.1.1",
|
||||||
"name": "eslint-plugin-react-hooks",
|
"name": "eslint-plugin-react-hooks",
|
||||||
"version": "7.0.1",
|
"version": "7.1.1",
|
||||||
"purl": "pkg:npm/eslint-plugin-react-hooks@7.0.1",
|
"purl": "pkg:npm/eslint-plugin-react-hooks@7.1.1",
|
||||||
"description": "ESLint rules for React Hooks",
|
"description": "ESLint rules for React Hooks",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1018,7 +1018,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/eslint-plugin-react-hooks@7.0.1"
|
"value": "licenses/eslint-plugin-react-hooks@7.1.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1121,10 +1121,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/livekit-client@2.18.1",
|
"bomRef": "pkg:npm/livekit-client@2.18.8",
|
||||||
"name": "livekit-client",
|
"name": "livekit-client",
|
||||||
"version": "2.18.1",
|
"version": "2.18.8",
|
||||||
"purl": "pkg:npm/livekit-client@2.18.1",
|
"purl": "pkg:npm/livekit-client@2.18.8",
|
||||||
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1142,7 +1142,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/livekit-client@2.18.1"
|
"value": "licenses/livekit-client@2.18.8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1152,10 +1152,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/lucide-react@1.8.0",
|
"bomRef": "pkg:npm/lucide-react@1.14.0",
|
||||||
"name": "lucide-react",
|
"name": "lucide-react",
|
||||||
"version": "1.8.0",
|
"version": "1.14.0",
|
||||||
"purl": "pkg:npm/lucide-react@1.8.0",
|
"purl": "pkg:npm/lucide-react@1.14.0",
|
||||||
"description": "A Lucide icon library package for React applications.",
|
"description": "A Lucide icon library package for React applications.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1177,7 +1177,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/lucide-react@1.8.0"
|
"value": "licenses/lucide-react@1.14.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1187,10 +1187,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/prettier@3.8.2",
|
"bomRef": "pkg:npm/prettier@3.8.3",
|
||||||
"name": "prettier",
|
"name": "prettier",
|
||||||
"version": "3.8.2",
|
"version": "3.8.3",
|
||||||
"purl": "pkg:npm/prettier@3.8.2",
|
"purl": "pkg:npm/prettier@3.8.3",
|
||||||
"description": "Prettier is an opinionated code formatter",
|
"description": "Prettier is an opinionated code formatter",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1212,7 +1212,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/prettier@3.8.2"
|
"value": "licenses/prettier@3.8.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1362,10 +1362,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/shadcn@4.2.0",
|
"bomRef": "pkg:npm/shadcn@4.6.0",
|
||||||
"name": "shadcn",
|
"name": "shadcn",
|
||||||
"version": "4.2.0",
|
"version": "4.6.0",
|
||||||
"purl": "pkg:npm/shadcn@4.2.0",
|
"purl": "pkg:npm/shadcn@4.6.0",
|
||||||
"description": "Add components to your apps.",
|
"description": "Add components to your apps.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1383,7 +1383,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/shadcn@4.2.0"
|
"value": "licenses/shadcn@4.6.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1393,10 +1393,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/sonner@1.7.4",
|
"bomRef": "pkg:npm/sonner@2.0.7",
|
||||||
"name": "sonner",
|
"name": "sonner",
|
||||||
"version": "1.7.4",
|
"version": "2.0.7",
|
||||||
"purl": "pkg:npm/sonner@1.7.4",
|
"purl": "pkg:npm/sonner@2.0.7",
|
||||||
"description": "An opinionated toast component for React.",
|
"description": "An opinionated toast component for React.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1418,7 +1418,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/sonner@1.7.4"
|
"value": "licenses/sonner@2.0.7"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1498,10 +1498,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/tailwindcss@4.2.2",
|
"bomRef": "pkg:npm/tailwindcss@4.2.4",
|
||||||
"name": "tailwindcss",
|
"name": "tailwindcss",
|
||||||
"version": "4.2.2",
|
"version": "4.2.4",
|
||||||
"purl": "pkg:npm/tailwindcss@4.2.2",
|
"purl": "pkg:npm/tailwindcss@4.2.4",
|
||||||
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
"description": "A utility-first CSS framework for rapidly building custom user interfaces.",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1523,7 +1523,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/tailwindcss@4.2.2"
|
"value": "licenses/tailwindcss@4.2.4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1568,10 +1568,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/typescript@5.9.3",
|
"bomRef": "pkg:npm/typescript@6.0.3",
|
||||||
"name": "typescript",
|
"name": "typescript",
|
||||||
"version": "5.9.3",
|
"version": "6.0.3",
|
||||||
"purl": "pkg:npm/typescript@5.9.3",
|
"purl": "pkg:npm/typescript@6.0.3",
|
||||||
"description": "TypeScript is a language for application scale JavaScript development",
|
"description": "TypeScript is a language for application scale JavaScript development",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1593,7 +1593,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/typescript@5.9.3"
|
"value": "licenses/typescript@6.0.3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1603,10 +1603,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/typescript-eslint@8.58.1",
|
"bomRef": "pkg:npm/typescript-eslint@8.59.1",
|
||||||
"name": "typescript-eslint",
|
"name": "typescript-eslint",
|
||||||
"version": "8.58.1",
|
"version": "8.59.1",
|
||||||
"purl": "pkg:npm/typescript-eslint@8.58.1",
|
"purl": "pkg:npm/typescript-eslint@8.59.1",
|
||||||
"description": "Tooling which enables you to use TypeScript with ESLint",
|
"description": "Tooling which enables you to use TypeScript with ESLint",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1628,7 +1628,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/typescript-eslint@8.58.1"
|
"value": "licenses/typescript-eslint@8.59.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1638,10 +1638,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/vite@7.3.2",
|
"bomRef": "pkg:npm/vite@8.0.10",
|
||||||
"name": "vite",
|
"name": "vite",
|
||||||
"version": "7.3.2",
|
"version": "8.0.10",
|
||||||
"purl": "pkg:npm/vite@7.3.2",
|
"purl": "pkg:npm/vite@8.0.10",
|
||||||
"description": "Native-ESM powered web dev build tool",
|
"description": "Native-ESM powered web dev build tool",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1663,7 +1663,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/vite@7.3.2"
|
"value": "licenses/vite@8.0.10"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
@ -1673,41 +1673,10 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"bomRef": "pkg:npm/vite-tsconfig-paths@6.1.1",
|
"bomRef": "pkg:npm/zod@4.4.1",
|
||||||
"name": "vite-tsconfig-paths",
|
|
||||||
"version": "6.1.1",
|
|
||||||
"purl": "pkg:npm/vite-tsconfig-paths@6.1.1",
|
|
||||||
"description": "Vite resolver for TypeScript compilerOptions.paths",
|
|
||||||
"licenses": [
|
|
||||||
{
|
|
||||||
"license": {
|
|
||||||
"id": "MIT"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"externalReferences": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "aleclarson/vite-tsconfig-paths"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"properties": [
|
|
||||||
{
|
|
||||||
"name": "local:licenseFolder",
|
|
||||||
"value": "licenses/vite-tsconfig-paths@6.1.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "local:sourcePackageDir",
|
|
||||||
"value": "apps/web/node_modules/vite-tsconfig-paths"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "library",
|
|
||||||
"bomRef": "pkg:npm/zod@4.3.6",
|
|
||||||
"name": "zod",
|
"name": "zod",
|
||||||
"version": "4.3.6",
|
"version": "4.4.1",
|
||||||
"purl": "pkg:npm/zod@4.3.6",
|
"purl": "pkg:npm/zod@4.4.1",
|
||||||
"description": "TypeScript-first schema declaration and validation library with static type inference",
|
"description": "TypeScript-first schema declaration and validation library with static type inference",
|
||||||
"licenses": [
|
"licenses": [
|
||||||
{
|
{
|
||||||
|
|
@ -1729,7 +1698,7 @@
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"name": "local:licenseFolder",
|
"name": "local:licenseFolder",
|
||||||
"value": "licenses/zod@4.3.6"
|
"value": "licenses/zod@4.4.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "local:sourcePackageDir",
|
"name": "local:sourcePackageDir",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"generatedAt": "2026-05-01T13:23:05.335Z",
|
"generatedAt": "2026-05-01T17:38:04.762Z",
|
||||||
"packageCount": 53,
|
"packageCount": 52,
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "@codemirror/commands",
|
"name": "@codemirror/commands",
|
||||||
|
|
@ -43,15 +43,15 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@codemirror/view",
|
"name": "@codemirror/view",
|
||||||
"version": "6.41.0",
|
"version": "6.41.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"repository": "git+https://github.com/codemirror/view.git",
|
"repository": "git+https://code.haverbeke.berlin/codemirror/view.git",
|
||||||
"description": "DOM view component for the CodeMirror code editor",
|
"description": "DOM view component for the CodeMirror code editor",
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@codemirror_view@6.41.0",
|
"licenseFolder": "licenses/@codemirror_view@6.41.1",
|
||||||
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/view"
|
"sourcePackageDir": "packages/markdown/node_modules/@codemirror/view"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@noble/curves",
|
"name": "@noble/curves",
|
||||||
"version": "2.0.1",
|
"version": "2.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://paulmillr.com/noble/",
|
"homepage": "https://paulmillr.com/noble/",
|
||||||
"repository": "git+https://github.com/paulmillr/noble-curves.git",
|
"repository": "git+https://github.com/paulmillr/noble-curves.git",
|
||||||
|
|
@ -103,12 +103,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@noble_curves@2.0.1",
|
"licenseFolder": "licenses/@noble_curves@2.2.0",
|
||||||
"sourcePackageDir": "apps/web/node_modules/@noble/curves"
|
"sourcePackageDir": "apps/web/node_modules/@noble/curves"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tailwindcss/vite",
|
"name": "@tailwindcss/vite",
|
||||||
"version": "4.2.2",
|
"version": "4.2.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://tailwindcss.com",
|
"homepage": "https://tailwindcss.com",
|
||||||
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
|
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
|
||||||
|
|
@ -116,12 +116,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tailwindcss_vite@4.2.2",
|
"licenseFolder": "licenses/@tailwindcss_vite@4.2.4",
|
||||||
"sourcePackageDir": "apps/web/node_modules/@tailwindcss/vite"
|
"sourcePackageDir": "apps/web/node_modules/@tailwindcss/vite"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tanstack/react-query",
|
"name": "@tanstack/react-query",
|
||||||
"version": "5.99.0",
|
"version": "5.100.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://tanstack.com/query",
|
"homepage": "https://tanstack.com/query",
|
||||||
"repository": "git+https://github.com/TanStack/query.git",
|
"repository": "git+https://github.com/TanStack/query.git",
|
||||||
|
|
@ -129,12 +129,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tanstack_react-query@5.99.0",
|
"licenseFolder": "licenses/@tanstack_react-query@5.100.7",
|
||||||
"sourcePackageDir": "packages/call/node_modules/@tanstack/react-query"
|
"sourcePackageDir": "packages/call/node_modules/@tanstack/react-query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tanstack/react-router",
|
"name": "@tanstack/react-router",
|
||||||
"version": "1.168.18",
|
"version": "1.169.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://tanstack.com/router",
|
"homepage": "https://tanstack.com/router",
|
||||||
"repository": "git+https://github.com/TanStack/router.git",
|
"repository": "git+https://github.com/TanStack/router.git",
|
||||||
|
|
@ -142,12 +142,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tanstack_react-router@1.168.18",
|
"licenseFolder": "licenses/@tanstack_react-router@1.169.1",
|
||||||
"sourcePackageDir": "apps/web/node_modules/@tanstack/react-router"
|
"sourcePackageDir": "apps/web/node_modules/@tanstack/react-router"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tanstack/react-virtual",
|
"name": "@tanstack/react-virtual",
|
||||||
"version": "3.13.23",
|
"version": "3.13.24",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://tanstack.com/virtual",
|
"homepage": "https://tanstack.com/virtual",
|
||||||
"repository": "git+https://github.com/TanStack/virtual.git",
|
"repository": "git+https://github.com/TanStack/virtual.git",
|
||||||
|
|
@ -155,12 +155,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tanstack_react-virtual@3.13.23",
|
"licenseFolder": "licenses/@tanstack_react-virtual@3.13.24",
|
||||||
"sourcePackageDir": "apps/web/node_modules/@tanstack/react-virtual"
|
"sourcePackageDir": "apps/web/node_modules/@tanstack/react-virtual"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tauri-apps/api",
|
"name": "@tauri-apps/api",
|
||||||
"version": "2.10.1",
|
"version": "2.11.0",
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"homepage": "https://github.com/tauri-apps/tauri#readme",
|
"homepage": "https://github.com/tauri-apps/tauri#readme",
|
||||||
"repository": "git+https://github.com/tauri-apps/tauri.git",
|
"repository": "git+https://github.com/tauri-apps/tauri.git",
|
||||||
|
|
@ -169,12 +169,12 @@
|
||||||
"LICENSE_MIT",
|
"LICENSE_MIT",
|
||||||
"LICENSE_APACHE-2.0"
|
"LICENSE_APACHE-2.0"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tauri-apps_api@2.10.1",
|
"licenseFolder": "licenses/@tauri-apps_api@2.11.0",
|
||||||
"sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/api"
|
"sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tauri-apps/cli",
|
"name": "@tauri-apps/cli",
|
||||||
"version": "2.10.1",
|
"version": "2.11.0",
|
||||||
"license": "Apache-2.0 OR MIT",
|
"license": "Apache-2.0 OR MIT",
|
||||||
"homepage": "https://github.com/tauri-apps/tauri#readme",
|
"homepage": "https://github.com/tauri-apps/tauri#readme",
|
||||||
"repository": "git+https://github.com/tauri-apps/tauri.git",
|
"repository": "git+https://github.com/tauri-apps/tauri.git",
|
||||||
|
|
@ -183,7 +183,7 @@
|
||||||
"LICENSE_MIT",
|
"LICENSE_MIT",
|
||||||
"LICENSE_APACHE-2.0"
|
"LICENSE_APACHE-2.0"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tauri-apps_cli@2.10.1",
|
"licenseFolder": "licenses/@tauri-apps_cli@2.11.0",
|
||||||
"sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/cli"
|
"sourcePackageDir": "apps/tauri/node_modules/@tauri-apps/cli"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -227,7 +227,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@tensamin/ttp-core",
|
"name": "@tensamin/ttp-core",
|
||||||
"version": "0.0.13",
|
"version": "0.0.15",
|
||||||
"license": "UNKNOWN",
|
"license": "UNKNOWN",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"repository": null,
|
"repository": null,
|
||||||
|
|
@ -235,7 +235,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.13",
|
"licenseFolder": "licenses/@tensamin_ttp-core@0.0.15",
|
||||||
"sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core"
|
"sourcePackageDir": "packages/ttp/node_modules/@tensamin/ttp-core"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -303,7 +303,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@typescript-eslint/parser",
|
"name": "@typescript-eslint/parser",
|
||||||
"version": "8.58.1",
|
"version": "8.59.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://typescript-eslint.io/packages/parser",
|
"homepage": "https://typescript-eslint.io/packages/parser",
|
||||||
"repository": "https://github.com/typescript-eslint/typescript-eslint.git",
|
"repository": "https://github.com/typescript-eslint/typescript-eslint.git",
|
||||||
|
|
@ -311,12 +311,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@typescript-eslint_parser@8.58.1",
|
"licenseFolder": "licenses/@typescript-eslint_parser@8.59.1",
|
||||||
"sourcePackageDir": "node_modules/@typescript-eslint/parser"
|
"sourcePackageDir": "node_modules/@typescript-eslint/parser"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "@vitejs/plugin-react",
|
"name": "@vitejs/plugin-react",
|
||||||
"version": "5.2.0",
|
"version": "6.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme",
|
"homepage": "https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#readme",
|
||||||
"repository": "git+https://github.com/vitejs/vite-plugin-react.git",
|
"repository": "git+https://github.com/vitejs/vite-plugin-react.git",
|
||||||
|
|
@ -324,7 +324,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/@vitejs_plugin-react@5.2.0",
|
"licenseFolder": "licenses/@vitejs_plugin-react@6.0.1",
|
||||||
"sourcePackageDir": "apps/web/node_modules/@vitejs/plugin-react"
|
"sourcePackageDir": "apps/web/node_modules/@vitejs/plugin-react"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -382,7 +382,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "eslint",
|
"name": "eslint",
|
||||||
"version": "10.2.0",
|
"version": "10.2.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://eslint.org",
|
"homepage": "https://eslint.org",
|
||||||
"repository": "eslint/eslint",
|
"repository": "eslint/eslint",
|
||||||
|
|
@ -390,12 +390,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/eslint@10.2.0",
|
"licenseFolder": "licenses/eslint@10.2.1",
|
||||||
"sourcePackageDir": "apps/web/node_modules/eslint"
|
"sourcePackageDir": "apps/web/node_modules/eslint"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "eslint-plugin-react-hooks",
|
"name": "eslint-plugin-react-hooks",
|
||||||
"version": "7.0.1",
|
"version": "7.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://react.dev/",
|
"homepage": "https://react.dev/",
|
||||||
"repository": "https://github.com/facebook/react.git",
|
"repository": "https://github.com/facebook/react.git",
|
||||||
|
|
@ -403,7 +403,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/eslint-plugin-react-hooks@7.0.1",
|
"licenseFolder": "licenses/eslint-plugin-react-hooks@7.1.1",
|
||||||
"sourcePackageDir": "node_modules/eslint-plugin-react-hooks"
|
"sourcePackageDir": "node_modules/eslint-plugin-react-hooks"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -447,7 +447,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "livekit-client",
|
"name": "livekit-client",
|
||||||
"version": "2.18.1",
|
"version": "2.18.8",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"repository": "git@github.com:livekit/client-sdk-js.git",
|
"repository": "git@github.com:livekit/client-sdk-js.git",
|
||||||
|
|
@ -455,12 +455,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/livekit-client@2.18.1",
|
"licenseFolder": "licenses/livekit-client@2.18.8",
|
||||||
"sourcePackageDir": "packages/call/node_modules/livekit-client"
|
"sourcePackageDir": "packages/call/node_modules/livekit-client"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lucide-react",
|
"name": "lucide-react",
|
||||||
"version": "1.8.0",
|
"version": "1.14.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"homepage": "https://lucide.dev",
|
"homepage": "https://lucide.dev",
|
||||||
"repository": "https://github.com/lucide-icons/lucide.git",
|
"repository": "https://github.com/lucide-icons/lucide.git",
|
||||||
|
|
@ -468,12 +468,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/lucide-react@1.8.0",
|
"licenseFolder": "licenses/lucide-react@1.14.0",
|
||||||
"sourcePackageDir": "apps/tauri/node_modules/lucide-react"
|
"sourcePackageDir": "apps/tauri/node_modules/lucide-react"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "prettier",
|
"name": "prettier",
|
||||||
"version": "3.8.2",
|
"version": "3.8.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://prettier.io",
|
"homepage": "https://prettier.io",
|
||||||
"repository": "prettier/prettier",
|
"repository": "prettier/prettier",
|
||||||
|
|
@ -481,7 +481,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/prettier@3.8.2",
|
"licenseFolder": "licenses/prettier@3.8.3",
|
||||||
"sourcePackageDir": "node_modules/prettier"
|
"sourcePackageDir": "node_modules/prettier"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -538,7 +538,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "shadcn",
|
"name": "shadcn",
|
||||||
"version": "4.2.0",
|
"version": "4.6.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": null,
|
"homepage": null,
|
||||||
"repository": "https://github.com/shadcn-ui/ui.git",
|
"repository": "https://github.com/shadcn-ui/ui.git",
|
||||||
|
|
@ -546,12 +546,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE.md"
|
"LICENSE.md"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/shadcn@4.2.0",
|
"licenseFolder": "licenses/shadcn@4.6.0",
|
||||||
"sourcePackageDir": "apps/web/node_modules/shadcn"
|
"sourcePackageDir": "apps/web/node_modules/shadcn"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sonner",
|
"name": "sonner",
|
||||||
"version": "1.7.4",
|
"version": "2.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://sonner.emilkowal.ski/",
|
"homepage": "https://sonner.emilkowal.ski/",
|
||||||
"repository": "git+https://github.com/emilkowalski/sonner.git",
|
"repository": "git+https://github.com/emilkowalski/sonner.git",
|
||||||
|
|
@ -559,7 +559,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE.md"
|
"LICENSE.md"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/sonner@1.7.4",
|
"licenseFolder": "licenses/sonner@2.0.7",
|
||||||
"sourcePackageDir": "apps/web/node_modules/sonner"
|
"sourcePackageDir": "apps/web/node_modules/sonner"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -590,7 +590,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tailwindcss",
|
"name": "tailwindcss",
|
||||||
"version": "4.2.2",
|
"version": "4.2.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://tailwindcss.com",
|
"homepage": "https://tailwindcss.com",
|
||||||
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
|
"repository": "https://github.com/tailwindlabs/tailwindcss.git",
|
||||||
|
|
@ -598,7 +598,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/tailwindcss@4.2.2",
|
"licenseFolder": "licenses/tailwindcss@4.2.4",
|
||||||
"sourcePackageDir": "apps/web/node_modules/tailwindcss"
|
"sourcePackageDir": "apps/web/node_modules/tailwindcss"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -616,7 +616,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "typescript",
|
"name": "typescript",
|
||||||
"version": "5.9.3",
|
"version": "6.0.3",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"homepage": "https://www.typescriptlang.org/",
|
"homepage": "https://www.typescriptlang.org/",
|
||||||
"repository": "https://github.com/microsoft/TypeScript.git",
|
"repository": "https://github.com/microsoft/TypeScript.git",
|
||||||
|
|
@ -624,12 +624,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE.txt"
|
"LICENSE.txt"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/typescript@5.9.3",
|
"licenseFolder": "licenses/typescript@6.0.3",
|
||||||
"sourcePackageDir": "apps/web/node_modules/typescript"
|
"sourcePackageDir": "apps/web/node_modules/typescript"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "typescript-eslint",
|
"name": "typescript-eslint",
|
||||||
"version": "8.58.1",
|
"version": "8.59.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://typescript-eslint.io/packages/typescript-eslint",
|
"homepage": "https://typescript-eslint.io/packages/typescript-eslint",
|
||||||
"repository": "https://github.com/typescript-eslint/typescript-eslint.git",
|
"repository": "https://github.com/typescript-eslint/typescript-eslint.git",
|
||||||
|
|
@ -637,12 +637,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/typescript-eslint@8.58.1",
|
"licenseFolder": "licenses/typescript-eslint@8.59.1",
|
||||||
"sourcePackageDir": "apps/web/node_modules/typescript-eslint"
|
"sourcePackageDir": "apps/web/node_modules/typescript-eslint"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "vite",
|
"name": "vite",
|
||||||
"version": "7.3.2",
|
"version": "8.0.10",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://vite.dev",
|
"homepage": "https://vite.dev",
|
||||||
"repository": "git+https://github.com/vitejs/vite.git",
|
"repository": "git+https://github.com/vitejs/vite.git",
|
||||||
|
|
@ -650,25 +650,12 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE.md"
|
"LICENSE.md"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/vite@7.3.2",
|
"licenseFolder": "licenses/vite@8.0.10",
|
||||||
"sourcePackageDir": "apps/web/node_modules/vite"
|
"sourcePackageDir": "apps/web/node_modules/vite"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "vite-tsconfig-paths",
|
|
||||||
"version": "6.1.1",
|
|
||||||
"license": "MIT",
|
|
||||||
"homepage": null,
|
|
||||||
"repository": "aleclarson/vite-tsconfig-paths",
|
|
||||||
"description": "Vite resolver for TypeScript compilerOptions.paths",
|
|
||||||
"files": [
|
|
||||||
"LICENSE"
|
|
||||||
],
|
|
||||||
"licenseFolder": "licenses/vite-tsconfig-paths@6.1.1",
|
|
||||||
"sourcePackageDir": "apps/web/node_modules/vite-tsconfig-paths"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "zod",
|
"name": "zod",
|
||||||
"version": "4.3.6",
|
"version": "4.4.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://zod.dev",
|
"homepage": "https://zod.dev",
|
||||||
"repository": "git+https://github.com/colinhacks/zod.git",
|
"repository": "git+https://github.com/colinhacks/zod.git",
|
||||||
|
|
@ -676,7 +663,7 @@
|
||||||
"files": [
|
"files": [
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"licenseFolder": "licenses/zod@4.3.6",
|
"licenseFolder": "licenses/zod@4.4.1",
|
||||||
"sourcePackageDir": "apps/web/node_modules/zod"
|
"sourcePackageDir": "apps/web/node_modules/zod"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ SOFTWARE.
|
||||||
|
|
||||||
# Licenses of bundled dependencies
|
# Licenses of bundled dependencies
|
||||||
The published Vite artifact additionally contains code with the following licenses:
|
The published Vite artifact additionally contains code with the following licenses:
|
||||||
BSD-2-Clause, CC0-1.0, ISC, MIT
|
Apache-2.0, BSD-2-Clause, CC0-1.0, ISC, MIT
|
||||||
|
|
||||||
# Bundled dependencies:
|
# Bundled dependencies:
|
||||||
## @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell/sourcemap-codec, @jridgewell/trace-mapping
|
## @jridgewell/gen-mapping, @jridgewell/remapping, @jridgewell/sourcemap-codec, @jridgewell/trace-mapping
|
||||||
|
|
@ -95,47 +95,11 @@ Repository: https://github.com/lukeed/polka
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## @rolldown/pluginutils
|
## @rollup/plugin-alias, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils
|
||||||
License: MIT
|
|
||||||
Repository: https://github.com/rolldown/rolldown
|
|
||||||
|
|
||||||
> MIT License
|
|
||||||
>
|
|
||||||
> Copyright (c) 2024-present VoidZero Inc. & Contributors
|
|
||||||
>
|
|
||||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
> of this software and associated documentation files (the "Software"), to deal
|
|
||||||
> in the Software without restriction, including without limitation the rights
|
|
||||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
> copies of the Software, and to permit persons to whom the Software is
|
|
||||||
> furnished to do so, subject to the following conditions:
|
|
||||||
>
|
|
||||||
> The above copyright notice and this permission notice shall be included in all
|
|
||||||
> copies or substantial portions of the Software.
|
|
||||||
>
|
|
||||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
> SOFTWARE.
|
|
||||||
>
|
|
||||||
> end of terms and conditions
|
|
||||||
>
|
|
||||||
> The licenses of externally maintained libraries from which parts of the Software is derived are listed [here](https://github.com/rolldown/rolldown/blob/main/THIRD-PARTY-LICENSE).
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
## @rollup/plugin-alias, @rollup/plugin-commonjs, @rollup/plugin-dynamic-import-vars, @rollup/pluginutils
|
|
||||||
License: MIT
|
License: MIT
|
||||||
By: Johannes Stein
|
By: Johannes Stein
|
||||||
Repository: https://github.com/rollup/plugins
|
Repository: https://github.com/rollup/plugins
|
||||||
|
|
||||||
License: MIT
|
|
||||||
By: Rich Harris
|
|
||||||
Repository: https://github.com/rollup/plugins
|
|
||||||
|
|
||||||
License: MIT
|
License: MIT
|
||||||
By: LarsDenBakker
|
By: LarsDenBakker
|
||||||
Repository: https://github.com/rollup/plugins
|
Repository: https://github.com/rollup/plugins
|
||||||
|
|
@ -168,6 +132,243 @@ Repository: https://github.com/rollup/plugins
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
## @vercel/detect-agent
|
||||||
|
License: Apache-2.0
|
||||||
|
By: Vercel
|
||||||
|
Repository: https://github.com/vercel/vercel
|
||||||
|
|
||||||
|
> Apache License
|
||||||
|
> Version 2.0, January 2004
|
||||||
|
> http://www.apache.org/licenses/
|
||||||
|
>
|
||||||
|
> TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
>
|
||||||
|
> 1. Definitions.
|
||||||
|
>
|
||||||
|
> "License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
> and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
>
|
||||||
|
> "Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
> the copyright owner that is granting the License.
|
||||||
|
>
|
||||||
|
> "Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
> other entities that control, are controlled by, or are under common
|
||||||
|
> control with that entity. For the purposes of this definition,
|
||||||
|
> "control" means (i) the power, direct or indirect, to cause the
|
||||||
|
> direction or management of such entity, whether by contract or
|
||||||
|
> otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
> outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
>
|
||||||
|
> "You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
> exercising permissions granted by this License.
|
||||||
|
>
|
||||||
|
> "Source" form shall mean the preferred form for making modifications,
|
||||||
|
> including but not limited to software source code, documentation
|
||||||
|
> source, and configuration files.
|
||||||
|
>
|
||||||
|
> "Object" form shall mean any form resulting from mechanical
|
||||||
|
> transformation or translation of a Source form, including but
|
||||||
|
> not limited to compiled object code, generated documentation,
|
||||||
|
> and conversions to other media types.
|
||||||
|
>
|
||||||
|
> "Work" shall mean the work of authorship, whether in Source or
|
||||||
|
> Object form, made available under the License, as indicated by a
|
||||||
|
> copyright notice that is included in or attached to the work
|
||||||
|
> (an example is provided in the Appendix below).
|
||||||
|
>
|
||||||
|
> "Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
> form, that is based on (or derived from) the Work and for which the
|
||||||
|
> editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
> represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
> of this License, Derivative Works shall not include works that remain
|
||||||
|
> separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
> the Work and Derivative Works thereof.
|
||||||
|
>
|
||||||
|
> "Contribution" shall mean any work of authorship, including
|
||||||
|
> the original version of the Work and any modifications or additions
|
||||||
|
> to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
> submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
> or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
> the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
> means any form of electronic, verbal, or written communication sent
|
||||||
|
> to the Licensor or its representatives, including but not limited to
|
||||||
|
> communication on electronic mailing lists, source code control systems,
|
||||||
|
> and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
> Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
> excluding communication that is conspicuously marked or otherwise
|
||||||
|
> designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
>
|
||||||
|
> "Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
> on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
> subsequently incorporated within the Work.
|
||||||
|
>
|
||||||
|
> 2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
> this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
> worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
> copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
> publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
> Work and such Derivative Works in Source or Object form.
|
||||||
|
>
|
||||||
|
> 3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
> this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
> worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
> (except as stated in this section) patent license to make, have made,
|
||||||
|
> use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
> where such license applies only to those patent claims licensable
|
||||||
|
> by such Contributor that are necessarily infringed by their
|
||||||
|
> Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
> with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
> institute patent litigation against any entity (including a
|
||||||
|
> cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
> or a Contribution incorporated within the Work constitutes direct
|
||||||
|
> or contributory patent infringement, then any patent licenses
|
||||||
|
> granted to You under this License for that Work shall terminate
|
||||||
|
> as of the date such litigation is filed.
|
||||||
|
>
|
||||||
|
> 4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
> Work or Derivative Works thereof in any medium, with or without
|
||||||
|
> modifications, and in Source or Object form, provided that You
|
||||||
|
> meet the following conditions:
|
||||||
|
>
|
||||||
|
> (a) You must give any other recipients of the Work or
|
||||||
|
> Derivative Works a copy of this License; and
|
||||||
|
>
|
||||||
|
> (b) You must cause any modified files to carry prominent notices
|
||||||
|
> stating that You changed the files; and
|
||||||
|
>
|
||||||
|
> (c) You must retain, in the Source form of any Derivative Works
|
||||||
|
> that You distribute, all copyright, patent, trademark, and
|
||||||
|
> attribution notices from the Source form of the Work,
|
||||||
|
> excluding those notices that do not pertain to any part of
|
||||||
|
> the Derivative Works; and
|
||||||
|
>
|
||||||
|
> (d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
> distribution, then any Derivative Works that You distribute must
|
||||||
|
> include a readable copy of the attribution notices contained
|
||||||
|
> within such NOTICE file, excluding those notices that do not
|
||||||
|
> pertain to any part of the Derivative Works, in at least one
|
||||||
|
> of the following places: within a NOTICE text file distributed
|
||||||
|
> as part of the Derivative Works; within the Source form or
|
||||||
|
> documentation, if provided along with the Derivative Works; or,
|
||||||
|
> within a display generated by the Derivative Works, if and
|
||||||
|
> wherever such third-party notices normally appear. The contents
|
||||||
|
> of the NOTICE file are for informational purposes only and
|
||||||
|
> do not modify the License. You may add Your own attribution
|
||||||
|
> notices within Derivative Works that You distribute, alongside
|
||||||
|
> or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
> that such additional attribution notices cannot be construed
|
||||||
|
> as modifying the License.
|
||||||
|
>
|
||||||
|
> You may add Your own copyright statement to Your modifications and
|
||||||
|
> may provide additional or different license terms and conditions
|
||||||
|
> for use, reproduction, or distribution of Your modifications, or
|
||||||
|
> for any such Derivative Works as a whole, provided Your use,
|
||||||
|
> reproduction, and distribution of the Work otherwise complies with
|
||||||
|
> the conditions stated in this License.
|
||||||
|
>
|
||||||
|
> 5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
> any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
> by You to the Licensor shall be under the terms and conditions of
|
||||||
|
> this License, without any additional terms or conditions.
|
||||||
|
> Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
> the terms of any separate license agreement you may have executed
|
||||||
|
> with Licensor regarding such Contributions.
|
||||||
|
>
|
||||||
|
> 6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
> names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
> except as required for reasonable and customary use in describing the
|
||||||
|
> origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
>
|
||||||
|
> 7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
> agreed to in writing, Licensor provides the Work (and each
|
||||||
|
> Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
> implied, including, without limitation, any warranties or conditions
|
||||||
|
> of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
> PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
> appropriateness of using or redistributing the Work and assume any
|
||||||
|
> risks associated with Your exercise of permissions under this License.
|
||||||
|
>
|
||||||
|
> 8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
> whether in tort (including negligence), contract, or otherwise,
|
||||||
|
> unless required by applicable law (such as deliberate and grossly
|
||||||
|
> negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
> liable to You for damages, including any direct, indirect, special,
|
||||||
|
> incidental, or consequential damages of any character arising as a
|
||||||
|
> result of this License or out of the use or inability to use the
|
||||||
|
> Work (including but not limited to damages for loss of goodwill,
|
||||||
|
> work stoppage, computer failure or malfunction, or any and all
|
||||||
|
> other commercial damages or losses), even if such Contributor
|
||||||
|
> has been advised of the possibility of such damages.
|
||||||
|
>
|
||||||
|
> 9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
> the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
> and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
> or other liability obligations and/or rights consistent with this
|
||||||
|
> License. However, in accepting such obligations, You may act only
|
||||||
|
> on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
> of any other Contributor, and only if You agree to indemnify,
|
||||||
|
> defend, and hold each Contributor harmless for any liability
|
||||||
|
> incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
> of your accepting any such warranty or additional liability.
|
||||||
|
>
|
||||||
|
> END OF TERMS AND CONDITIONS
|
||||||
|
>
|
||||||
|
> APPENDIX: How to apply the Apache License to your work.
|
||||||
|
>
|
||||||
|
> To apply the Apache License to your work, attach the following
|
||||||
|
> boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
> replaced with your own identifying information. (Don't include
|
||||||
|
> the brackets!) The text should be enclosed in the appropriate
|
||||||
|
> comment syntax for the file format. We also recommend that a
|
||||||
|
> file or class name and description of purpose be included on the
|
||||||
|
> same "printed page" as the copyright notice for easier
|
||||||
|
> identification within third-party archives.
|
||||||
|
>
|
||||||
|
> Copyright 2017 Vercel, Inc.
|
||||||
|
>
|
||||||
|
> Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
> you may not use this file except in compliance with the License.
|
||||||
|
> You may obtain a copy of the License at
|
||||||
|
>
|
||||||
|
> http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
>
|
||||||
|
> Unless required by applicable law or agreed to in writing, software
|
||||||
|
> distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
> See the License for the specific language governing permissions and
|
||||||
|
> limitations under the License.
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
## @vitest/utils
|
||||||
|
License: MIT
|
||||||
|
Repository: https://github.com/vitest-dev/vitest
|
||||||
|
|
||||||
|
> MIT License
|
||||||
|
>
|
||||||
|
> Copyright (c) 2021-Present VoidZero Inc. and Vitest contributors
|
||||||
|
>
|
||||||
|
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
> of this software and associated documentation files (the "Software"), to deal
|
||||||
|
> in the Software without restriction, including without limitation the rights
|
||||||
|
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
> copies of the Software, and to permit persons to whom the Software is
|
||||||
|
> furnished to do so, subject to the following conditions:
|
||||||
|
>
|
||||||
|
> The above copyright notice and this permission notice shall be included in all
|
||||||
|
> copies or substantial portions of the Software.
|
||||||
|
>
|
||||||
|
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
> SOFTWARE.
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
## anymatch
|
## anymatch
|
||||||
License: ISC
|
License: ISC
|
||||||
By: Elan Shanker
|
By: Elan Shanker
|
||||||
|
|
@ -296,7 +497,7 @@ Repositories: https://github.com/sindresorhus/bundle-name, https://github.com/si
|
||||||
## cac
|
## cac
|
||||||
License: MIT
|
License: MIT
|
||||||
By: egoist
|
By: egoist
|
||||||
Repository: https://github.com/egoist/cac
|
Repository: https://github.com/cacjs/cac
|
||||||
|
|
||||||
> The MIT License (MIT)
|
> The MIT License (MIT)
|
||||||
>
|
>
|
||||||
|
|
@ -351,38 +552,6 @@ Repository: https://github.com/paulmillr/chokidar
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## commondir, shell-quote
|
|
||||||
License: MIT
|
|
||||||
By: James Halliday
|
|
||||||
Repositories: http://github.com/substack/node-commondir, http://github.com/ljharb/shell-quote
|
|
||||||
|
|
||||||
> The MIT License
|
|
||||||
>
|
|
||||||
> Copyright (c) 2013 James Halliday (mail@substack.net)
|
|
||||||
>
|
|
||||||
> Permission is hereby granted, free of charge,
|
|
||||||
> to any person obtaining a copy of this software and
|
|
||||||
> associated documentation files (the "Software"), to
|
|
||||||
> deal in the Software without restriction, including
|
|
||||||
> without limitation the rights to use, copy, modify,
|
|
||||||
> merge, publish, distribute, sublicense, and/or sell
|
|
||||||
> copies of the Software, and to permit persons to whom
|
|
||||||
> the Software is furnished to do so,
|
|
||||||
> subject to the following conditions:
|
|
||||||
>
|
|
||||||
> The above copyright notice and this permission notice
|
|
||||||
> shall be included in all copies or substantial portions of the Software.
|
|
||||||
>
|
|
||||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
||||||
> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
## connect
|
## connect
|
||||||
License: MIT
|
License: MIT
|
||||||
By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell
|
By: TJ Holowaychuk, Douglas Christopher Wilson, Jonathan Ong, Tim Caswell
|
||||||
|
|
@ -534,36 +703,6 @@ Repository: https://github.com/mathiasbynens/cssesc
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## dotenv
|
|
||||||
License: BSD-2-Clause
|
|
||||||
Repository: https://github.com/motdotla/dotenv
|
|
||||||
|
|
||||||
> Copyright (c) 2015, Scott Motte
|
|
||||||
> All rights reserved.
|
|
||||||
>
|
|
||||||
> Redistribution and use in source and binary forms, with or without
|
|
||||||
> modification, are permitted provided that the following conditions are met:
|
|
||||||
>
|
|
||||||
> * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
> list of conditions and the following disclaimer.
|
|
||||||
>
|
|
||||||
> * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
> this list of conditions and the following disclaimer in the documentation
|
|
||||||
> and/or other materials provided with the distribution.
|
|
||||||
>
|
|
||||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
> DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
> FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
> DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
> SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
> OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
## dotenv-expand
|
## dotenv-expand
|
||||||
License: BSD-2-Clause
|
License: BSD-2-Clause
|
||||||
By: motdotla
|
By: motdotla
|
||||||
|
|
@ -1023,13 +1162,6 @@ Repository: https://github.com/micromatch/is-glob
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## is-reference
|
|
||||||
License: MIT
|
|
||||||
By: Rich Harris
|
|
||||||
Repository: https://github.com/Rich-Harris/is-reference
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
## isexe, which
|
## isexe, which
|
||||||
License: ISC
|
License: ISC
|
||||||
By: Isaac Z. Schlueter
|
By: Isaac Z. Schlueter
|
||||||
|
|
@ -1417,7 +1549,7 @@ Repository: https://github.com/jshttp/on-finished
|
||||||
|
|
||||||
## parse5
|
## parse5
|
||||||
License: MIT
|
License: MIT
|
||||||
By: Ivan Nikulin, https://github.com/inikulin/parse5/graphs/contributors
|
By: Ivan Nikulin, James Garbutt, Felix Boehm, Titus
|
||||||
Repository: https://github.com/inikulin/parse5
|
Repository: https://github.com/inikulin/parse5
|
||||||
|
|
||||||
> Copyright (c) 2013-2019 Ivan Nikulin (ifaaan@gmail.com, https://github.com/inikulin)
|
> Copyright (c) 2013-2019 Ivan Nikulin (ifaaan@gmail.com, https://github.com/inikulin)
|
||||||
|
|
@ -1817,6 +1949,38 @@ Repository: https://github.com/kevva/shebang-command
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
|
## shell-quote
|
||||||
|
License: MIT
|
||||||
|
By: James Halliday
|
||||||
|
Repository: http://github.com/ljharb/shell-quote
|
||||||
|
|
||||||
|
> The MIT License
|
||||||
|
>
|
||||||
|
> Copyright (c) 2013 James Halliday (mail@substack.net)
|
||||||
|
>
|
||||||
|
> Permission is hereby granted, free of charge,
|
||||||
|
> to any person obtaining a copy of this software and
|
||||||
|
> associated documentation files (the "Software"), to
|
||||||
|
> deal in the Software without restriction, including
|
||||||
|
> without limitation the rights to use, copy, modify,
|
||||||
|
> merge, publish, distribute, sublicense, and/or sell
|
||||||
|
> copies of the Software, and to permit persons to whom
|
||||||
|
> the Software is furnished to do so,
|
||||||
|
> subject to the following conditions:
|
||||||
|
>
|
||||||
|
> The above copyright notice and this permission notice
|
||||||
|
> shall be included in all copies or substantial portions of the Software.
|
||||||
|
>
|
||||||
|
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
> OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||||
|
> ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
## sirv
|
## sirv
|
||||||
License: MIT
|
License: MIT
|
||||||
By: Luke Edwards
|
By: Luke Edwards
|
||||||
|
|
@ -1919,60 +2083,6 @@ Repository: https://github.com/micromatch/to-regex-range
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## tsconfck
|
|
||||||
License: MIT
|
|
||||||
By: dominikg
|
|
||||||
Repository: https://github.com/dominikg/tsconfck
|
|
||||||
|
|
||||||
> MIT License
|
|
||||||
>
|
|
||||||
> Copyright (c) 2021-present dominikg and [contributors](https://github.com/dominikg/tsconfck/graphs/contributors)
|
|
||||||
>
|
|
||||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
> of this software and associated documentation files (the "Software"), to deal
|
|
||||||
> in the Software without restriction, including without limitation the rights
|
|
||||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
> copies of the Software, and to permit persons to whom the Software is
|
|
||||||
> furnished to do so, subject to the following conditions:
|
|
||||||
>
|
|
||||||
> The above copyright notice and this permission notice shall be included in all
|
|
||||||
> copies or substantial portions of the Software.
|
|
||||||
>
|
|
||||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
> SOFTWARE.
|
|
||||||
>
|
|
||||||
> -- Licenses for 3rd-party code included in tsconfck --
|
|
||||||
>
|
|
||||||
> # strip-bom and strip-json-comments
|
|
||||||
> MIT License
|
|
||||||
>
|
|
||||||
> Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
|
||||||
>
|
|
||||||
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
> of this software and associated documentation files (the "Software"), to deal
|
|
||||||
> in the Software without restriction, including without limitation the rights
|
|
||||||
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
> copies of the Software, and to permit persons to whom the Software is
|
|
||||||
> furnished to do so, subject to the following conditions:
|
|
||||||
>
|
|
||||||
> The above copyright notice and this permission notice shall be included in all
|
|
||||||
> copies or substantial portions of the Software.
|
|
||||||
>
|
|
||||||
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
> SOFTWARE.
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
## unpipe
|
## unpipe
|
||||||
License: MIT
|
License: MIT
|
||||||
By: Douglas Christopher Wilson
|
By: Douglas Christopher Wilson
|
||||||
17
package.json
17
package.json
|
|
@ -15,6 +15,7 @@
|
||||||
"pre-build": "bun run lint && bun run build:packages",
|
"pre-build": "bun run lint && bun run build:packages",
|
||||||
"build:apps": "bun run copy-licenses && bun run pre-build && bun run build:mobile && bun run build:desktop && bun --bun run copy-releases",
|
"build:apps": "bun run copy-licenses && bun run pre-build && bun run build:mobile && bun run build:desktop && bun --bun run copy-releases",
|
||||||
"build:packages": "bun scripts/build-packages.ts",
|
"build:packages": "bun scripts/build-packages.ts",
|
||||||
|
"update:packages": "bun scripts/update-packages.ts",
|
||||||
"dev:web": "cd apps/web && bun dev",
|
"dev:web": "cd apps/web && bun dev",
|
||||||
"build:web": "cd apps/web && bun run build",
|
"build:web": "cd apps/web && bun run build",
|
||||||
"preview:web": "cd apps/web && bun run preview",
|
"preview:web": "cd apps/web && bun run preview",
|
||||||
|
|
@ -28,18 +29,18 @@
|
||||||
"@types/node": "^25.6.0",
|
"@types/node": "^25.6.0",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@typescript-eslint/parser": "^8.58.1",
|
"@typescript-eslint/parser": "^8.59.1",
|
||||||
"eslint": "^10.2.0",
|
"eslint": "^10.2.1",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"globals": "^17.4.0",
|
"globals": "^17.5.0",
|
||||||
"prettier": "^3.8.2",
|
"prettier": "^3.8.3",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.3",
|
||||||
"typescript-eslint": "^8.58.1",
|
"typescript-eslint": "^8.59.1",
|
||||||
"jsonc-parser": "^3.3.1"
|
"jsonc-parser": "^3.3.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
"@tensamin/ui": "https://git.methanium.net/tensamin/ui/archive/0.0.30.tar.gz",
|
||||||
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.13.tar.gz"
|
"@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.15.tar.gz"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tensamin/ttp-core": "*",
|
"@tensamin/ttp-core": "*",
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@livekit/components-react": "^2.9.20",
|
"@livekit/components-react": "^2.9.20",
|
||||||
"@tanstack/react-query": "^5.0.0",
|
"@tanstack/react-query": "^5.100.7",
|
||||||
"@tanstack/react-router": "^1.0.0",
|
"@tanstack/react-router": "^1.169.1",
|
||||||
"@tanstack/react-virtual": "^3.0.0",
|
"@tanstack/react-virtual": "^3.13.24",
|
||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tensamin/crypto": "workspace:*",
|
"@tensamin/crypto": "workspace:*",
|
||||||
"@tensamin/markdown": "workspace:*",
|
"@tensamin/markdown": "workspace:*",
|
||||||
|
|
@ -29,8 +29,8 @@
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"@tensamin/user": "workspace:*",
|
"@tensamin/user": "workspace:*",
|
||||||
"deepfilternet3-noise-filter": "^1.2.1",
|
"deepfilternet3-noise-filter": "^1.2.1",
|
||||||
"livekit-client": "^2.18.1",
|
"livekit-client": "^2.18.8",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"recharts": "^3.8.1",
|
"recharts": "^3.8.1",
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ export default function ScreenShareDialog({
|
||||||
|
|
||||||
let active = true;
|
let active = true;
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setSelectedSourceId(null);
|
setSelectedSourceId(null);
|
||||||
setSelectedAudioOutputId(NONE_AUDIO_OUTPUT);
|
setSelectedAudioOutputId(NONE_AUDIO_OUTPUT);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
"@tensamin/markdown": "workspace:*",
|
"@tensamin/markdown": "workspace:*",
|
||||||
"@tensamin/shared": "workspace:*",
|
"@tensamin/shared": "workspace:*",
|
||||||
"@tensamin/ui": "*",
|
"@tensamin/ui": "*",
|
||||||
"lucide-react": "^0.564.0",
|
"lucide-react": "^1.14.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue