mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-19 07:29:40 +02:00
Merge pull request #227
* Search and install plugins PoC * Checksum * Tab sidebar for settings * Fix nested tabs, and tweaks * Table for plugin results * Deep links working * Focus window during deep links * Merge branch 'master' into plugin-directory * More stuff
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
[package]
|
||||
name = "yaak-plugins"
|
||||
links = "yaak-plugins"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
dunce = "1.0.4"
|
||||
futures-util = "0.3.30"
|
||||
log = "0.4.21"
|
||||
@@ -12,16 +14,23 @@ md5 = "0.7.0"
|
||||
path-slash = "0.2.1"
|
||||
rand = "0.9.0"
|
||||
regex = "1.10.6"
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
thiserror = "2.0.7"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "process"] }
|
||||
tokio-tungstenite = "0.26.1"
|
||||
ts-rs = { workspace = true, features = ["import-esm"] }
|
||||
sha2 = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
yaak-templates = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-common = { workspace = true }
|
||||
base64 = "0.22.1"
|
||||
zip-extract = "0.4.0"
|
||||
chrono = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -372,7 +372,7 @@ export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
|
||||
5
src-tauri/yaak-plugins/bindings/gen_search.ts
Normal file
5
src-tauri/yaak-plugins/bindings/gen_search.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginSearchResponse = { results: Array<PluginVersion>, };
|
||||
|
||||
export type PluginVersion = { id: string, version: string, description: string | null, displayName: string, homepageUrl: string | null, repositoryUrl: string, checksum: string, readme: string | null, yanked: boolean, };
|
||||
5
src-tauri/yaak-plugins/build.rs
Normal file
5
src-tauri/yaak-plugins/build.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
const COMMANDS: &[&str] = &["search", "install"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
}
|
||||
@@ -1,2 +1,14 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { PluginSearchResponse, PluginVersion } from './bindings/gen_search';
|
||||
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
export * from './bindings/gen_search';
|
||||
|
||||
export async function searchPlugins(query: string) {
|
||||
return invoke<PluginSearchResponse>('plugin:yaak-plugins|search', { query });
|
||||
}
|
||||
|
||||
export async function installPlugin(plugin: PluginVersion) {
|
||||
return invoke<string>('plugin:yaak-plugins|install', { plugin });
|
||||
}
|
||||
|
||||
3
src-tauri/yaak-plugins/permissions/default.toml
Normal file
3
src-tauri/yaak-plugins/permissions/default.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-search", "allow-install"]
|
||||
67
src-tauri/yaak-plugins/src/api.rs
Normal file
67
src-tauri/yaak-plugins/src/api.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use crate::commands::{PluginSearchResponse, PluginVersion};
|
||||
use crate::error::Result;
|
||||
use reqwest::{Response, Url};
|
||||
use std::str::FromStr;
|
||||
use log::info;
|
||||
use tauri::{AppHandle, Runtime, is_dev};
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
use crate::error::Error::ApiErr;
|
||||
|
||||
pub async fn get_plugin<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
name: &str,
|
||||
version: Option<String>,
|
||||
) -> Result<PluginVersion> {
|
||||
info!("Getting plugin: {name} {version:?}");
|
||||
let mut url = base_url(&format!("/{name}"));
|
||||
if let Some(version) = version {
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("version", &version);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url.clone()).send().await?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiErr(format!("{} response to {}", resp.status(), url.to_string())));
|
||||
}
|
||||
Ok(resp.json().await?)
|
||||
}
|
||||
|
||||
pub async fn download_plugin_archive<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
plugin_version: &PluginVersion,
|
||||
) -> Result<Response> {
|
||||
let name = plugin_version.name.clone();
|
||||
let version = plugin_version.version.clone();
|
||||
info!("Downloading plugin: {name} {version}");
|
||||
let mut url = base_url(&format!("/{}/download", name));
|
||||
{
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("version", &version);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url.clone()).send().await?;
|
||||
if !resp.status().is_success() {
|
||||
return Err(ApiErr(format!("{} response to {}", resp.status(), url.to_string())));
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
pub async fn search_plugins<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
query: &str,
|
||||
) -> Result<PluginSearchResponse> {
|
||||
let mut url = base_url("/search");
|
||||
{
|
||||
let mut query_pairs = url.query_pairs_mut();
|
||||
query_pairs.append_pair("query", query);
|
||||
};
|
||||
let resp = yaak_api_client(app_handle)?.get(url).send().await?;
|
||||
Ok(resp.json().await?)
|
||||
}
|
||||
|
||||
fn base_url(path: &str) -> Url {
|
||||
let base_url = if is_dev() {
|
||||
"http://localhost:9444/api/v1/plugins"
|
||||
} else {
|
||||
"https://api.yaak.app/api/v1/plugins"
|
||||
};
|
||||
Url::from_str(&format!("{base_url}{path}")).unwrap()
|
||||
}
|
||||
8
src-tauri/yaak-plugins/src/checksum.rs
Normal file
8
src-tauri/yaak-plugins/src/checksum.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
pub(crate) fn compute_checksum(bytes: impl AsRef<[u8]>) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&bytes);
|
||||
let hash = hasher.finalize();
|
||||
hex::encode(hash)
|
||||
}
|
||||
45
src-tauri/yaak-plugins/src/commands.rs
Normal file
45
src-tauri/yaak-plugins/src/commands.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::api::search_plugins;
|
||||
use crate::error::Result;
|
||||
use crate::install::download_and_install;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow, command};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn search<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
query: &str,
|
||||
) -> Result<PluginSearchResponse> {
|
||||
search_plugins(&app_handle, query).await
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub(crate) async fn install<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin: PluginVersion,
|
||||
) -> Result<String> {
|
||||
download_and_install(&window, &plugin).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginSearchResponse {
|
||||
pub results: Vec<PluginVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_search.ts")]
|
||||
pub struct PluginVersion {
|
||||
pub id: String,
|
||||
pub version: String,
|
||||
pub description: Option<String>,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub homepage_url: Option<String>,
|
||||
pub repository_url: Option<String>,
|
||||
pub checksum: String,
|
||||
pub readme: Option<String>,
|
||||
pub yanked: bool,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::events::InternalEvent;
|
||||
use serde::{Serialize, Serializer};
|
||||
use thiserror::Error;
|
||||
use tokio::io;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
@@ -8,9 +9,12 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
CryptoErr(#[from] yaak_crypto::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
DbErr(#[from] yaak_models::error::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
TemplateErr(#[from] yaak_templates::error::Error),
|
||||
|
||||
|
||||
#[error("IO error: {0}")]
|
||||
IoErr(#[from] io::Error),
|
||||
|
||||
@@ -23,8 +27,17 @@ pub enum Error {
|
||||
#[error("Grpc send error: {0}")]
|
||||
GrpcSendErr(#[from] SendError<InternalEvent>),
|
||||
|
||||
#[error("Failed to send request: {0}")]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
|
||||
#[error("JSON error: {0}")]
|
||||
JsonErr(#[from] serde_json::Error),
|
||||
|
||||
#[error("API Error: {0}")]
|
||||
ApiErr(String),
|
||||
|
||||
#[error(transparent)]
|
||||
CommonError(#[from] yaak_common::error::Error),
|
||||
|
||||
#[error("Timeout elapsed: {0}")]
|
||||
TimeoutElapsed(#[from] tokio::time::error::Elapsed),
|
||||
@@ -38,6 +51,9 @@ pub enum Error {
|
||||
#[error("Plugin error: {0}")]
|
||||
PluginErr(String),
|
||||
|
||||
#[error("zip error: {0}")]
|
||||
ZipError(#[from] zip_extract::ZipExtractError),
|
||||
|
||||
#[error("Client not initialized error")]
|
||||
ClientNotInitializedErr,
|
||||
|
||||
@@ -45,4 +61,13 @@ pub enum Error {
|
||||
UnknownEventErr,
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -67,7 +67,7 @@ pub enum InternalEventPayload {
|
||||
BootResponse(BootResponse),
|
||||
|
||||
ReloadRequest(EmptyPayload),
|
||||
ReloadResponse(EmptyPayload),
|
||||
ReloadResponse(BootResponse),
|
||||
|
||||
TerminateRequest,
|
||||
TerminateResponse,
|
||||
|
||||
60
src-tauri/yaak-plugins/src/install.rs
Normal file
60
src-tauri/yaak-plugins/src/install.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::api::download_plugin_archive;
|
||||
use crate::checksum::compute_checksum;
|
||||
use crate::commands::PluginVersion;
|
||||
use crate::error::Error::PluginErr;
|
||||
use crate::error::Result;
|
||||
use crate::events::PluginWindowContext;
|
||||
use crate::manager::PluginManager;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
use std::fs::create_dir_all;
|
||||
use std::io::Cursor;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use yaak_models::models::Plugin;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::{UpdateSource, generate_id};
|
||||
|
||||
pub async fn download_and_install<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
plugin_version: &PluginVersion,
|
||||
) -> Result<String> {
|
||||
let resp = download_plugin_archive(window.app_handle(), &plugin_version).await?;
|
||||
let bytes = resp.bytes().await?;
|
||||
|
||||
let checksum = compute_checksum(&bytes);
|
||||
if checksum != plugin_version.checksum {
|
||||
return Err(PluginErr(format!(
|
||||
"Checksum mismatch {}b {checksum} != {}",
|
||||
bytes.len(),
|
||||
plugin_version.checksum
|
||||
)));
|
||||
}
|
||||
|
||||
info!("Checksum matched {}", checksum);
|
||||
|
||||
let plugin_dir = window.path().app_data_dir()?.join("plugins").join(generate_id());
|
||||
let plugin_dir_str = plugin_dir.to_str().unwrap().to_string();
|
||||
create_dir_all(&plugin_dir)?;
|
||||
|
||||
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
|
||||
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
let plugin_manager = window.state::<PluginManager>();
|
||||
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &plugin_dir_str).await?;
|
||||
|
||||
let p = window.db().upsert_plugin(
|
||||
&Plugin {
|
||||
id: plugin_version.id.clone(),
|
||||
checked_at: Some(Utc::now().naive_utc()),
|
||||
directory: plugin_dir_str.clone(),
|
||||
enabled: true,
|
||||
url: None,
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Background,
|
||||
)?;
|
||||
|
||||
info!("Installed plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||
|
||||
Ok(p.id)
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use crate::commands::{install, search};
|
||||
use crate::manager::PluginManager;
|
||||
use log::info;
|
||||
use std::process::exit;
|
||||
use tauri::plugin::{Builder, TauriPlugin};
|
||||
use tauri::{Manager, RunEvent, Runtime, State};
|
||||
use tauri::{Manager, RunEvent, Runtime, State, generate_handler};
|
||||
|
||||
mod commands;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod manager;
|
||||
@@ -13,9 +15,13 @@ pub mod plugin_handle;
|
||||
mod server_ws;
|
||||
pub mod template_callback;
|
||||
mod util;
|
||||
mod checksum;
|
||||
pub mod api;
|
||||
pub mod install;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak-plugins")
|
||||
.invoke_handler(generate_handler![search, install])
|
||||
.setup(|app_handle, _| {
|
||||
let manager = PluginManager::new(app_handle.clone());
|
||||
app_handle.manage(manager.clone());
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::server_ws::PluginRuntimeServerWebsocket;
|
||||
use log::{error, info, warn};
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tauri::path::BaseDirectory;
|
||||
@@ -38,12 +38,13 @@ pub struct PluginManager {
|
||||
plugins: Arc<Mutex<Vec<PluginHandle>>>,
|
||||
kill_tx: tokio::sync::watch::Sender<bool>,
|
||||
ws_service: Arc<PluginRuntimeServerWebsocket>,
|
||||
vendored_plugin_dir: PathBuf,
|
||||
installed_plugin_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PluginCandidate {
|
||||
dir: String,
|
||||
watch: bool,
|
||||
}
|
||||
|
||||
impl PluginManager {
|
||||
@@ -56,11 +57,21 @@ impl PluginManager {
|
||||
let ws_service =
|
||||
PluginRuntimeServerWebsocket::new(events_tx, client_disconnect_tx, client_connect_tx);
|
||||
|
||||
let vendored_plugin_dir = app_handle
|
||||
.path()
|
||||
.resolve("vendored/plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let installed_plugin_dir =
|
||||
app_handle.path().app_data_dir().expect("failed to get app data dir");
|
||||
|
||||
let plugin_manager = PluginManager {
|
||||
plugins: Default::default(),
|
||||
subscribers: Default::default(),
|
||||
ws_service: Arc::new(ws_service.clone()),
|
||||
kill_tx: kill_server_tx,
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
};
|
||||
|
||||
// Forward events to subscribers
|
||||
@@ -135,18 +146,13 @@ impl PluginManager {
|
||||
&self,
|
||||
app_handle: &AppHandle<R>,
|
||||
) -> Vec<PluginCandidate> {
|
||||
let bundled_plugins_dir = &app_handle
|
||||
.path()
|
||||
.resolve("vendored/plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
let plugins_dir = if is_dev() {
|
||||
// Use plugins directly for easy development
|
||||
env::current_dir()
|
||||
.map(|cwd| cwd.join("../plugins").canonicalize().unwrap())
|
||||
.unwrap_or_else(|_| bundled_plugins_dir.clone())
|
||||
.unwrap_or_else(|_| self.vendored_plugin_dir.to_path_buf())
|
||||
} else {
|
||||
bundled_plugins_dir.clone()
|
||||
self.vendored_plugin_dir.to_path_buf()
|
||||
};
|
||||
|
||||
info!("Loading bundled plugins from {plugins_dir:?}");
|
||||
@@ -155,13 +161,7 @@ impl PluginManager {
|
||||
.await
|
||||
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str())
|
||||
.iter()
|
||||
.map(|d| {
|
||||
let is_vendored = plugins_dir.starts_with(bundled_plugins_dir);
|
||||
PluginCandidate {
|
||||
dir: d.into(),
|
||||
watch: !is_vendored,
|
||||
}
|
||||
})
|
||||
.map(|d| PluginCandidate { dir: d.into() })
|
||||
.collect();
|
||||
|
||||
let plugins = app_handle.db().list_plugins().unwrap_or_default();
|
||||
@@ -169,7 +169,6 @@ impl PluginManager {
|
||||
.iter()
|
||||
.map(|p| PluginCandidate {
|
||||
dir: p.directory.to_owned(),
|
||||
watch: true,
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -203,7 +202,6 @@ impl PluginManager {
|
||||
&self,
|
||||
window_context: &PluginWindowContext,
|
||||
dir: &str,
|
||||
watch: bool,
|
||||
) -> Result<()> {
|
||||
info!("Adding plugin by dir {dir}");
|
||||
let maybe_tx = self.ws_service.app_to_plugin_events_tx.lock().await;
|
||||
@@ -212,6 +210,9 @@ impl PluginManager {
|
||||
Some(tx) => tx,
|
||||
};
|
||||
let plugin_handle = PluginHandle::new(dir, tx.clone());
|
||||
let dir_path = Path::new(dir);
|
||||
let is_vendored = dir_path.starts_with(self.vendored_plugin_dir.as_path());
|
||||
let is_installed = dir_path.starts_with(self.installed_plugin_dir.as_path());
|
||||
|
||||
// Boot the plugin
|
||||
let event = timeout(
|
||||
@@ -221,7 +222,7 @@ impl PluginManager {
|
||||
&plugin_handle,
|
||||
&InternalEventPayload::BootRequest(BootRequest {
|
||||
dir: dir.to_string(),
|
||||
watch,
|
||||
watch: !is_vendored && !is_installed,
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -256,10 +257,7 @@ impl PluginManager {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Err(e) = self
|
||||
.add_plugin_by_dir(window_context, candidate.dir.as_str(), candidate.watch)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = self.add_plugin_by_dir(window_context, candidate.dir.as_str()).await {
|
||||
warn!("Failed to add plugin {} {e:?}", candidate.dir);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user