mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-12 21:35:20 +01:00
Compare commits
2 Commits
yaak-cli-0
...
fix/native
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67cbb06bb9 | ||
|
|
c8ba35e268 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -10447,6 +10447,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"log 0.4.29",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"regex 1.11.1",
|
||||
"reqwest",
|
||||
"serde",
|
||||
|
||||
@@ -1383,13 +1383,12 @@ async fn cmd_reload_plugins<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> YaakResult<()> {
|
||||
) -> YaakResult<Vec<(String, String)>> {
|
||||
let plugins = app_handle.db().list_plugins()?;
|
||||
let plugin_context =
|
||||
PluginContext::new(Some(window.label().to_string()), window.workspace_id());
|
||||
let _errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
|
||||
// Note: errors are returned but we don't show toasts here since this is a manual reload
|
||||
Ok(())
|
||||
let errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1731,6 +1730,7 @@ pub fn run() {
|
||||
git_ext::cmd_git_rm_remote,
|
||||
//
|
||||
// Plugin commands
|
||||
plugins_ext::cmd_plugin_init_errors,
|
||||
plugins_ext::cmd_plugins_install_from_directory,
|
||||
plugins_ext::cmd_plugins_search,
|
||||
plugins_ext::cmd_plugins_install,
|
||||
|
||||
@@ -198,6 +198,13 @@ pub async fn cmd_plugins_uninstall<R: Runtime>(
|
||||
Ok(delete_and_uninstall(plugin_manager, &query_manager, &plugin_context, plugin_id).await?)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn cmd_plugin_init_errors(
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<Vec<(String, String)>> {
|
||||
Ok(plugin_manager.take_init_errors().await)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn cmd_plugins_updates<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
@@ -306,7 +313,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
dev_mode,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to initialize plugins");
|
||||
.expect("Failed to start plugin runtime");
|
||||
|
||||
app_handle_clone.manage(manager);
|
||||
});
|
||||
|
||||
@@ -18,8 +18,9 @@ zstd = "0.13"
|
||||
hyper-util = { version = "0.1.17", default-features = false, features = ["client-legacy"] }
|
||||
log = { workspace = true }
|
||||
mime_guess = "2.0.5"
|
||||
native-tls = "0.2"
|
||||
regex = "1.11.1"
|
||||
reqwest = { workspace = true, features = ["rustls-tls-manual-roots-no-provider", "socks", "http2", "stream"] }
|
||||
reqwest = { workspace = true, features = ["rustls-tls-manual-roots-no-provider", "native-tls", "socks", "http2", "stream"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
@@ -6,6 +6,56 @@ use std::sync::Arc;
|
||||
use yaak_models::models::DnsOverride;
|
||||
use yaak_tls::{ClientCertificateConfig, get_tls_config};
|
||||
|
||||
/// Build a native-tls connector for maximum compatibility when certificate
|
||||
/// validation is disabled. Unlike rustls, native-tls uses the OS TLS stack
|
||||
/// (Secure Transport on macOS, SChannel on Windows, OpenSSL on Linux) which
|
||||
/// supports TLS 1.0+ for legacy servers.
|
||||
fn build_native_tls_connector(
|
||||
client_cert: Option<ClientCertificateConfig>,
|
||||
) -> Result<native_tls::TlsConnector> {
|
||||
let mut builder = native_tls::TlsConnector::builder();
|
||||
builder.danger_accept_invalid_certs(true);
|
||||
builder.danger_accept_invalid_hostnames(true);
|
||||
builder.min_protocol_version(Some(native_tls::Protocol::Tlsv10));
|
||||
|
||||
if let Some(identity) = build_native_tls_identity(client_cert)? {
|
||||
builder.identity(identity);
|
||||
}
|
||||
|
||||
Ok(builder.build()?)
|
||||
}
|
||||
|
||||
fn build_native_tls_identity(
|
||||
client_cert: Option<ClientCertificateConfig>,
|
||||
) -> Result<Option<native_tls::Identity>> {
|
||||
let config = match client_cert {
|
||||
None => return Ok(None),
|
||||
Some(c) => c,
|
||||
};
|
||||
|
||||
// Try PFX/PKCS12 first
|
||||
if let Some(pfx_path) = &config.pfx_file {
|
||||
if !pfx_path.is_empty() {
|
||||
let pfx_data = std::fs::read(pfx_path)?;
|
||||
let password = config.passphrase.as_deref().unwrap_or("");
|
||||
let identity = native_tls::Identity::from_pkcs12(&pfx_data, password)?;
|
||||
return Ok(Some(identity));
|
||||
}
|
||||
}
|
||||
|
||||
// Try CRT + KEY files
|
||||
if let (Some(crt_path), Some(key_path)) = (&config.crt_file, &config.key_file) {
|
||||
if !crt_path.is_empty() && !key_path.is_empty() {
|
||||
let crt_data = std::fs::read(crt_path)?;
|
||||
let key_data = std::fs::read(key_path)?;
|
||||
let identity = native_tls::Identity::from_pkcs8(&crt_data, &key_data)?;
|
||||
return Ok(Some(identity));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpConnectionProxySettingAuth {
|
||||
pub user: String,
|
||||
@@ -51,10 +101,17 @@ impl HttpConnectionOptions {
|
||||
// This is needed so we can emit DNS timing events for each request
|
||||
.pool_max_idle_per_host(0);
|
||||
|
||||
// Configure TLS with optional client certificate
|
||||
let config =
|
||||
get_tls_config(self.validate_certificates, true, self.client_certificate.clone())?;
|
||||
client = client.use_preconfigured_tls(config);
|
||||
// Configure TLS
|
||||
if self.validate_certificates {
|
||||
// Use rustls with platform certificate verification (TLS 1.2+ only)
|
||||
let config = get_tls_config(true, true, self.client_certificate.clone())?;
|
||||
client = client.use_preconfigured_tls(config);
|
||||
} else {
|
||||
// Use native TLS for maximum compatibility (supports TLS 1.0+)
|
||||
let connector =
|
||||
build_native_tls_connector(self.client_certificate.clone())?;
|
||||
client = client.use_preconfigured_tls(connector);
|
||||
}
|
||||
|
||||
// Configure DNS resolver - keep a reference to configure per-request
|
||||
let resolver = LocalhostResolver::new(self.dns_overrides.clone());
|
||||
|
||||
@@ -9,6 +9,12 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
TlsError(#[from] yaak_tls::error::Error),
|
||||
|
||||
#[error("Native TLS error: {0}")]
|
||||
NativeTlsError(#[from] native_tls::Error),
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Request failed with {0:?}")]
|
||||
RequestError(String),
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ pub struct PluginManager {
|
||||
vendored_plugin_dir: PathBuf,
|
||||
pub(crate) installed_plugin_dir: PathBuf,
|
||||
dev_mode: bool,
|
||||
/// Errors from plugin initialization, retrievable once via `take_init_errors`.
|
||||
init_errors: Arc<Mutex<Vec<(String, String)>>>,
|
||||
}
|
||||
|
||||
/// Callback for plugin initialization events (e.g., toast notifications)
|
||||
@@ -93,6 +95,7 @@ impl PluginManager {
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
dev_mode,
|
||||
init_errors: Default::default(),
|
||||
};
|
||||
|
||||
// Forward events to subscribers
|
||||
@@ -183,17 +186,21 @@ impl PluginManager {
|
||||
|
||||
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
|
||||
if !init_errors.is_empty() {
|
||||
let joined = init_errors
|
||||
.into_iter()
|
||||
.map(|(dir, err)| format!("{dir}: {err}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
|
||||
for (dir, err) in &init_errors {
|
||||
warn!("Plugin failed to initialize: {dir}: {err}");
|
||||
}
|
||||
*plugin_manager.init_errors.lock().await = init_errors;
|
||||
}
|
||||
|
||||
Ok(plugin_manager)
|
||||
}
|
||||
|
||||
/// Take any initialization errors, clearing them from the manager.
|
||||
/// Returns a list of `(plugin_directory, error_message)` pairs.
|
||||
pub async fn take_init_errors(&self) -> Vec<(String, String)> {
|
||||
std::mem::take(&mut *self.init_errors.lock().await)
|
||||
}
|
||||
|
||||
/// Get the vendored plugin directory path (resolves dev mode path if applicable)
|
||||
pub fn get_plugins_dir(&self) -> PathBuf {
|
||||
if self.dev_mode {
|
||||
|
||||
@@ -123,6 +123,39 @@ export function initGlobalListeners() {
|
||||
console.log('Got plugin updates event', payload);
|
||||
showPluginUpdatesToast(payload);
|
||||
});
|
||||
|
||||
// Check for plugin initialization errors
|
||||
invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => {
|
||||
for (const [dir, message] of errors) {
|
||||
const dirBasename = dir.split('/').pop() ?? dir;
|
||||
showToast({
|
||||
id: `plugin-init-error-${dirBasename}`,
|
||||
color: 'warning',
|
||||
timeout: null,
|
||||
message: (
|
||||
<VStack>
|
||||
<h2 className="font-semibold">Plugin failed to load</h2>
|
||||
<p className="text-text-subtle text-sm">
|
||||
{dirBasename}: {message}
|
||||
</p>
|
||||
</VStack>
|
||||
),
|
||||
action: ({ hide }) => (
|
||||
<Button
|
||||
size="xs"
|
||||
color="warning"
|
||||
variant="border"
|
||||
onClick={() => {
|
||||
hide();
|
||||
openSettings.mutate('plugins:installed');
|
||||
}}
|
||||
>
|
||||
View Plugins
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdateInstalledToast(version: string) {
|
||||
|
||||
@@ -42,6 +42,7 @@ type TauriCmd =
|
||||
| 'cmd_new_child_window'
|
||||
| 'cmd_new_main_window'
|
||||
| 'cmd_plugin_info'
|
||||
| 'cmd_plugin_init_errors'
|
||||
| 'cmd_reload_plugins'
|
||||
| 'cmd_render_template'
|
||||
| 'cmd_save_response'
|
||||
|
||||
Reference in New Issue
Block a user