Start on plugin ctx API (#64)

This commit is contained in:
Gregory Schier
2024-08-14 06:42:54 -07:00
committed by GitHub
parent e47a2c5fab
commit 12f4c2c668
106 changed files with 1086 additions and 1219 deletions

66
src-tauri/Cargo.lock generated
View File

@@ -2052,30 +2052,6 @@ dependencies = [
"system-deps",
]
[[package]]
name = "grpc"
version = "0.1.0"
dependencies = [
"anyhow",
"dunce",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
"log",
"md5",
"prost 0.12.6",
"prost-reflect",
"prost-types 0.12.6",
"serde",
"serde_json",
"tauri",
"tauri-plugin-shell",
"tokio",
"tokio-stream",
"tonic 0.10.2",
"tonic-reflection",
"uuid",
]
[[package]]
name = "gtk"
version = "0.18.1"
@@ -6125,13 +6101,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "templates"
version = "0.1.0"
dependencies = [
"log",
]
[[package]]
name = "tendril"
version = "0.4.3"
@@ -7573,7 +7542,6 @@ dependencies = [
"chrono",
"cocoa",
"datetime",
"grpc",
"hex_color",
"http 1.1.0",
"log",
@@ -7597,13 +7565,38 @@ dependencies = [
"tauri-plugin-shell",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"templates",
"thiserror",
"tokio",
"tokio-stream",
"uuid",
"yaak_grpc",
"yaak_models",
"yaak_plugin_runtime",
"yaak_templates",
]
[[package]]
name = "yaak_grpc"
version = "0.1.0"
dependencies = [
"anyhow",
"dunce",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
"log",
"md5",
"prost 0.12.6",
"prost-reflect",
"prost-types 0.12.6",
"serde",
"serde_json",
"tauri",
"tauri-plugin-shell",
"tokio",
"tokio-stream",
"tonic 0.10.2",
"tonic-reflection",
"uuid",
]
[[package]]
@@ -7651,6 +7644,13 @@ dependencies = [
"yaak_models",
]
[[package]]
name = "yaak_templates"
version = "0.1.0"
dependencies = [
"log",
]
[[package]]
name = "zbus"
version = "4.0.1"

View File

@@ -1,5 +1,5 @@
[workspace]
members = ["grpc", "templates", "yaak_plugin_runtime", "yaak_models"]
members = ["yaak_grpc", "yaak_templates", "yaak_plugin_runtime", "yaak_models"]
[package]
@@ -26,8 +26,8 @@ cocoa = "0.25.0"
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
[dependencies]
grpc = { path = "./grpc" }
templates = { path = "./templates" }
yaak_grpc = { path = "yaak_grpc" }
yaak_templates = { path = "yaak_templates" }
yaak_plugin_runtime = { path = "yaak_plugin_runtime" }
anyhow = "1.0.86"
base64 = "0.22.0"
@@ -43,7 +43,7 @@ reqwest_cookie_store = "0.8.0"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
serde_yaml = "0.9.34"
tauri = { workspace = true }
tauri = { workspace = true, features = ["unstable"] }
tauri-plugin-shell = { workspace = true }
tauri-plugin-clipboard-manager = "2.0.0-rc.0"
tauri-plugin-dialog = "2.0.0-rc.0"

View File

@@ -0,0 +1,2 @@
ALTER TABLE environments DROP COLUMN model;
ALTER TABLE environments ADD COLUMN model TEXT DEFAULT 'environment';

View File

@@ -3,9 +3,11 @@ use std::fmt::Display;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::{AppHandle, Manager};
use tauri::{Manager, Runtime, WebviewWindow};
use yaak_models::queries::{generate_id, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string};
use yaak_models::queries::{
generate_id, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string,
};
use crate::is_dev;
@@ -36,7 +38,7 @@ pub enum AnalyticsResource {
impl AnalyticsResource {
pub fn from_str(s: &str) -> serde_json::Result<AnalyticsResource> {
return serde_json::from_str(format!("\"{s}\"").as_str());
serde_json::from_str(format!("\"{s}\"").as_str())
}
}
@@ -74,7 +76,7 @@ pub enum AnalyticsAction {
impl AnalyticsAction {
pub fn from_str(s: &str) -> serde_json::Result<AnalyticsAction> {
return serde_json::from_str(format!("\"{s}\"").as_str());
serde_json::from_str(format!("\"{s}\"").as_str())
}
}
@@ -96,19 +98,18 @@ pub struct LaunchEventInfo {
pub num_launches: i32,
}
pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
pub async fn track_launch_event<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEventInfo {
let last_tracked_version_key = "last_tracked_version";
let mut info = LaunchEventInfo::default();
info.num_launches = get_num_launches(app).await + 1;
info.previous_version =
get_key_value_string(app, NAMESPACE, last_tracked_version_key, "").await;
info.current_version = app.package_info().version.to_string();
info.num_launches = get_num_launches(w).await + 1;
info.previous_version = get_key_value_string(w, NAMESPACE, last_tracked_version_key, "").await;
info.current_version = w.package_info().version.to_string();
if info.previous_version.is_empty() {
track_event(
app,
w,
AnalyticsResource::App,
AnalyticsAction::LaunchFirst,
None,
@@ -118,7 +119,7 @@ pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
info.launched_after_update = info.current_version != info.previous_version;
if info.launched_after_update {
track_event(
app,
w,
AnalyticsResource::App,
AnalyticsAction::LaunchUpdate,
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
@@ -129,7 +130,7 @@ pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
// Track a launch event in all cases
track_event(
app,
w,
AnalyticsResource::App,
AnalyticsAction::Launch,
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
@@ -139,27 +140,27 @@ pub async fn track_launch_event(app: &AppHandle) -> LaunchEventInfo {
// Update key values
set_key_value_string(
app,
w,
NAMESPACE,
last_tracked_version_key,
info.current_version.as_str(),
)
.await;
set_key_value_int(app, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches).await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches).await;
info
}
pub async fn track_event(
app_handle: &AppHandle,
pub async fn track_event<R: Runtime>(
w: &WebviewWindow<R>,
resource: AnalyticsResource,
action: AnalyticsAction,
attributes: Option<Value>,
) {
let id = get_id(app_handle).await;
let id = get_id(w).await;
let event = format!("{}.{}", resource, action);
let attributes_json = attributes.unwrap_or("{}".to_string().into()).to_string();
let info = app_handle.package_info();
let info = w.app_handle().package_info();
let tz = datetime::sys_timezone().unwrap_or("unknown".to_string());
let site = match is_dev() {
true => "site_TkHWjoXwZPq3HfhERb",
@@ -177,7 +178,7 @@ pub async fn track_event(
("v", info.version.clone().to_string()),
("os", get_os().to_string()),
("tz", tz),
("xy", get_window_size(app_handle)),
("xy", get_window_size(w)),
];
let req = reqwest::Client::builder()
.build()
@@ -208,13 +209,8 @@ fn get_os() -> &'static str {
}
}
fn get_window_size(app_handle: &AppHandle) -> String {
let window = match app_handle.webview_windows().into_values().next() {
Some(w) => w,
None => return "unknown".to_string(),
};
let current_monitor = match window.current_monitor() {
fn get_window_size<R: Runtime>(w: &WebviewWindow<R>) -> String {
let current_monitor = match w.current_monitor() {
Ok(Some(m)) => m,
_ => return "unknown".to_string(),
};
@@ -231,17 +227,17 @@ fn get_window_size(app_handle: &AppHandle) -> String {
)
}
async fn get_id(app_handle: &AppHandle) -> String {
let id = get_key_value_string(app_handle, "analytics", "id", "").await;
async fn get_id<R: Runtime>(w: &WebviewWindow<R>) -> String {
let id = get_key_value_string(w, "analytics", "id", "").await;
if id.is_empty() {
let new_id = generate_id();
set_key_value_string(app_handle, "analytics", "id", new_id.as_str()).await;
set_key_value_string(w, "analytics", "id", new_id.as_str()).await;
new_id
} else {
id
}
}
pub async fn get_num_launches(app: &AppHandle) -> i32 {
get_key_value_int(app, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
pub async fn get_num_launches<R: Runtime>(w: &WebviewWindow<R>) -> i32 {
get_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
}

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use KeyAndValueRef::{Ascii, Binary};
use grpc::{KeyAndValueRef, MetadataMap};
use yaak_grpc::{KeyAndValueRef, MetadataMap};
pub fn metadata_to_map(metadata: MetadataMap) -> HashMap<String, String> {
let mut entries = HashMap::new();

View File

@@ -11,24 +11,25 @@ use crate::{render, response_err};
use base64::Engine;
use http::header::{ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderName, HeaderValue};
use log::{error, info, warn};
use log::{error, warn};
use mime_guess::Mime;
use reqwest::redirect::Policy;
use reqwest::Method;
use reqwest::{multipart, Url};
use tauri::{Manager, WebviewWindow};
use tauri::{Manager, Runtime, WebviewWindow};
use tokio::sync::oneshot;
use tokio::sync::watch::Receiver;
use yaak_models::models::{Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader};
use yaak_models::models::{
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
};
use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_jar};
pub async fn send_http_request(
window: &WebviewWindow,
pub async fn send_http_request<R: Runtime>(
window: &WebviewWindow<R>,
request: HttpRequest,
response: &HttpResponse,
environment: Option<Environment>,
cookie_jar: Option<CookieJar>,
download_path: Option<PathBuf>,
cancel_rx: &mut Receiver<bool>,
) -> Result<HttpResponse, String> {
let environment_ref = environment.as_ref();
@@ -442,16 +443,6 @@ pub async fn send_http_request(
.await
.expect("Failed to update response");
// Copy response to the download path, if specified
match (download_path, response.body_path.clone()) {
(Some(dl_path), Some(body_path)) => {
info!("Downloading response body to {}", dl_path.display());
fs::copy(body_path, dl_path)
.expect("Failed to copy file for response download");
}
_ => {}
};
// Add cookie store if specified
if let Some((cookie_store, mut cookie_jar)) = maybe_cookie_manager {
// let cookies = response_headers.get_all(SET_COOKIE).iter().map(|h| {
@@ -466,7 +457,8 @@ pub async fn send_http_request(
.unwrap()
.iter_any()
.map(|c| {
let json_cookie = serde_json::to_value(&c).expect("Failed to serialize cookie");
let json_cookie =
serde_json::to_value(&c).expect("Failed to serialize cookie");
serde_json::from_value(json_cookie).expect("Failed to deserialize cookie")
})
.collect::<Vec<_>>();

View File

@@ -16,17 +16,17 @@ use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
use rand::random;
use serde_json::{json, Value};
use tauri::Listener;
#[cfg(target_os = "macos")]
use tauri::TitleBarStyle;
use tauri::{AppHandle, Emitter, LogicalSize, RunEvent, State, WebviewUrl, WebviewWindow};
use tauri::{Listener, Runtime};
use tauri::{Manager, WindowEvent};
use tauri_plugin_log::{fern, Target, TargetKind};
use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex;
use tokio::sync::{watch, Mutex};
use ::grpc::manager::{DynamicMessage, GrpcHandle};
use ::grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
use yaak_plugin_runtime::manager::PluginManager;
use crate::analytics::{AnalyticsAction, AnalyticsResource};
@@ -42,7 +42,7 @@ use yaak_models::models::{
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Settings, Workspace,
};
use yaak_models::queries::{
cancel_pending_grpc_connections, cancel_pending_responses, create_http_response,
cancel_pending_grpc_connections, cancel_pending_responses, create_default_http_response,
delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar, delete_environment,
delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request,
delete_http_response, delete_workspace, duplicate_grpc_request, duplicate_http_request,
@@ -54,7 +54,10 @@ use yaak_models::queries::{
upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_workspace,
};
use yaak_plugin_runtime::events::FilterResponse;
use yaak_plugin_runtime::events::{
FilterResponse, GetHttpRequestByIdResponse, InternalEvent, InternalEventPayload,
SendHttpRequestResponse,
};
mod analytics;
mod export_resources;
@@ -96,11 +99,15 @@ async fn cmd_metadata(app_handle: AppHandle) -> Result<AppMetaData, ()> {
#[tauri::command]
async fn cmd_dismiss_notification(
app: AppHandle,
window: WebviewWindow,
notification_id: &str,
yaak_notifier: State<'_, Mutex<YaakNotifier>>,
) -> Result<(), String> {
yaak_notifier.lock().await.seen(&app, notification_id).await
yaak_notifier
.lock()
.await
.seen(&window, notification_id)
.await
}
#[tauri::command]
@@ -135,17 +142,21 @@ async fn cmd_grpc_go(
request_id: &str,
environment_id: Option<&str>,
proto_files: Vec<String>,
w: WebviewWindow,
window: WebviewWindow,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> {
let req = get_grpc_request(&w, request_id)
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?;
let environment = match environment_id {
Some(id) => Some(get_environment(&w, id).await.map_err(|e| e.to_string())?),
Some(id) => Some(
get_environment(&window, id)
.await
.map_err(|e| e.to_string())?,
),
None => None,
};
let workspace = get_workspace(&w, &req.workspace_id)
let workspace = get_workspace(&window, &req.workspace_id)
.await
.map_err(|e| e.to_string())?;
let mut metadata = HashMap::new();
@@ -199,7 +210,7 @@ async fn cmd_grpc_go(
let conn = {
let req = req.clone();
upsert_grpc_connection(
&w,
&window,
&GrpcConnection {
workspace_id: req.workspace_id,
request_id: req.id,
@@ -256,7 +267,7 @@ async fn cmd_grpc_go(
Ok(c) => c,
Err(err) => {
upsert_grpc_connection(
&w,
&window,
&GrpcConnection {
elapsed: start.elapsed().as_millis() as i32,
error: Some(err.clone()),
@@ -282,7 +293,7 @@ async fn cmd_grpc_go(
let cb = {
let cancelled_rx = cancelled_rx.clone();
let w = w.clone();
let w = window.clone();
let base_msg = base_msg.clone();
let method_desc = method_desc.clone();
let vars = vars.clone();
@@ -355,10 +366,10 @@ async fn cmd_grpc_go(
}
}
};
let event_handler = w.listen_any(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
let event_handler = window.listen_any(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
let grpc_listen = {
let w = w.clone();
let w = window.clone();
let base_event = base_msg.clone();
let req = req.clone();
let vars = vars.clone();
@@ -603,7 +614,7 @@ async fn cmd_grpc_go(
{
let conn_id = conn_id.clone();
tauri::async_runtime::spawn(async move {
let w = w.clone();
let w = window.clone();
tokio::select! {
_ = grpc_listen => {
let events = list_grpc_events(&w, &conn_id)
@@ -691,7 +702,6 @@ async fn cmd_send_ephemeral_request(
&response,
environment,
cookie_jar,
None,
&mut cancel_rx,
)
.await
@@ -701,7 +711,7 @@ async fn cmd_send_ephemeral_request(
async fn cmd_filter_response(
w: WebviewWindow,
response_id: &str,
plugin_manager: State<'_, Mutex<PluginManager>>,
plugin_manager: State<'_, PluginManager>,
filter: &str,
) -> Result<FilterResponse, String> {
let response = get_http_response(&w, response_id)
@@ -724,8 +734,6 @@ async fn cmd_filter_response(
// TODO: Have plugins register their own content type (regex?)
plugin_manager
.lock()
.await
.run_filter(filter, &body, &content_type)
.await
.map_err(|e| e.to_string())
@@ -734,15 +742,13 @@ async fn cmd_filter_response(
#[tauri::command]
async fn cmd_import_data(
w: WebviewWindow,
plugin_manager: State<'_, Mutex<PluginManager>>,
plugin_manager: State<'_, PluginManager>,
file_path: &str,
) -> Result<WorkspaceExportResources, String> {
let file =
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
let (import_result, plugin_name) = plugin_manager
.lock()
.await
.run_import(file_contents)
.await
.map_err(|e| e.to_string())?;
@@ -853,7 +859,7 @@ async fn cmd_import_data(
);
analytics::track_event(
&w.app_handle(),
&w,
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
@@ -867,7 +873,7 @@ async fn cmd_import_data(
async fn cmd_request_to_curl(
app: AppHandle,
request_id: &str,
plugin_manager: State<'_, Mutex<PluginManager>>,
plugin_manager: State<'_, PluginManager>,
environment_id: Option<&str>,
) -> Result<String, String> {
let request = get_http_request(&app, request_id)
@@ -883,8 +889,6 @@ async fn cmd_request_to_curl(
let rendered = render_request(&request, &workspace, environment.as_ref());
let import_response = plugin_manager
.lock()
.await
.run_export_curl(&rendered)
.await
.map_err(|e| e.to_string())?;
@@ -894,19 +898,19 @@ async fn cmd_request_to_curl(
#[tauri::command]
async fn cmd_curl_to_request(
command: &str,
plugin_manager: State<'_, Mutex<PluginManager>>,
plugin_manager: State<'_, PluginManager>,
workspace_id: &str,
w: WebviewWindow,
) -> Result<HttpRequest, String> {
let (import_result, plugin_name) = plugin_manager
.lock()
.await
.run_import(command)
.await
.map_err(|e| e.to_string())?;
let (import_result, plugin_name) = {
plugin_manager
.run_import(command)
.await
.map_err(|e| e.to_string())?
};
analytics::track_event(
&w.app_handle(),
&w,
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
@@ -947,7 +951,7 @@ async fn cmd_export_data(
f.sync_all().expect("Failed to sync");
analytics::track_event(
&window.app_handle(),
&window,
AnalyticsResource::App,
AnalyticsAction::Export,
None,
@@ -984,7 +988,6 @@ async fn cmd_send_http_request(
window: WebviewWindow,
environment_id: Option<&str>,
cookie_jar_id: Option<&str>,
download_dir: Option<&str>,
// NOTE: We receive the entire request because to account for the race
// condition where the user may have just edited a field before sending
// that has not yet been saved in the DB.
@@ -1010,28 +1013,9 @@ async fn cmd_send_http_request(
None => None,
};
let response = create_http_response(
&window,
&request.id,
0,
0,
"",
0,
None,
None,
None,
vec![],
None,
None,
)
.await
.expect("Failed to create response");
let download_path = if let Some(p) = download_dir {
Some(std::path::Path::new(p).to_path_buf())
} else {
None
};
let response = create_default_http_response(&window, &request.id)
.await
.map_err(|e| e.to_string())?;
let (cancel_tx, mut cancel_rx) = tokio::sync::watch::channel(false);
window.listen_any(
@@ -1047,16 +1031,15 @@ async fn cmd_send_http_request(
&response,
environment,
cookie_jar,
download_path,
&mut cancel_rx,
)
.await
}
async fn response_err(
async fn response_err<R: Runtime>(
response: &HttpResponse,
error: String,
w: &WebviewWindow,
w: &WebviewWindow<R>,
) -> Result<HttpResponse, String> {
warn!("Failed to send request: {}", error);
let mut response = response.clone();
@@ -1080,7 +1063,7 @@ async fn cmd_track_event(
AnalyticsAction::from_str(action),
) {
(Ok(resource), Ok(action)) => {
analytics::track_event(&window.app_handle(), resource, action, attributes).await;
analytics::track_event(&window, resource, action, attributes).await;
}
(r, a) => {
error!(
@@ -1635,9 +1618,8 @@ pub fn run() {
let grpc_handle = GrpcHandle::new(&app.app_handle());
app.manage(Mutex::new(grpc_handle));
// Add plugin manager
let grpc_handle = GrpcHandle::new(&app.app_handle());
app.manage(Mutex::new(grpc_handle));
let app_handle = app.app_handle().clone();
monitor_plugin_events(&app_handle);
Ok(())
})
@@ -1715,10 +1697,9 @@ pub fn run() {
.run(|app_handle, event| {
match event {
RunEvent::Ready => {
create_window(app_handle, "/");
let h = app_handle.clone();
let w = create_window(app_handle, "/");
tauri::async_runtime::spawn(async move {
let info = analytics::track_launch_event(&h).await;
let info = analytics::track_launch_event(&w).await;
debug!("Launched Yaak {:?}", info);
});
@@ -1743,10 +1724,12 @@ pub fn run() {
let h = app_handle.clone();
tauri::async_runtime::spawn(async move {
let windows = h.webview_windows();
let w = windows.values().next().unwrap();
tokio::time::sleep(Duration::from_millis(4000)).await;
let val: State<'_, Mutex<YaakNotifier>> = h.state();
let val: State<'_, Mutex<YaakNotifier>> = w.state();
let mut n = val.lock().await;
if let Err(e) = n.check(&h).await {
if let Err(e) = n.check(&w).await {
warn!("Failed to check for notifications {}", e)
}
});
@@ -1905,3 +1888,86 @@ fn safe_uri(endpoint: &str) -> String {
format!("http://{}", endpoint)
}
}
fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
let app_handle = app_handle.clone();
tauri::async_runtime::spawn(async move {
let plugin_manager: State<'_, PluginManager> = app_handle.state();
let (_rx_id, mut rx) = plugin_manager.subscribe().await;
let app_handle = app_handle.clone();
while let Some(event) = rx.recv().await {
let payload = match handle_plugin_event(&app_handle, &event).await {
Some(e) => e,
None => continue,
};
if let Err(e) = plugin_manager.reply(&event, &payload).await {
warn!("Failed to reply to plugin manager: {}", e)
}
}
});
}
async fn handle_plugin_event<R: Runtime>(
app_handle: &AppHandle<R>,
event: &InternalEvent,
) -> Option<InternalEventPayload> {
let event = match event.clone().payload {
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = get_http_request(app_handle, req.id.as_str()).await.ok();
InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
http_request,
})
}
InternalEventPayload::SendHttpRequestRequest(req) => {
let webview_windows = app_handle.get_focused_window()?.webview_windows();
let w = match webview_windows.iter().next() {
None => return None,
Some((_, w)) => w,
};
let url = w.url().unwrap();
let mut query_pairs = url.query_pairs();
let cookie_jar_id = query_pairs
.find(|(k, _v)| k == "cookie_jar_id")
.map(|(_k, v)| v.to_string());
let cookie_jar = match cookie_jar_id {
None => None,
Some(id) => get_cookie_jar(w, id.as_str()).await.ok(),
};
let environment_id = query_pairs
.find(|(k, _v)| k == "environment_id")
.map(|(_k, v)| v.to_string());
let environment = match environment_id {
None => None,
Some(id) => get_environment(w, id.as_str()).await.ok(),
};
let resp = create_default_http_response(w, req.http_request.id.as_str())
.await
.unwrap();
let result = send_http_request(
&w,
req.http_request,
&resp,
environment,
cookie_jar,
&mut watch::channel(false).1, // No-op cancel channel
)
.await;
let http_response = match result {
Ok(r) => r,
Err(_e) => return None,
};
InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse { http_response })
}
_ => return None,
};
Some(event)
}

View File

@@ -5,7 +5,7 @@ use chrono::{DateTime, Duration, Utc};
use log::debug;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use tauri::{AppHandle, Emitter};
use tauri::{Emitter, Manager, Runtime, WebviewWindow};
use yaak_models::queries::{get_key_value_raw, set_key_value_raw};
// Check for updates every hour
@@ -42,16 +42,16 @@ impl YaakNotifier {
}
}
pub async fn seen(&mut self, app: &AppHandle, id: &str) -> Result<(), String> {
let mut seen = get_kv(app).await?;
pub async fn seen<R: Runtime>(&mut self, w: &WebviewWindow<R>, id: &str) -> Result<(), String> {
let mut seen = get_kv(w).await?;
seen.push(id.to_string());
debug!("Marked notification as seen {}", id);
let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?;
set_key_value_raw(app, KV_NAMESPACE, KV_KEY, seen_json.as_str()).await;
set_key_value_raw(w, KV_NAMESPACE, KV_KEY, seen_json.as_str()).await;
Ok(())
}
pub async fn check(&mut self, app: &AppHandle) -> Result<(), String> {
pub async fn check<R: Runtime>(&mut self, w: &WebviewWindow<R>) -> Result<(), String> {
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
if ignore_check {
@@ -60,8 +60,8 @@ impl YaakNotifier {
self.last_check = SystemTime::now();
let num_launches = get_num_launches(app).await;
let info = app.package_info().clone();
let num_launches = get_num_launches(w).await;
let info = w.app_handle().package_info().clone();
let req = reqwest::Client::default()
.request(Method::GET, "https://notify.yaak.app/notifications")
.query(&[
@@ -80,21 +80,21 @@ impl YaakNotifier {
.map_err(|e| e.to_string())?;
let age = notification.timestamp.signed_duration_since(Utc::now());
let seen = get_kv(app).await?;
let seen = get_kv(w).await?;
if seen.contains(&notification.id) || (age > Duration::days(2)) {
debug!("Already seen notification {}", notification.id);
return Ok(());
}
debug!("Got notification {:?}", notification);
let _ = app.emit("notification", notification.clone());
let _ = w.emit("notification", notification.clone());
Ok(())
}
}
async fn get_kv(app: &AppHandle) -> Result<Vec<String>, String> {
match get_key_value_raw(app, "notifications", "seen").await {
async fn get_kv<R: Runtime>(w: &WebviewWindow<R>) -> Result<Vec<String>, String> {
match get_key_value_raw(w, "notifications", "seen").await {
None => Ok(Vec::new()),
Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()),
}

View File

@@ -1,7 +1,7 @@
use std::collections::HashMap;
use serde_json::Value;
use crate::template_fns::timestamp;
use templates::parse_and_render;
use yaak_templates::parse_and_render;
use yaak_models::models::{
Environment, EnvironmentVariable, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace,
};

View File

@@ -1,5 +1,5 @@
[package]
name = "grpc"
name = "yaak_grpc"
version = "0.1.0"
edition = "2021"

View File

@@ -15,10 +15,10 @@ use sea_query::Keyword::CurrentTimestamp;
use sea_query::{Cond, Expr, OnConflict, Order, Query, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;
use serde::Serialize;
use tauri::{AppHandle, Emitter, Manager, WebviewWindow, Wry};
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
pub async fn set_key_value_string(
mgr: &impl Manager<Wry>,
pub async fn set_key_value_string<R: Runtime>(
mgr: &WebviewWindow<R>,
namespace: &str,
key: &str,
value: &str,
@@ -27,8 +27,8 @@ pub async fn set_key_value_string(
set_key_value_raw(mgr, namespace, key, &encoded.unwrap()).await
}
pub async fn set_key_value_int(
mgr: &impl Manager<Wry>,
pub async fn set_key_value_int<R: Runtime>(
mgr: &WebviewWindow<R>,
namespace: &str,
key: &str,
value: i32,
@@ -37,8 +37,8 @@ pub async fn set_key_value_int(
set_key_value_raw(mgr, namespace, key, &encoded.unwrap()).await
}
pub async fn get_key_value_string(
mgr: &impl Manager<Wry>,
pub async fn get_key_value_string<R: Runtime>(
mgr: &impl Manager<R>,
namespace: &str,
key: &str,
default: &str,
@@ -58,8 +58,8 @@ pub async fn get_key_value_string(
}
}
pub async fn get_key_value_int(
mgr: &impl Manager<Wry>,
pub async fn get_key_value_int<R: Runtime>(
mgr: &impl Manager<R>,
namespace: &str,
key: &str,
default: i32,
@@ -79,15 +79,15 @@ pub async fn get_key_value_int(
}
}
pub async fn set_key_value_raw(
mgr: &impl Manager<Wry>,
pub async fn set_key_value_raw<R: Runtime>(
w: &WebviewWindow<R>,
namespace: &str,
key: &str,
value: &str,
) -> (KeyValue, bool) {
let existing = get_key_value_raw(mgr, namespace, key).await;
let existing = get_key_value_raw(w, namespace, key).await;
let dbm = &*mgr.state::<SqliteConnection>();
let dbm = &*w.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::insert()
.into_table(KeyValueIden::Table)
@@ -119,11 +119,11 @@ pub async fn set_key_value_raw(
let kv = stmt
.query_row(&*params.as_params(), |row| row.try_into())
.expect("Failed to upsert KeyValue");
(kv, existing.is_none())
(emit_upserted_model(w, kv), existing.is_none())
}
pub async fn get_key_value_raw(
mgr: &impl Manager<Wry>,
pub async fn get_key_value_raw<R: Runtime>(
mgr: &impl Manager<R>,
namespace: &str,
key: &str,
) -> Option<KeyValue> {
@@ -143,7 +143,7 @@ pub async fn get_key_value_raw(
.ok()
}
pub async fn list_workspaces(mgr: &impl Manager<Wry>) -> Result<Vec<Workspace>> {
pub async fn list_workspaces<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<Workspace>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
@@ -155,7 +155,7 @@ pub async fn list_workspaces(mgr: &impl Manager<Wry>) -> Result<Vec<Workspace>>
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn get_workspace(mgr: &impl Manager<Wry>, id: &str) -> Result<Workspace> {
pub async fn get_workspace<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Workspace> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
@@ -167,7 +167,10 @@ pub async fn get_workspace(mgr: &impl Manager<Wry>, id: &str) -> Result<Workspac
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn upsert_workspace(window: &WebviewWindow, workspace: Workspace) -> Result<Workspace> {
pub async fn upsert_workspace<R: Runtime>(
window: &WebviewWindow<R>,
workspace: Workspace,
) -> Result<Workspace> {
let id = match workspace.id.as_str() {
"" => generate_model_id(ModelType::TypeWorkspace),
_ => workspace.id.to_string(),
@@ -222,7 +225,10 @@ pub async fn upsert_workspace(window: &WebviewWindow, workspace: Workspace) -> R
Ok(emit_upserted_model(window, m))
}
pub async fn delete_workspace(window: &WebviewWindow, id: &str) -> Result<Workspace> {
pub async fn delete_workspace<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<Workspace> {
let workspace = get_workspace(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -241,7 +247,7 @@ pub async fn delete_workspace(window: &WebviewWindow, id: &str) -> Result<Worksp
emit_deleted_model(window, workspace)
}
pub async fn get_cookie_jar(mgr: &impl Manager<Wry>, id: &str) -> Result<CookieJar> {
pub async fn get_cookie_jar<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<CookieJar> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -254,8 +260,8 @@ pub async fn get_cookie_jar(mgr: &impl Manager<Wry>, id: &str) -> Result<CookieJ
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_cookie_jars(
mgr: &impl Manager<Wry>,
pub async fn list_cookie_jars<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<CookieJar>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -270,7 +276,10 @@ pub async fn list_cookie_jars(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn delete_cookie_jar(window: &WebviewWindow, id: &str) -> Result<CookieJar> {
pub async fn delete_cookie_jar<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<CookieJar> {
let cookie_jar = get_cookie_jar(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -284,13 +293,19 @@ pub async fn delete_cookie_jar(window: &WebviewWindow, id: &str) -> Result<Cooki
emit_deleted_model(window, cookie_jar)
}
pub async fn duplicate_grpc_request(window: &WebviewWindow, id: &str) -> Result<GrpcRequest> {
pub async fn duplicate_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<GrpcRequest> {
let mut request = get_grpc_request(window, id).await?.clone();
request.id = "".to_string();
upsert_grpc_request(window, &request).await
}
pub async fn delete_grpc_request(window: &WebviewWindow, id: &str) -> Result<GrpcRequest> {
pub async fn delete_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<GrpcRequest> {
let req = get_grpc_request(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -304,8 +319,8 @@ pub async fn delete_grpc_request(window: &WebviewWindow, id: &str) -> Result<Grp
emit_deleted_model(window, req)
}
pub async fn upsert_grpc_request(
window: &WebviewWindow,
pub async fn upsert_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
request: &GrpcRequest,
) -> Result<GrpcRequest> {
let id = match request.id.as_str() {
@@ -346,7 +361,11 @@ pub async fn upsert_grpc_request(
request.service.as_ref().map(|s| s.as_str()).into(),
request.method.as_ref().map(|s| s.as_str()).into(),
request.message.as_str().into(),
request .authentication_type .as_ref() .map(|s| s.as_str()) .into(),
request
.authentication_type
.as_ref()
.map(|s| s.as_str())
.into(),
serde_json::to_string(&request.authentication)?.into(),
serde_json::to_string(&request.metadata)?.into(),
])
@@ -376,7 +395,7 @@ pub async fn upsert_grpc_request(
Ok(emit_upserted_model(window, m))
}
pub async fn get_grpc_request(mgr: &impl Manager<Wry>, id: &str) -> Result<GrpcRequest> {
pub async fn get_grpc_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<GrpcRequest> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -389,8 +408,8 @@ pub async fn get_grpc_request(mgr: &impl Manager<Wry>, id: &str) -> Result<GrpcR
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_grpc_requests(
mgr: &impl Manager<Wry>,
pub async fn list_grpc_requests<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<GrpcRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -405,8 +424,8 @@ pub async fn list_grpc_requests(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn upsert_grpc_connection(
window: &WebviewWindow,
pub async fn upsert_grpc_connection<R: Runtime>(
window: &WebviewWindow<R>,
connection: &GrpcConnection,
) -> Result<GrpcConnection> {
let id = match connection.id.as_str() {
@@ -467,7 +486,10 @@ pub async fn upsert_grpc_connection(
Ok(emit_upserted_model(window, m))
}
pub async fn get_grpc_connection(mgr: &impl Manager<Wry>, id: &str) -> Result<GrpcConnection> {
pub async fn get_grpc_connection<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<GrpcConnection> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
@@ -479,8 +501,8 @@ pub async fn get_grpc_connection(mgr: &impl Manager<Wry>, id: &str) -> Result<Gr
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_grpc_connections(
mgr: &impl Manager<Wry>,
pub async fn list_grpc_connections<R: Runtime>(
mgr: &impl Manager<R>,
request_id: &str,
) -> Result<Vec<GrpcConnection>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -497,7 +519,10 @@ pub async fn list_grpc_connections(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn delete_grpc_connection(window: &WebviewWindow, id: &str) -> Result<GrpcConnection> {
pub async fn delete_grpc_connection<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<GrpcConnection> {
let resp = get_grpc_connection(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -512,14 +537,20 @@ pub async fn delete_grpc_connection(window: &WebviewWindow, id: &str) -> Result<
emit_deleted_model(window, resp)
}
pub async fn delete_all_grpc_connections(window: &WebviewWindow, request_id: &str) -> Result<()> {
pub async fn delete_all_grpc_connections<R: Runtime>(
window: &WebviewWindow<R>,
request_id: &str,
) -> Result<()> {
for r in list_grpc_connections(window, request_id).await? {
delete_grpc_connection(window, &r.id).await?;
}
Ok(())
}
pub async fn upsert_grpc_event(window: &WebviewWindow, event: &GrpcEvent) -> Result<GrpcEvent> {
pub async fn upsert_grpc_event<R: Runtime>(
window: &WebviewWindow<R>,
event: &GrpcEvent,
) -> Result<GrpcEvent> {
let id = match event.id.as_str() {
"" => generate_model_id(ModelType::TypeGrpcEvent),
_ => event.id.to_string(),
@@ -575,7 +606,7 @@ pub async fn upsert_grpc_event(window: &WebviewWindow, event: &GrpcEvent) -> Res
Ok(emit_upserted_model(window, m))
}
pub async fn get_grpc_event(mgr: &impl Manager<Wry>, id: &str) -> Result<GrpcEvent> {
pub async fn get_grpc_event<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<GrpcEvent> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
@@ -587,8 +618,8 @@ pub async fn get_grpc_event(mgr: &impl Manager<Wry>, id: &str) -> Result<GrpcEve
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_grpc_events(
mgr: &impl Manager<Wry>,
pub async fn list_grpc_events<R: Runtime>(
mgr: &impl Manager<R>,
connection_id: &str,
) -> Result<Vec<GrpcEvent>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -605,8 +636,8 @@ pub async fn list_grpc_events(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn upsert_cookie_jar(
window: &WebviewWindow,
pub async fn upsert_cookie_jar<R: Runtime>(
window: &WebviewWindow<R>,
cookie_jar: &CookieJar,
) -> Result<CookieJar> {
let id = match cookie_jar.id.as_str() {
@@ -653,8 +684,8 @@ pub async fn upsert_cookie_jar(
Ok(emit_upserted_model(window, m))
}
pub async fn list_environments(
mgr: &impl Manager<Wry>,
pub async fn list_environments<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<Environment>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -671,7 +702,10 @@ pub async fn list_environments(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn delete_environment(window: &WebviewWindow, id: &str) -> Result<Environment> {
pub async fn delete_environment<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<Environment> {
let env = get_environment(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -686,7 +720,7 @@ pub async fn delete_environment(window: &WebviewWindow, id: &str) -> Result<Envi
emit_deleted_model(window, env)
}
async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings> {
async fn get_settings<R: Runtime>(mgr: &impl Manager<R>) -> Result<Settings> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -699,7 +733,7 @@ async fn get_settings(mgr: &impl Manager<Wry>) -> Result<Settings> {
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn get_or_create_settings(mgr: &impl Manager<Wry>) -> Settings {
pub async fn get_or_create_settings<R: Runtime>(mgr: &impl Manager<R>) -> Settings {
if let Ok(settings) = get_settings(mgr).await {
return settings;
}
@@ -721,7 +755,10 @@ pub async fn get_or_create_settings(mgr: &impl Manager<Wry>) -> Settings {
.expect("Failed to insert Settings")
}
pub async fn update_settings(window: &WebviewWindow, settings: Settings) -> Result<Settings> {
pub async fn update_settings<R: Runtime>(
window: &WebviewWindow<R>,
settings: Settings,
) -> Result<Settings> {
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -773,8 +810,8 @@ pub async fn update_settings(window: &WebviewWindow, settings: Settings) -> Resu
Ok(emit_upserted_model(window, m))
}
pub async fn upsert_environment(
window: &WebviewWindow,
pub async fn upsert_environment<R: Runtime>(
window: &WebviewWindow<R>,
environment: Environment,
) -> Result<Environment> {
let id = match environment.id.as_str() {
@@ -821,7 +858,7 @@ pub async fn upsert_environment(
Ok(emit_upserted_model(window, m))
}
pub async fn get_environment(mgr: &impl Manager<Wry>, id: &str) -> Result<Environment> {
pub async fn get_environment<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Environment> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -834,7 +871,7 @@ pub async fn get_environment(mgr: &impl Manager<Wry>, id: &str) -> Result<Enviro
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn get_folder(mgr: &impl Manager<Wry>, id: &str) -> Result<Folder> {
pub async fn get_folder<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Folder> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -847,7 +884,10 @@ pub async fn get_folder(mgr: &impl Manager<Wry>, id: &str) -> Result<Folder> {
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_folders(mgr: &impl Manager<Wry>, workspace_id: &str) -> Result<Vec<Folder>> {
pub async fn list_folders<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<Folder>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -862,7 +902,7 @@ pub async fn list_folders(mgr: &impl Manager<Wry>, workspace_id: &str) -> Result
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn delete_folder(window: &WebviewWindow, id: &str) -> Result<Folder> {
pub async fn delete_folder<R: Runtime>(window: &WebviewWindow<R>, id: &str) -> Result<Folder> {
let folder = get_folder(window, id).await?;
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -877,7 +917,7 @@ pub async fn delete_folder(window: &WebviewWindow, id: &str) -> Result<Folder> {
emit_deleted_model(window, folder)
}
pub async fn upsert_folder(window: &WebviewWindow, r: Folder) -> Result<Folder> {
pub async fn upsert_folder<R: Runtime>(window: &WebviewWindow<R>, r: Folder) -> Result<Folder> {
let id = match r.id.as_str() {
"" => generate_model_id(ModelType::TypeFolder),
_ => r.id.to_string(),
@@ -925,13 +965,19 @@ pub async fn upsert_folder(window: &WebviewWindow, r: Folder) -> Result<Folder>
Ok(emit_upserted_model(window, m))
}
pub async fn duplicate_http_request(window: &WebviewWindow, id: &str) -> Result<HttpRequest> {
pub async fn duplicate_http_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<HttpRequest> {
let mut request = get_http_request(window, id).await?.clone();
request.id = "".to_string();
upsert_http_request(window, request).await
}
pub async fn upsert_http_request(window: &WebviewWindow, r: HttpRequest) -> Result<HttpRequest> {
pub async fn upsert_http_request<R: Runtime>(
window: &WebviewWindow<R>,
r: HttpRequest,
) -> Result<HttpRequest> {
let id = match r.id.as_str() {
"" => generate_model_id(ModelType::TypeHttpRequest),
_ => r.id.to_string(),
@@ -1004,8 +1050,8 @@ pub async fn upsert_http_request(window: &WebviewWindow, r: HttpRequest) -> Resu
Ok(emit_upserted_model(window, m))
}
pub async fn list_http_requests(
mgr: &impl Manager<Wry>,
pub async fn list_http_requests<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<HttpRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -1021,7 +1067,7 @@ pub async fn list_http_requests(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn get_http_request(mgr: &impl Manager<Wry>, id: &str) -> Result<HttpRequest> {
pub async fn get_http_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<HttpRequest> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -1034,7 +1080,10 @@ pub async fn get_http_request(mgr: &impl Manager<Wry>, id: &str) -> Result<HttpR
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn delete_http_request(window: &WebviewWindow, id: &str) -> Result<HttpRequest> {
pub async fn delete_http_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<HttpRequest> {
let req = get_http_request(window, id).await?;
// DB deletes will cascade but this will delete the files
@@ -1051,9 +1100,30 @@ pub async fn delete_http_request(window: &WebviewWindow, id: &str) -> Result<Htt
emit_deleted_model(window, req)
}
pub async fn create_default_http_response<R: Runtime>(
window: &WebviewWindow<R>,
request_id: &str,
) -> Result<HttpResponse> {
create_http_response(
&window,
request_id,
0,
0,
"",
0,
None,
None,
None,
vec![],
None,
None,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn create_http_response(
window: &WebviewWindow,
pub async fn create_http_response<R: Runtime>(
window: &WebviewWindow<R>,
request_id: &str,
elapsed: i64,
elapsed_headers: i64,
@@ -1146,8 +1216,8 @@ pub async fn cancel_pending_responses(app: &AppHandle) -> Result<()> {
Ok(())
}
pub async fn update_response_if_id(
window: &WebviewWindow,
pub async fn update_response_if_id<R: Runtime>(
window: &WebviewWindow<R>,
response: &HttpResponse,
) -> Result<HttpResponse> {
if response.id.is_empty() {
@@ -1157,8 +1227,8 @@ pub async fn update_response_if_id(
}
}
pub async fn update_response(
window: &WebviewWindow,
pub async fn update_response<R: Runtime>(
window: &WebviewWindow<R>,
response: &HttpResponse,
) -> Result<HttpResponse> {
let dbm = &*window.app_handle().state::<SqliteConnection>();
@@ -1211,7 +1281,10 @@ pub async fn update_response(
Ok(emit_upserted_model(window, m))
}
pub async fn get_http_response(mgr: &impl Manager<Wry>, id: &str) -> Result<HttpResponse> {
pub async fn get_http_response<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<HttpResponse> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
let (sql, params) = Query::select()
@@ -1223,7 +1296,10 @@ pub async fn get_http_response(mgr: &impl Manager<Wry>, id: &str) -> Result<Http
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn delete_http_response(window: &WebviewWindow, id: &str) -> Result<HttpResponse> {
pub async fn delete_http_response<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<HttpResponse> {
let resp = get_http_response(window, id).await?;
// Delete the body file if it exists
@@ -1244,15 +1320,18 @@ pub async fn delete_http_response(window: &WebviewWindow, id: &str) -> Result<Ht
emit_deleted_model(window, resp)
}
pub async fn delete_all_http_responses(window: &WebviewWindow, request_id: &str) -> Result<()> {
pub async fn delete_all_http_responses<R: Runtime>(
window: &WebviewWindow<R>,
request_id: &str,
) -> Result<()> {
for r in list_responses(window, request_id, None).await? {
delete_http_response(window, &r.id).await?;
}
Ok(())
}
pub async fn list_responses(
mgr: &impl Manager<Wry>,
pub async fn list_responses<R: Runtime>(
mgr: &impl Manager<R>,
request_id: &str,
limit: Option<i64>,
) -> Result<Vec<HttpResponse>> {
@@ -1271,8 +1350,8 @@ pub async fn list_responses(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn list_responses_by_workspace_id(
mgr: &impl Manager<Wry>,
pub async fn list_responses_by_workspace_id<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<HttpResponse>> {
let dbm = &*mgr.state::<SqliteConnection>();
@@ -1288,7 +1367,7 @@ pub async fn list_responses_by_workspace_id(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn debug_pool(mgr: &impl Manager<Wry>) {
pub async fn debug_pool<R: Runtime>(mgr: &impl Manager<R>) {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await;
debug!("Debug database state: {:?}", db.state());
@@ -1310,7 +1389,7 @@ struct ModelPayload<M: Serialize + Clone> {
pub window_label: String,
}
fn emit_upserted_model<M: Serialize + Clone>(window: &WebviewWindow, model: M) -> M {
fn emit_upserted_model<M: Serialize + Clone, R: Runtime>(window: &WebviewWindow<R>, model: M) -> M {
let payload = ModelPayload {
model: model.clone(),
window_label: window.label().to_string(),
@@ -1320,7 +1399,10 @@ fn emit_upserted_model<M: Serialize + Clone>(window: &WebviewWindow, model: M) -
model
}
fn emit_deleted_model<M: Serialize + Clone>(window: &WebviewWindow, model: M) -> Result<M> {
fn emit_deleted_model<M: Serialize + Clone, R: Runtime>(
window: &WebviewWindow<R>,
model: M,
) -> Result<M> {
let payload = ModelPayload {
model: model.clone(),
window_label: window.label().to_string(),

View File

@@ -1,7 +1,10 @@
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use yaak_models::models::{CookieJar, Environment, Folder, GrpcConnection, GrpcEvent, GrpcRequest, HttpRequest, HttpResponse, KeyValue, Settings, Workspace};
use yaak_models::models::{
CookieJar, Environment, Folder, GrpcConnection, GrpcEvent, GrpcRequest, HttpRequest,
HttpResponse, KeyValue, Settings, Workspace,
};
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
@@ -20,12 +23,22 @@ pub struct InternalEvent {
pub enum InternalEventPayload {
BootRequest(BootRequest),
BootResponse(BootResponse),
ImportRequest(ImportRequest),
ImportResponse(ImportResponse),
FilterRequest(FilterRequest),
FilterResponse(FilterResponse),
ExportHttpRequestRequest(ExportHttpRequestRequest),
ExportHttpRequestResponse(ExportHttpRequestResponse),
SendHttpRequestRequest(SendHttpRequestRequest),
SendHttpRequestResponse(SendHttpRequestResponse),
GetHttpRequestByIdRequest(GetHttpRequestByIdRequest),
GetHttpRequestByIdResponse(GetHttpRequestByIdResponse),
/// Returned when a plugin doesn't get run, just so the server
/// has something to listen for
EmptyResponse(EmptyResponse),
@@ -95,17 +108,33 @@ pub struct ExportHttpRequestResponse {
pub content: String,
}
// TODO: Migrate plugins to return this type
// #[derive(Debug, Clone, Serialize, Deserialize, TS)]
// #[serde(rename_all = "camelCase", untagged)]
// #[ts(export)]
// pub enum ExportableModel {
// Workspace(Workspace),
// Environment(Environment),
// Folder(Folder),
// HttpRequest(HttpRequest),
// GrpcRequest(GrpcRequest),
// }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export)]
pub struct SendHttpRequestRequest {
pub http_request: HttpRequest,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export)]
pub struct SendHttpRequestResponse {
pub http_response: HttpResponse,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export)]
pub struct GetHttpRequestByIdRequest {
pub id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export)]
pub struct GetHttpRequestByIdResponse {
pub http_request: Option<HttpRequest>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]

View File

@@ -1,7 +1,7 @@
use crate::error::Result;
use crate::events::{
ExportHttpRequestRequest, ExportHttpRequestResponse, FilterRequest, FilterResponse,
ImportRequest, ImportResponse, InternalEventPayload,
ExportHttpRequestRequest, ExportHttpRequestResponse, FilterRequest, FilterResponse
, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload,
};
use crate::error::Error::PluginErr;
@@ -10,6 +10,7 @@ use crate::plugin::start_server;
use crate::server::PluginRuntimeGrpcServer;
use std::time::Duration;
use tauri::{AppHandle, Runtime};
use tokio::sync::mpsc;
use tokio::sync::watch::Sender;
use yaak_models::models::HttpRequest;
@@ -35,14 +36,33 @@ impl PluginManager {
PluginManager { kill_tx, server }
}
pub async fn cleanup(&mut self) {
pub async fn subscribe(&self) -> (String, mpsc::Receiver<InternalEvent>) {
self.server.subscribe().await
}
pub async fn unsubscribe(&self, rx_id: &str) {
self.server.unsubscribe(rx_id).await
}
pub async fn cleanup(&self) {
self.kill_tx.send_replace(true);
// Give it a bit of time to kill
tokio::time::sleep(Duration::from_millis(500)).await;
}
pub async fn run_import(&mut self, content: &str) -> Result<(ImportResponse, String)> {
pub async fn reply(
&self,
source_event: &InternalEvent,
payload: &InternalEventPayload,
) -> Result<()> {
let reply_id = Some(source_event.clone().id);
self.server
.send(&payload, source_event.plugin_ref_id.as_str(), reply_id)
.await
}
pub async fn run_import(&self, content: &str) -> Result<(ImportResponse, String)> {
let reply_events = self
.server
.send_and_wait(&InternalEventPayload::ImportRequest(ImportRequest {
@@ -67,7 +87,7 @@ impl PluginManager {
}
pub async fn run_export_curl(
&mut self,
&self,
request: &HttpRequest,
) -> Result<ExportHttpRequestResponse> {
let event = self
@@ -90,7 +110,7 @@ impl PluginManager {
}
pub async fn run_filter(
&mut self,
&self,
filter: &str,
content: &str,
content_type: &str,

View File

@@ -13,7 +13,6 @@ use tauri::plugin::{Builder, TauriPlugin};
use tauri::{Manager, RunEvent, Runtime, State};
use tokio::fs::read_dir;
use tokio::net::TcpListener;
use tokio::sync::Mutex;
use tonic::codegen::tokio_stream;
use tonic::transport::Server;
@@ -30,8 +29,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.await
.expect("Failed to read plugins dir");
let manager = PluginManager::new(&app, plugin_dirs).await;
let manager_state = Mutex::new(manager);
app.manage(manager_state);
app.manage(manager);
Ok(())
})
})
@@ -41,8 +39,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
api.prevent_exit();
tauri::async_runtime::block_on(async move {
info!("Exiting plugin runtime due to app exit");
let manager: State<Mutex<PluginManager>> = app.state();
manager.lock().await.cleanup().await;
let manager: State<PluginManager> = app.state();
manager.cleanup().await;
exit(0);
});
}
@@ -79,7 +77,7 @@ pub async fn start_server(
_ => {}
};
}
server.unsubscribe(rx_id).await;
server.unsubscribe(rx_id.as_str()).await;
});
};

View File

@@ -95,8 +95,8 @@ impl PluginRuntimeGrpcServer {
(id, rx)
}
pub async fn unsubscribe(&self, rx_id: String) {
self.subscribers.lock().await.remove(rx_id.as_str());
pub async fn unsubscribe(&self, rx_id: &str) {
self.subscribers.lock().await.remove(rx_id);
}
pub async fn remove_plugins(&self, plugin_ids: Vec<String>) {
@@ -214,6 +214,13 @@ impl PluginRuntimeGrpcServer {
let msg = format!("Failed to find plugin for {plugin_name}");
Err(PluginNotFoundErr(msg))
}
pub async fn send(&self, payload: &InternalEventPayload, plugin_ref_id: &str, reply_id: Option<String>)-> Result<()> {
let plugin = self.plugin_by_ref_id(plugin_ref_id).await?;
let event = plugin.build_event_to_send(payload, reply_id);
plugin.send(&event).await
}
pub async fn send_to_plugin(
&self,
@@ -301,7 +308,7 @@ impl PluginRuntimeGrpcServer {
break;
}
}
server.unsubscribe(rx_id).await;
server.unsubscribe(rx_id.as_str()).await;
found_events
})
@@ -321,30 +328,6 @@ impl PluginRuntimeGrpcServer {
Ok(events)
}
pub async fn send(&self, payload: InternalEventPayload) -> Result<Vec<InternalEvent>> {
let mut events: Vec<InternalEvent> = Vec::new();
let plugins = self.plugin_ref_to_plugin.lock().await;
if plugins.is_empty() {
return Err(NoPluginsErr("Send failed because no plugins exist".into()));
}
for ph in plugins.values() {
let event = ph.build_event_to_send(&payload, None);
self.send_to_plugin_handle(ph, &event).await?;
events.push(event);
}
Ok(events)
}
async fn send_to_plugin_handle(
&self,
plugin: &PluginHandle,
event: &InternalEvent,
) -> Result<()> {
plugin.send(event).await
}
async fn load_plugins(
&self,
to_plugin_tx: mpsc::Sender<tonic::Result<EventStreamEvent>>,

View File

@@ -1,5 +1,5 @@
[package]
name = "templates"
name = "yaak_templates"
version = "0.1.0"
edition = "2021"