Connection re-use for plugin networking and beta NTLM plugin (#295)

This commit is contained in:
Gregory Schier
2025-11-10 14:41:49 -08:00
committed by GitHub
parent d318546d0c
commit 6389fd3b8f
48 changed files with 941 additions and 554 deletions

View File

@@ -6,6 +6,7 @@ use yaak_common::window::WorkspaceWindowTrait;
use yaak_models::models::{
Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, WebsocketRequest, Workspace,
};
use yaak_models::util::generate_prefixed_id;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
@@ -15,7 +16,7 @@ pub struct InternalEvent {
pub plugin_ref_id: String,
pub plugin_name: String,
pub reply_id: Option<String>,
pub window_context: PluginWindowContext,
pub context: PluginContext,
pub payload: InternalEventPayload,
}
@@ -29,32 +30,32 @@ pub(crate) struct InternalEventRawPayload {
pub plugin_ref_id: String,
pub plugin_name: String,
pub reply_id: Option<String>,
pub window_context: PluginWindowContext,
pub context: PluginContext,
pub payload: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", tag = "type")]
#[serde(rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub enum PluginWindowContext {
None,
Label {
label: String,
workspace_id: Option<String>,
},
pub struct PluginContext {
pub id: String,
pub label: Option<String>,
pub workspace_id: Option<String>,
}
impl PluginWindowContext {
pub fn new<R: Runtime>(window: &WebviewWindow<R>) -> Self {
Self::Label {
label: window.label().to_string(),
workspace_id: window.workspace_id(),
impl PluginContext {
pub fn new_empty() -> Self {
Self {
id: "default".to_string(),
label: None,
workspace_id: None,
}
}
pub fn new_no_workspace<R: Runtime>(window: &WebviewWindow<R>) -> Self {
Self::Label {
label: window.label().to_string(),
workspace_id: None,
pub fn new<R: Runtime>(window: &WebviewWindow<R>) -> Self {
Self {
label: Some(window.label().to_string()),
workspace_id: window.workspace_id(),
id: generate_prefixed_id("pctx"),
}
}
}

View File

@@ -2,7 +2,7 @@ use crate::api::{PluginVersion, download_plugin_archive, get_plugin};
use crate::checksum::compute_checksum;
use crate::error::Error::PluginErr;
use crate::error::Result;
use crate::events::PluginWindowContext;
use crate::events::PluginContext;
use crate::manager::PluginManager;
use chrono::Utc;
use log::info;
@@ -19,7 +19,7 @@ pub async fn delete_and_uninstall<R: Runtime>(
) -> Result<Plugin> {
let plugin_manager = window.state::<PluginManager>();
let plugin = window.db().delete_plugin_by_id(plugin_id, &UpdateSource::from_window(&window))?;
plugin_manager.uninstall(&PluginWindowContext::new(&window), plugin.directory.as_str()).await?;
plugin_manager.uninstall(&PluginContext::new(&window), plugin.directory.as_str()).await?;
Ok(plugin)
}
@@ -55,7 +55,7 @@ pub async fn download_and_install<R: Runtime>(
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
plugin_manager.add_plugin_by_dir(&PluginWindowContext::new(&window), &plugin_dir_str).await?;
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &plugin_dir_str).await?;
window.db().upsert_plugin(
&Plugin {

View File

@@ -12,7 +12,7 @@ use crate::events::{
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
GetTemplateFunctionConfigRequest, GetTemplateFunctionConfigResponse,
GetTemplateFunctionSummaryResponse, GetThemesRequest, GetThemesResponse, ImportRequest,
ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext,
ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginContext,
RenderPurpose,
};
use crate::native_template_functions::{template_function_keyring, template_function_secure};
@@ -132,7 +132,7 @@ impl PluginManager {
Ok(_) => {
info!("Plugin runtime client connected!");
plugin_manager
.initialize_all_plugins(&app_handle, &PluginWindowContext::None)
.initialize_all_plugins(&app_handle, &PluginContext::new_empty())
.await
.expect("Failed to reload plugins");
}
@@ -195,19 +195,19 @@ impl PluginManager {
[bundled_plugin_dirs, installed_plugin_dirs].concat()
}
pub async fn uninstall(&self, window_context: &PluginWindowContext, dir: &str) -> Result<()> {
pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
let plugin = self.get_plugin_by_dir(dir).await.ok_or(PluginNotFoundErr(dir.to_string()))?;
self.remove_plugin(window_context, &plugin).await
self.remove_plugin(plugin_context, &plugin).await
}
async fn remove_plugin(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
plugin: &PluginHandle,
) -> Result<()> {
// Terminate the plugin
self.send_to_plugin_and_wait(
window_context,
plugin_context,
plugin,
&InternalEventPayload::TerminateRequest,
)
@@ -223,11 +223,7 @@ impl PluginManager {
Ok(())
}
pub async fn add_plugin_by_dir(
&self,
window_context: &PluginWindowContext,
dir: &str,
) -> Result<()> {
pub async fn add_plugin_by_dir(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
info!("Adding plugin by dir {dir}");
let maybe_tx = self.ws_service.app_to_plugin_events_tx.lock().await;
@@ -244,7 +240,7 @@ impl PluginManager {
let event = timeout(
Duration::from_secs(5),
self.send_to_plugin_and_wait(
window_context,
plugin_context,
&plugin_handle,
&InternalEventPayload::BootRequest(BootRequest {
dir: dir.to_string(),
@@ -268,19 +264,19 @@ impl PluginManager {
pub async fn initialize_all_plugins<R: Runtime>(
&self,
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
) -> Result<()> {
let start = Instant::now();
let candidates = self.list_plugin_dirs(app_handle).await;
for candidate in candidates.clone() {
// First remove the plugin if it exists
if let Some(plugin) = self.get_plugin_by_dir(candidate.dir.as_str()).await {
if let Err(e) = self.remove_plugin(window_context, &plugin).await {
if let Err(e) = self.remove_plugin(plugin_context, &plugin).await {
error!("Failed to remove plugin {} {e:?}", candidate.dir);
continue;
}
}
if let Err(e) = self.add_plugin_by_dir(window_context, candidate.dir.as_str()).await {
if let Err(e) = self.add_plugin_by_dir(plugin_context, candidate.dir.as_str()).await {
warn!("Failed to add plugin {} {e:?}", candidate.dir);
}
}
@@ -320,13 +316,13 @@ impl PluginManager {
source_event: &InternalEvent,
payload: &InternalEventPayload,
) -> Result<()> {
let window_context = source_event.to_owned().window_context;
let plugin_context = source_event.to_owned().context;
let reply_id = Some(source_event.to_owned().id);
let plugin = self
.get_plugin_by_ref_id(source_event.plugin_ref_id.as_str())
.await
.ok_or(PluginNotFoundErr(source_event.plugin_ref_id.to_string()))?;
let event = plugin.build_event_to_send_raw(&window_context, &payload, reply_id);
let event = plugin.build_event_to_send_raw(&plugin_context, &payload, reply_id);
plugin.send(&event).await
}
@@ -350,27 +346,27 @@ impl PluginManager {
async fn send_to_plugin_and_wait(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
plugin: &PluginHandle,
payload: &InternalEventPayload,
) -> Result<InternalEvent> {
let events =
self.send_to_plugins_and_wait(window_context, payload, vec![plugin.to_owned()]).await?;
self.send_to_plugins_and_wait(plugin_context, payload, vec![plugin.to_owned()]).await?;
Ok(events.first().unwrap().to_owned())
}
async fn send_and_wait(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
payload: &InternalEventPayload,
) -> Result<Vec<InternalEvent>> {
let plugins = { self.plugins.lock().await.clone() };
self.send_to_plugins_and_wait(window_context, payload, plugins).await
self.send_to_plugins_and_wait(plugin_context, payload, plugins).await
}
async fn send_to_plugins_and_wait(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
payload: &InternalEventPayload,
plugins: Vec<PluginHandle>,
) -> Result<Vec<InternalEvent>> {
@@ -380,7 +376,7 @@ impl PluginManager {
// 1. Build the events with IDs and everything
let events_to_send = plugins
.iter()
.map(|p| p.build_event_to_send(window_context, payload, None))
.map(|p| p.build_event_to_send(plugin_context, payload, None))
.collect::<Vec<InternalEvent>>();
// 2. Spawn thread to subscribe to incoming events and check reply ids
@@ -433,7 +429,7 @@ impl PluginManager {
) -> Result<Vec<GetThemesResponse>> {
let reply_events = self
.send_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::GetThemesRequest(GetThemesRequest {}),
)
.await?;
@@ -454,7 +450,7 @@ impl PluginManager {
) -> Result<Vec<GetGrpcRequestActionsResponse>> {
let reply_events = self
.send_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::GetGrpcRequestActionsRequest(EmptyPayload {}),
)
.await?;
@@ -475,7 +471,7 @@ impl PluginManager {
) -> Result<Vec<GetHttpRequestActionsResponse>> {
let reply_events = self
.send_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::GetHttpRequestActionsRequest(EmptyPayload {}),
)
.await?;
@@ -520,11 +516,11 @@ impl PluginManager {
Some(v) => v,
};
let window_context = &PluginWindowContext::new(&window);
let plugin_context = &PluginContext::new(&window);
let vars = &make_vars_hashmap(environment_chain);
let cb = PluginTemplateCallback::new(
window.app_handle(),
&window_context,
&plugin_context,
RenderPurpose::Preview,
);
// We don't want to fail for this op because the UI will not be able to list any auth types then
@@ -536,7 +532,7 @@ impl PluginManager {
let event = self
.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&plugin,
&InternalEventPayload::GetTemplateFunctionConfigRequest(
GetTemplateFunctionConfigRequest {
@@ -566,7 +562,7 @@ impl PluginManager {
let plugin =
self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?;
let event = plugin.build_event_to_send(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::CallHttpRequestActionRequest(req),
None,
);
@@ -583,7 +579,7 @@ impl PluginManager {
let plugin =
self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?;
let event = plugin.build_event_to_send(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::CallGrpcRequestActionRequest(req),
None,
);
@@ -595,10 +591,10 @@ impl PluginManager {
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationSummaryResponse)>> {
let window_context = PluginWindowContext::new(window);
let plugin_context = PluginContext::new(window);
let reply_events = self
.send_and_wait(
&window_context,
&plugin_context,
&InternalEventPayload::GetHttpAuthenticationSummaryRequest(EmptyPayload {}),
)
.await?;
@@ -635,7 +631,7 @@ impl PluginManager {
let vars = &make_vars_hashmap(environment_chain);
let cb = PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(&window),
&PluginContext::new(&window),
RenderPurpose::Preview,
);
// We don't want to fail for this op because the UI will not be able to list any auth types then
@@ -646,7 +642,7 @@ impl PluginManager {
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
let event = self
.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&plugin,
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
GetHttpAuthenticationConfigRequest {
@@ -681,7 +677,7 @@ impl PluginManager {
vars,
&PluginTemplateCallback::new(
window.app_handle(),
&PluginWindowContext::new(&window),
&PluginContext::new(&window),
RenderPurpose::Preview,
),
&RenderOptions {
@@ -697,7 +693,7 @@ impl PluginManager {
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
self.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&plugin,
&InternalEventPayload::CallHttpAuthenticationActionRequest(
CallHttpAuthenticationActionRequest {
@@ -719,6 +715,7 @@ impl PluginManager {
window: &WebviewWindow<R>,
auth_name: &str,
req: CallHttpAuthenticationRequest,
plugin_context: &PluginContext,
) -> Result<CallHttpAuthenticationResponse> {
let disabled = match req.values.get("disabled") {
Some(JsonPrimitive::Boolean(v)) => v.clone(),
@@ -742,7 +739,7 @@ impl PluginManager {
let event = self
.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
plugin_context,
&plugin,
&InternalEventPayload::CallHttpAuthenticationRequest(req),
)
@@ -761,10 +758,10 @@ impl PluginManager {
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<GetTemplateFunctionSummaryResponse>> {
let window_context = PluginWindowContext::new(window);
let plugin_context = PluginContext::new(window);
let reply_events = self
.send_and_wait(
&window_context,
&plugin_context,
&InternalEventPayload::GetTemplateFunctionSummaryRequest(EmptyPayload {}),
)
.await?;
@@ -787,7 +784,7 @@ impl PluginManager {
pub async fn call_template_function(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
fn_name: &str,
values: HashMap<String, serde_json::Value>,
purpose: RenderPurpose,
@@ -798,7 +795,7 @@ impl PluginManager {
};
let events = self
.send_and_wait(window_context, &InternalEventPayload::CallTemplateFunctionRequest(req))
.send_and_wait(plugin_context, &InternalEventPayload::CallTemplateFunctionRequest(req))
.await
.map_err(|e| RenderError(format!("Failed to call template function {e:}")))?;
@@ -832,7 +829,7 @@ impl PluginManager {
) -> Result<ImportResponse> {
let reply_events = self
.send_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&InternalEventPayload::ImportRequest(ImportRequest {
content: content.to_string(),
}),
@@ -872,7 +869,7 @@ impl PluginManager {
let event = self
.send_to_plugin_and_wait(
&PluginWindowContext::new(window),
&PluginContext::new(window),
&plugin,
&InternalEventPayload::FilterRequest(FilterRequest {
filter: filter.to_string(),

View File

@@ -1,5 +1,5 @@
use crate::events::{
FormInput, FormInputBase, FormInputText, PluginWindowContext, RenderPurpose, TemplateFunction,
FormInput, FormInputBase, FormInputText, PluginContext, RenderPurpose, TemplateFunction,
TemplateFunctionArg,
};
use crate::template_callback::PluginTemplateCallback;
@@ -65,13 +65,10 @@ pub(crate) fn template_function_keyring() -> TemplateFunction {
pub fn template_function_secure_run<R: Runtime>(
app_handle: &AppHandle<R>,
args: HashMap<String, serde_json::Value>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
) -> Result<String> {
match window_context.clone() {
PluginWindowContext::Label {
workspace_id: Some(wid),
..
} => {
match plugin_context.workspace_id.clone() {
Some(wid) => {
let value = args.get("value").map(|v| v.to_owned()).unwrap_or_default();
let value = match value {
serde_json::Value::String(s) => s,
@@ -97,13 +94,13 @@ pub fn template_function_secure_run<R: Runtime>(
let r = String::from_utf8(r).map_err(|e| RenderError(e.to_string()))?;
Ok(r)
}
_ => Err(RenderError("workspace_id missing from window context".to_string())),
_ => Err(RenderError("workspace_id missing from plugin context".to_string())),
}
}
pub fn template_function_secure_transform_arg<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
arg_name: &str,
arg_value: &str,
) -> Result<String> {
@@ -111,11 +108,8 @@ pub fn template_function_secure_transform_arg<R: Runtime>(
return Ok(arg_value.to_string());
}
match window_context.clone() {
PluginWindowContext::Label {
workspace_id: Some(wid),
..
} => {
match plugin_context.workspace_id.clone() {
Some(wid) => {
if arg_value.is_empty() {
return Ok("".to_string());
}
@@ -132,13 +126,13 @@ pub fn template_function_secure_transform_arg<R: Runtime>(
let r = BASE64_STANDARD.encode(r);
Ok(format!("YENC_{}", r))
}
_ => Err(RenderError("workspace_id missing from window context".to_string())),
_ => Err(RenderError("workspace_id missing from plugin context".to_string())),
}
}
pub fn decrypt_secure_template_function<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
template: &str,
) -> Result<String> {
let mut parsed = Parser::new(template).parse()?;
@@ -159,7 +153,7 @@ pub fn decrypt_secure_template_function<R: Runtime>(
}
}
new_tokens.push(Token::Raw {
text: template_function_secure_run(app_handle, args_map, window_context)?,
text: template_function_secure_run(app_handle, args_map, plugin_context)?,
});
}
t => {
@@ -175,10 +169,10 @@ pub fn decrypt_secure_template_function<R: Runtime>(
pub fn encrypt_secure_template_function<R: Runtime>(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
template: &str,
) -> Result<String> {
let decrypted = decrypt_secure_template_function(&app_handle, window_context, template)?;
let decrypted = decrypt_secure_template_function(&app_handle, plugin_context, template)?;
let tokens = Tokens {
tokens: vec![Token::Tag {
val: Val::Fn {
@@ -193,7 +187,7 @@ pub fn encrypt_secure_template_function<R: Runtime>(
Ok(transform_args(
tokens,
&PluginTemplateCallback::new(app_handle, window_context, RenderPurpose::Preview),
&PluginTemplateCallback::new(app_handle, plugin_context, RenderPurpose::Preview),
)?
.to_string())
}

View File

@@ -1,5 +1,5 @@
use crate::error::Result;
use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext};
use crate::events::{InternalEvent, InternalEventPayload, PluginContext};
use crate::plugin_meta::{PluginMetadata, get_plugin_meta};
use crate::util::gen_id;
use std::path::Path;
@@ -33,16 +33,16 @@ impl PluginHandle {
pub fn build_event_to_send(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
payload: &InternalEventPayload,
reply_id: Option<String>,
) -> InternalEvent {
self.build_event_to_send_raw(window_context, payload, reply_id)
self.build_event_to_send_raw(plugin_context, payload, reply_id)
}
pub(crate) fn build_event_to_send_raw(
&self,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
payload: &InternalEventPayload,
reply_id: Option<String>,
) -> InternalEvent {
@@ -53,7 +53,7 @@ impl PluginHandle {
plugin_name: dir.file_name().unwrap().to_str().unwrap().to_string(),
reply_id,
payload: payload.clone(),
window_context: window_context.clone(),
context: plugin_context.clone(),
}
}

View File

@@ -76,10 +76,11 @@ impl PluginRuntimeServerWebsocket {
return;
}
let event = match serde_json::from_str::<InternalEventRawPayload>(&msg.into_text().unwrap()) {
let msg_text = msg.into_text().unwrap();
let event = match serde_json::from_str::<InternalEventRawPayload>(&msg_text) {
Ok(e) => e,
Err(e) => {
error!("Failed to decode plugin event {e:?}");
error!("Failed to decode plugin event {e:?} -> {msg_text}");
continue;
}
};
@@ -98,7 +99,7 @@ impl PluginRuntimeServerWebsocket {
payload,
plugin_ref_id: event.plugin_ref_id,
plugin_name: event.plugin_name,
window_context: event.window_context,
context: event.context,
reply_id: event.reply_id,
};

View File

@@ -1,4 +1,4 @@
use crate::events::{PluginWindowContext, RenderPurpose};
use crate::events::{PluginContext, RenderPurpose};
use crate::manager::PluginManager;
use crate::native_template_functions::{
template_function_keychain_run, template_function_secure_run,
@@ -13,19 +13,19 @@ use yaak_templates::error::Result;
pub struct PluginTemplateCallback<R: Runtime> {
app_handle: AppHandle<R>,
render_purpose: RenderPurpose,
window_context: PluginWindowContext,
plugin_context: PluginContext,
}
impl<R: Runtime> PluginTemplateCallback<R> {
pub fn new(
app_handle: &AppHandle<R>,
window_context: &PluginWindowContext,
plugin_context: &PluginContext,
render_purpose: RenderPurpose,
) -> PluginTemplateCallback<R> {
PluginTemplateCallback {
render_purpose,
app_handle: app_handle.to_owned(),
window_context: window_context.to_owned(),
plugin_context: plugin_context.to_owned(),
}
}
}
@@ -37,7 +37,7 @@ impl<R: Runtime> TemplateCallback for PluginTemplateCallback<R> {
let fn_name = if fn_name == "Response" { "response" } else { fn_name };
if fn_name == "secure" {
return template_function_secure_run(&self.app_handle, args, &self.window_context);
return template_function_secure_run(&self.app_handle, args, &self.plugin_context);
} else if fn_name == "keychain" || fn_name == "keyring" {
return template_function_keychain_run(args);
}
@@ -45,7 +45,7 @@ impl<R: Runtime> TemplateCallback for PluginTemplateCallback<R> {
let plugin_manager = &*self.app_handle.state::<PluginManager>();
let resp = plugin_manager
.call_template_function(
&self.window_context,
&self.plugin_context,
fn_name,
args,
self.render_purpose.to_owned(),
@@ -58,7 +58,7 @@ impl<R: Runtime> TemplateCallback for PluginTemplateCallback<R> {
if fn_name == "secure" {
return template_function_secure_transform_arg(
&self.app_handle,
&self.window_context,
&self.plugin_context,
arg_name,
arg_value,
);