Show toast on plugin event handling errors instead of crashing

Also set folder context on template render and fix timestamp function
This commit is contained in:
Gregory Schier
2025-10-06 06:53:45 -07:00
parent dbc606fb53
commit 485a9ea47c
18 changed files with 203 additions and 130 deletions

View File

@@ -58,7 +58,7 @@ Built with [Tauri](https://tauri.app), Rust, and React, its fast, lightweight
## Contribution Policy ## Contribution Policy
Yaak is open source, but only accepting contributions for bug fixes. To get started, Yaak is open source but only accepting contributions for bug fixes. To get started,
visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment. visit [`DEVELOPMENT.md`](DEVELOPMENT.md) for tips on setting up your environment.
## Useful Resources ## Useful Resources

View File

@@ -437,7 +437,7 @@ export type SetKeyValueRequest = { key: string, value: string, };
export type SetKeyValueResponse = {}; export type SetKeyValueResponse = {};
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, }; export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
export type TemplateFunction = { name: string, description?: string, export type TemplateFunction = { name: string, description?: string,
/** /**

View File

@@ -1,6 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, variables: Array<EnvironmentVariable>, color: string | null, parentModel: string, parentId: string | null, }; export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };

View File

@@ -454,6 +454,8 @@ export class PluginInstance {
show: async (args) => { show: async (args) => {
await this.#sendAndWaitForReply(windowContext, { await this.#sendAndWaitForReply(windowContext, {
type: 'show_toast_request', type: 'show_toast_request',
// Handle default here because null/undefined both convert to None in Rust translation
timeout: args.timeout === undefined ? 5000 : args.timeout,
...args, ...args,
}); });
}, },

View File

@@ -271,6 +271,12 @@ export const plugin: PluginDefinition = {
label: 'Advanced', label: 'Advanced',
inputs: [ inputs: [
{ type: 'text', name: 'scope', label: 'Scope', optional: true }, { type: 'text', name: 'scope', label: 'Scope', optional: true },
{
type: 'text',
name: 'headerName',
label: 'Header Name',
defaultValue: 'Authorization',
},
{ {
type: 'text', type: 'text',
name: 'headerPrefix', name: 'headerPrefix',
@@ -397,15 +403,9 @@ export const plugin: PluginDefinition = {
throw new Error('Invalid grant type ' + grantType); throw new Error('Invalid grant type ' + grantType);
} }
const headerName = stringArg(values, 'headerName') || 'Authorization';
const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim(); const headerValue = `${headerPrefix} ${token.response[tokenName]}`.trim();
return { return { setHeaders: [{ name: headerName, value: headerValue }] };
setHeaders: [
{
name: 'Authorization',
value: headerValue,
},
],
};
}, },
}, },
}; };

View File

@@ -1,5 +1,6 @@
import type { HttpUrlParameter } from '@yaakapp-internal/models'; import type { HttpUrlParameter } from '@yaakapp-internal/models';
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
import { resolvedModelName } from '@yaakapp/app/lib/resolvedModelName';
export const plugin: PluginDefinition = { export const plugin: PluginDefinition = {
templateFunctions: [ templateFunctions: [
@@ -96,5 +97,22 @@ export const plugin: PluginDefinition = {
return renderedValue; return renderedValue;
}, },
}, },
{
name: 'request.name',
args: [
{
name: 'requestId',
label: 'Http Request',
type: 'http_request',
},
],
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
const requestId = String(args.values.requestId ?? 'n/a');
const httpRequest = await ctx.httpRequest.getById({ id: requestId });
if (httpRequest == null) return null;
return resolvedModelName(httpRequest);
},
},
], ],
}; };

View File

@@ -155,7 +155,7 @@ export function formatDatetime(args: {
format?: string; format?: string;
in?: ContextFn<Date>; in?: ContextFn<Date>;
}): string { }): string {
const { date, format = 'yyyy-MM-dd HH:mm:ss' } = args; const { date, format } = args;
const d = parseDateString(date ?? ''); const d = parseDateString(date ?? '');
return formatDate(d, String(format), { in: args.in }); return formatDate(d, String(format || 'yyyy-MM-dd HH:mm:ss'), { in: args.in });
} }

View File

@@ -35,6 +35,9 @@ pub enum Error {
#[error(transparent)] #[error(transparent)]
CommonError(#[from] yaak_common::error::Error), CommonError(#[from] yaak_common::error::Error),
#[error(transparent)]
ClipboardError(#[from] tauri_plugin_clipboard_manager::Error),
#[error("Updater error: {0}")] #[error("Updater error: {0}")]
UpdaterError(#[from] tauri_plugin_updater::Error), UpdaterError(#[from] tauri_plugin_updater::Error),

View File

@@ -1,6 +1,7 @@
extern crate core; extern crate core;
use crate::encoding::read_response_body; use crate::encoding::read_response_body;
use crate::error::Error::GenericError; use crate::error::Error::GenericError;
use crate::error::Result;
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request}; use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
use crate::http_request::{resolve_http_request, send_http_request}; use crate::http_request::{resolve_http_request, send_http_request};
use crate::import::import_data; use crate::import::import_data;
@@ -49,7 +50,7 @@ use yaak_plugins::plugin_meta::PluginMetadata;
use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent; use yaak_sse::sse::ServerSentEvent;
use yaak_templates::format::format_json; use yaak_templates::format::format_json;
use yaak_templates::{Tokens, transform_args, RenderOptions, RenderErrorBehavior}; use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
mod commands; mod commands;
mod encoding; mod encoding;
@@ -1499,7 +1500,27 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
// We might have recursive back-and-forth calls between app and plugin, so we don't // We might have recursive back-and-forth calls between app and plugin, so we don't
// want to block here // want to block here
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await; let ev = plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
let ev = match ev {
Ok(Some(ev)) => ev,
Ok(None) => return,
Err(e) => {
warn!("Failed to handle plugin event: {e:?}");
let _ = app_handle.emit("show_toast", InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: e.to_string(),
color: Some(Color::Danger),
icon: None,
timeout: Some(30000),
}));
return;
},
};
let plugin_manager: State<'_, PluginManager> = app_handle.state();
if let Err(e) = plugin_manager.reply(&event, &ev).await {
warn!("Failed to reply to plugin manager: {:?}", e)
}
}); });
} }
plugin_manager.unsubscribe(rx_id.as_str()).await; plugin_manager.unsubscribe(rx_id.as_str()).await;
@@ -1534,11 +1555,16 @@ async fn call_frontend<R: Runtime>(
fn get_window_from_window_context<R: Runtime>( fn get_window_from_window_context<R: Runtime>(
app_handle: &AppHandle<R>, app_handle: &AppHandle<R>,
window_context: &PluginWindowContext, window_context: &PluginWindowContext,
) -> Option<WebviewWindow<R>> { ) -> Result<WebviewWindow<R>> {
let label = match window_context { let label = match window_context {
PluginWindowContext::Label { label, .. } => label, PluginWindowContext::Label { label, .. } => label,
PluginWindowContext::None => { PluginWindowContext::None => {
return app_handle.webview_windows().iter().next().map(|(_, w)| w.to_owned()); return app_handle
.webview_windows()
.iter()
.next()
.map(|(_, w)| w.to_owned())
.ok_or(GenericError("No windows open".to_string()));
} }
}; };
@@ -1551,7 +1577,7 @@ fn get_window_from_window_context<R: Runtime>(
error!("Failed to find window by {window_context:?}"); error!("Failed to find window by {window_context:?}");
} }
window Ok(window.ok_or(GenericError(format!("Failed to find window for {}", label)))?)
} }
fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> { fn workspace_from_window<R: Runtime>(window: &WebviewWindow<R>) -> Option<Workspace> {

View File

@@ -1,3 +1,4 @@
use crate::error::Result;
use crate::http_request::send_http_request; use crate::http_request::send_http_request;
use crate::render::{render_grpc_request, render_http_request, render_json_value}; use crate::render::{render_grpc_request, render_http_request, render_json_value};
use crate::window::{CreateWindowConfig, create_window}; use crate::window::{CreateWindowConfig, create_window};
@@ -7,10 +8,12 @@ use crate::{
}; };
use chrono::Utc; use chrono::Utc;
use cookie::Cookie; use cookie::Cookie;
use log::{error, warn}; use log::error;
use tauri::{AppHandle, Emitter, Manager, Runtime, State}; use tauri::{AppHandle, Emitter, Manager, Runtime};
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
use yaak_common::window::WorkspaceWindowTrait;
use yaak_models::models::{HttpResponse, Plugin}; use yaak_models::models::{HttpResponse, Plugin};
use yaak_models::queries::any_request::AnyRequest;
use yaak_models::query_manager::QueryManagerExt; use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::UpdateSource; use yaak_models::util::UpdateSource;
use yaak_plugins::events::{ use yaak_plugins::events::{
@@ -20,7 +23,6 @@ use yaak_plugins::events::{
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
TemplateRenderResponse, WindowNavigateEvent, TemplateRenderResponse, WindowNavigateEvent,
}; };
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle; use yaak_plugins::plugin_handle::PluginHandle;
use yaak_plugins::template_callback::PluginTemplateCallback; use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_templates::{RenderErrorBehavior, RenderOptions}; use yaak_templates::{RenderErrorBehavior, RenderOptions};
@@ -29,109 +31,111 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
app_handle: &AppHandle<R>, app_handle: &AppHandle<R>,
event: &InternalEvent, event: &InternalEvent,
plugin_handle: &PluginHandle, plugin_handle: &PluginHandle,
) { ) -> Result<Option<InternalEventPayload>> {
// debug!("Got event to app {event:?}"); // debug!("Got event to app {event:?}");
let window_context = event.window_context.to_owned(); let window_context = event.window_context.to_owned();
let response_event: Option<InternalEventPayload> = match event.clone().payload { match event.clone().payload {
InternalEventPayload::CopyTextRequest(req) => { InternalEventPayload::CopyTextRequest(req) => {
app_handle app_handle.clipboard().write_text(req.text.as_str())?;
.clipboard() Ok(Some(InternalEventPayload::CopyTextResponse(EmptyPayload {})))
.write_text(req.text.as_str())
.expect("Failed to write text to clipboard");
Some(InternalEventPayload::CopyTextResponse(EmptyPayload {}))
} }
InternalEventPayload::ShowToastRequest(req) => { InternalEventPayload::ShowToastRequest(req) => {
match window_context { match window_context {
PluginWindowContext::Label { label, .. } => app_handle PluginWindowContext::Label { label, .. } => {
.emit_to(label, "show_toast", req) app_handle.emit_to(label, "show_toast", req)?
.expect("Failed to emit show_toast to window"), }
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"), _ => app_handle.emit("show_toast", req)?,
}; };
Some(InternalEventPayload::ShowToastResponse(EmptyPayload {})) Ok(Some(InternalEventPayload::ShowToastResponse(EmptyPayload {})))
} }
InternalEventPayload::PromptTextRequest(_) => { InternalEventPayload::PromptTextRequest(_) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for render"); Ok(call_frontend(&window, event).await)
call_frontend(&window, event).await
} }
InternalEventPayload::FindHttpResponsesRequest(req) => { InternalEventPayload::FindHttpResponsesRequest(req) => {
let http_responses = app_handle let http_responses = app_handle
.db() .db()
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64)) .list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
.unwrap_or_default(); .unwrap_or_default();
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse { Ok(Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
http_responses, http_responses,
})) })))
} }
InternalEventPayload::GetHttpRequestByIdRequest(req) => { InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = app_handle.db().get_http_request(&req.id).ok(); let http_request = app_handle.db().get_http_request(&req.id).ok();
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse { Ok(Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
http_request, http_request,
})) })))
} }
InternalEventPayload::RenderGrpcRequestRequest(req) => { InternalEventPayload::RenderGrpcRequestRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for render grpc request");
let workspace = let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL"); workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment_id = environment_from_window(&window).map(|e| e.id); let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window let environment_chain = window.db().resolve_environments(
.db() &workspace.id,
.resolve_environments(&workspace.id, None, environment_id.as_deref()) req.grpc_request.folder_id.as_deref(),
.expect("Failed to resolve environments"); environment_id.as_deref(),
)?;
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let opt = RenderOptions { let opt = RenderOptions {
error_behavior: RenderErrorBehavior::Throw, error_behavior: RenderErrorBehavior::Throw,
}; };
let grpc_request = render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt) let grpc_request =
.await render_grpc_request(&req.grpc_request, environment_chain, &cb, &opt).await?;
.expect("Failed to render grpc request"); Ok(Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse {
grpc_request, grpc_request,
})) })))
} }
InternalEventPayload::RenderHttpRequestRequest(req) => { InternalEventPayload::RenderHttpRequestRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for render http request");
let workspace = let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL"); workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment_id = environment_from_window(&window).map(|e| e.id); let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window let environment_chain = window.db().resolve_environments(
.db() &workspace.id,
.resolve_environments(&workspace.id, None, environment_id.as_deref()) req.http_request.folder_id.as_deref(),
.expect("Failed to resolve environments"); environment_id.as_deref(),
)?;
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let opt = &RenderOptions { let opt = &RenderOptions {
error_behavior: RenderErrorBehavior::Throw, error_behavior: RenderErrorBehavior::Throw,
}; };
let http_request = render_http_request(&req.http_request, environment_chain, &cb, &opt) let http_request =
.await render_http_request(&req.http_request, environment_chain, &cb, &opt).await?;
.expect("Failed to render http request"); Ok(Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
http_request, http_request,
})) })))
} }
InternalEventPayload::TemplateRenderRequest(req) => { InternalEventPayload::TemplateRenderRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for render");
let workspace = let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL"); workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
let environment_id = environment_from_window(&window).map(|e| e.id); let environment_id = environment_from_window(&window).map(|e| e.id);
let environment_chain = window let folder_id = if let Some(id) = window.request_id() {
.db() match window.db().get_any_request(&id) {
.resolve_environments(&workspace.id, None, environment_id.as_deref()) Ok(AnyRequest::HttpRequest(r)) => r.folder_id,
.expect("Failed to resolve environments"); Ok(AnyRequest::GrpcRequest(r)) => r.folder_id,
Ok(AnyRequest::WebsocketRequest(r)) => r.folder_id,
Err(_) => None,
}
} else {
None
};
let environment_chain = window.db().resolve_environments(
&workspace.id,
folder_id.as_deref(),
environment_id.as_deref(),
)?;
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
let opt = RenderOptions { let opt = RenderOptions {
error_behavior: RenderErrorBehavior::Throw, error_behavior: RenderErrorBehavior::Throw,
}; };
let data = render_json_value(req.data, environment_chain, &cb, &opt) let data = render_json_value(req.data, environment_chain, &cb, &opt).await?;
.await Ok(Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })))
.expect("Failed to render template");
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
} }
InternalEventPayload::ErrorResponse(resp) => { InternalEventPayload::ErrorResponse(resp) => {
error!("Plugin error: {}: {:?}", resp.error, resp); error!("Plugin error: {}: {:?}", resp.error, resp);
@@ -144,16 +148,15 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
resp.error resp.error
), ),
color: Some(Color::Danger), color: Some(Color::Danger),
timeout: None, timeout: Some(30000),
..Default::default() ..Default::default()
}), }),
None, None,
); );
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
None
} }
InternalEventPayload::ReloadResponse(req) => { InternalEventPayload::ReloadResponse(req) => {
let plugins = app_handle.db().list_plugins().unwrap(); let plugins = app_handle.db().list_plugins()?;
for plugin in plugins { for plugin in plugins {
if plugin.directory != plugin_handle.dir { if plugin.directory != plugin_handle.dir {
continue; continue;
@@ -163,7 +166,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
..plugin ..plugin
}; };
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap(); app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin)?;
} }
if !req.silent { if !req.silent {
@@ -178,13 +181,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}), }),
None, None,
); );
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await
} else {
Ok(None)
} }
None
} }
InternalEventPayload::SendHttpRequestRequest(req) => { InternalEventPayload::SendHttpRequestRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for sending HTTP request");
let mut http_request = req.http_request; let mut http_request = req.http_request;
let workspace = let workspace =
workspace_from_window(&window).expect("Failed to get workspace_id from window URL"); workspace_from_window(&window).expect("Failed to get workspace_id from window URL");
@@ -198,20 +201,17 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
let http_response = if http_request.id.is_empty() { let http_response = if http_request.id.is_empty() {
HttpResponse::default() HttpResponse::default()
} else { } else {
window window.db().upsert_http_response(
.db() &HttpResponse {
.upsert_http_response( request_id: http_request.id.clone(),
&HttpResponse { workspace_id: http_request.workspace_id.clone(),
request_id: http_request.id.clone(), ..Default::default()
workspace_id: http_request.workspace_id.clone(), },
..Default::default() &UpdateSource::Plugin,
}, )?
&UpdateSource::Plugin,
)
.unwrap()
}; };
let result = send_http_request( let http_response = send_http_request(
&window, &window,
&http_request, &http_request,
&http_response, &http_response,
@@ -219,16 +219,11 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
cookie_jar, cookie_jar,
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel &mut tokio::sync::watch::channel(false).1, // No-op cancel channel
) )
.await; .await?;
let http_response = match result { Ok(Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
Ok(r) => r,
Err(_e) => return,
};
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
http_response, http_response,
})) })))
} }
InternalEventPayload::OpenWindowRequest(req) => { InternalEventPayload::OpenWindowRequest(req) => {
let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128); let (navigation_tx, mut navigation_rx) = tokio::sync::mpsc::channel(128);
@@ -251,8 +246,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}), }),
None, None,
); );
Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle)).await; return Box::pin(handle_plugin_event(app_handle, &error_event, plugin_handle))
return; .await;
} }
{ {
@@ -288,32 +283,33 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}); });
} }
None Ok(None)
} }
InternalEventPayload::CloseWindowRequest(req) => { InternalEventPayload::CloseWindowRequest(req) => {
if let Some(window) = app_handle.webview_windows().get(&req.label) { if let Some(window) = app_handle.webview_windows().get(&req.label) {
window.close().expect("Failed to close window"); window.close()?;
} }
None Ok(None)
} }
InternalEventPayload::SetKeyValueRequest(req) => { InternalEventPayload::SetKeyValueRequest(req) => {
let name = plugin_handle.info().name; let name = plugin_handle.info().name;
app_handle.db().set_plugin_key_value(&name, &req.key, &req.value); app_handle.db().set_plugin_key_value(&name, &req.key, &req.value);
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})) Ok(Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {})))
} }
InternalEventPayload::GetKeyValueRequest(req) => { InternalEventPayload::GetKeyValueRequest(req) => {
let name = plugin_handle.info().name; let name = plugin_handle.info().name;
let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value); let value = app_handle.db().get_plugin_key_value(&name, &req.key).map(|v| v.value);
Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value })) Ok(Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value })))
} }
InternalEventPayload::DeleteKeyValueRequest(req) => { InternalEventPayload::DeleteKeyValueRequest(req) => {
let name = plugin_handle.info().name; let name = plugin_handle.info().name;
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap(); let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key)?;
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted })) Ok(Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse {
deleted,
})))
} }
InternalEventPayload::ListCookieNamesRequest(_req) => { InternalEventPayload::ListCookieNamesRequest(_req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for listing cookies");
let names = match cookie_jar_from_window(&window) { let names = match cookie_jar_from_window(&window) {
None => Vec::new(), None => Vec::new(),
Some(j) => j Some(j) => j
@@ -322,11 +318,12 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string())) .filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
.collect(), .collect(),
}; };
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse { names })) Ok(Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse {
names,
})))
} }
InternalEventPayload::GetCookieValueRequest(req) => { InternalEventPayload::GetCookieValueRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)?;
.expect("Failed to find window for listing cookies");
let value = match cookie_jar_from_window(&window) { let value = match cookie_jar_from_window(&window) {
None => None, None => None,
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) { Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
@@ -336,15 +333,8 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
_ => None, _ => None,
}), }),
}; };
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })) Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
}
_ => None,
};
if let Some(e) = response_event {
let plugin_manager: State<'_, PluginManager> = app_handle.state();
if let Err(e) = plugin_manager.reply(&event, &e).await {
warn!("Failed to reply to plugin manager: {:?}", e)
} }
_ => Ok(None),
} }
} }

View File

@@ -5,6 +5,7 @@ pub trait WorkspaceWindowTrait {
fn workspace_id(&self) -> Option<String>; fn workspace_id(&self) -> Option<String>;
fn cookie_jar_id(&self) -> Option<String>; fn cookie_jar_id(&self) -> Option<String>;
fn environment_id(&self) -> Option<String>; fn environment_id(&self) -> Option<String>;
fn request_id(&self) -> Option<String>;
} }
impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> { impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
@@ -28,4 +29,10 @@ impl<R: Runtime> WorkspaceWindowTrait for WebviewWindow<R> {
let mut query_pairs = url.query_pairs(); let mut query_pairs = url.query_pairs();
query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string()) query_pairs.find(|(k, _v)| k == "environment_id").map(|(_k, v)| v.to_string())
} }
fn request_id(&self) -> Option<String> {
let url = self.url().unwrap();
let mut query_pairs = url.query_pairs();
query_pairs.find(|(k, _v)| k == "request_id").map(|(_k, v)| v.to_string())
}
} }

View File

@@ -0,0 +1,23 @@
use crate::db_context::DbContext;
use crate::error::Result;
use crate::models::{
GrpcRequest, HttpRequest, WebsocketRequest,
};
pub enum AnyRequest {
HttpRequest(HttpRequest),
GrpcRequest(GrpcRequest),
WebsocketRequest(WebsocketRequest),
}
impl<'a> DbContext<'a> {
pub fn get_any_request(&self, id: &str) -> Result<AnyRequest> {
if let Ok(http_request) = self.get_http_request(id) {
Ok(AnyRequest::HttpRequest(http_request))
} else if let Ok(grpc_request) = self.get_grpc_request(id) {
Ok(AnyRequest::GrpcRequest(grpc_request))
} else {
Ok(AnyRequest::WebsocketRequest(self.get_websocket_request(id)?))
}
}
}

View File

@@ -1,3 +1,4 @@
pub mod any_request;
mod batch; mod batch;
mod cookie_jars; mod cookie_jars;
mod environments; mod environments;

View File

@@ -123,6 +123,9 @@ pub enum InternalEventPayload {
RenderGrpcRequestRequest(RenderGrpcRequestRequest), RenderGrpcRequestRequest(RenderGrpcRequestRequest),
RenderGrpcRequestResponse(RenderGrpcRequestResponse), RenderGrpcRequestResponse(RenderGrpcRequestResponse),
TemplateRenderRequest(TemplateRenderRequest),
TemplateRenderResponse(TemplateRenderResponse),
GetKeyValueRequest(GetKeyValueRequest), GetKeyValueRequest(GetKeyValueRequest),
GetKeyValueResponse(GetKeyValueResponse), GetKeyValueResponse(GetKeyValueResponse),
SetKeyValueRequest(SetKeyValueRequest), SetKeyValueRequest(SetKeyValueRequest),
@@ -135,9 +138,6 @@ pub enum InternalEventPayload {
WindowCloseEvent, WindowCloseEvent,
CloseWindowRequest(CloseWindowRequest), CloseWindowRequest(CloseWindowRequest),
TemplateRenderRequest(TemplateRenderRequest),
TemplateRenderResponse(TemplateRenderResponse),
ShowToastRequest(ShowToastRequest), ShowToastRequest(ShowToastRequest),
ShowToastResponse(EmptyPayload), ShowToastResponse(EmptyPayload),

View File

@@ -67,7 +67,7 @@ export function HttpAuthenticationEditor({ model }: Props) {
</EmptyStateText> </EmptyStateText>
); );
} else { } else {
return <EmptyStateText>Authentication not configured</EmptyStateText>; return <EmptyStateText>No authentication</EmptyStateText>;
} }
} }

View File

@@ -351,7 +351,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
label="Request" label="Request"
onChangeValue={setActiveTab} onChangeValue={setActiveTab}
tabs={tabs} tabs={tabs}
tabListClassName="mt-1 !mb-1.5" tabListClassName="mt-1 mb-1.5"
> >
<TabContent value={TAB_AUTH}> <TabContent value={TAB_AUTH}>
<HttpAuthenticationEditor model={activeRequest} /> <HttpAuthenticationEditor model={activeRequest} />

View File

@@ -84,7 +84,8 @@ export function Tabs({
className={classNames( className={classNames(
tabListClassName, tabListClassName,
addBorders && '!-ml-1', addBorders && '!-ml-1',
'flex items-center hide-scrollbars mb-2', addBorders && layout === 'vertical' && 'mb-2',
'flex items-center hide-scrollbars',
layout === 'horizontal' && 'h-full overflow-auto p-2 -mr-2', layout === 'horizontal' && 'h-full overflow-auto p-2 -mr-2',
layout === 'vertical' && 'overflow-x-auto overflow-y-visible ', layout === 'vertical' && 'overflow-x-auto overflow-y-visible ',
// Give space for button focus states within overflow boundary. // Give space for button focus states within overflow boundary.

View File

@@ -21,6 +21,8 @@ export function languageFromContentType(
} else if (justContentType.includes('javascript')) { } else if (justContentType.includes('javascript')) {
// Sometimes `application/javascript` returns JSON, so try detecting that // Sometimes `application/javascript` returns JSON, so try detecting that
return detectFromContent(content, 'javascript'); return detectFromContent(content, 'javascript');
} else if (justContentType.includes('markdown')) {
return 'markdown';
} }
return detectFromContent(content, 'text'); return detectFromContent(content, 'text');