NodeJS Plugin Runtime (#53)

This commit is contained in:
Gregory Schier
2024-07-19 10:41:47 -07:00
committed by GitHub
parent 7e5408fc92
commit 102bd588c2
106 changed files with 5246 additions and 21337 deletions

View File

@@ -32,10 +32,12 @@ use tokio::sync::Mutex;
use ::grpc::manager::{DynamicMessage, GrpcHandle};
use ::grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
use plugin_runtime::manager::PluginManager;
use crate::analytics::{AnalyticsAction, AnalyticsResource};
use crate::grpc::metadata_to_map;
use crate::http_request::send_http_request;
use crate::models::ImportResult;
use crate::models::{
cancel_pending_grpc_connections, cancel_pending_responses, create_http_response,
delete_all_grpc_connections, delete_all_http_responses, delete_cookie_jar, delete_environment,
@@ -53,22 +55,15 @@ use crate::models::{
WorkspaceExportResources,
};
use crate::notifications::YaakNotifier;
use crate::plugin::{
find_plugins, get_plugin, run_plugin_export_curl, run_plugin_filter, run_plugin_import,
ImportResult, PluginCapability,
};
use crate::render::{render_request, variables_from_environment};
use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu;
mod analytics;
mod deno;
mod deno_ops;
mod grpc;
mod http_request;
mod models;
mod notifications;
mod plugin;
mod render;
#[cfg(target_os = "macos")]
mod tauri_plugin_mac_window;
@@ -284,8 +279,8 @@ async fn cmd_grpc_go(
..conn.clone()
},
)
.await
.map_err(|e| e.to_string())?;
.await
.map_err(|e| e.to_string())?;
return Ok(conn_id);
}
};
@@ -740,22 +735,15 @@ async fn cmd_filter_response(
}
}
// TODO: Have plugins register their own content type (regex?)
let plugin_name = if content_type.contains("json") {
"filter-jsonpath"
} else {
"filter-xpath"
};
let body = read_to_string(response.body_path.unwrap()).unwrap();
let plugin = match get_plugin(&w.app_handle(), plugin_name).map_err(|e| e.to_string())? {
None => return Err("Failed to get plugin".into()),
Some(p) => p,
};
let filter_result = run_plugin_filter(&plugin, filter, &body)
// TODO: Have plugins register their own content type (regex?)
let manager: State<PluginManager> = w.app_handle().state();
manager
.inner()
.run_response_filter(filter, &body, &content_type)
.await
.expect("Failed to run filter");
Ok(filter_result.filtered)
.map(|r| r.data)
}
#[tauri::command]
@@ -764,140 +752,129 @@ async fn cmd_import_data(
file_path: &str,
_workspace_id: &str,
) -> Result<WorkspaceExportResources, String> {
let mut result: Option<ImportResult> = None;
let file =
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str();
let plugins = find_plugins(w.app_handle(), &PluginCapability::Import)
.await
.map_err(|e| e.to_string())?;
for plugin in plugins {
let v = run_plugin_import(&plugin, file_contents)
let manager: State<PluginManager> = w.app_handle().state();
let import_response = manager.inner().run_import(file_contents).await?;
let import_result: ImportResult =
serde_json::from_str(import_response.data.as_str()).map_err(|e| e.to_string())?;
// TODO: Track the plugin that ran, maybe return the run info in the plugin response?
let plugin_name = import_response.info.unwrap_or_default().plugin;
info!("Imported data using {}", plugin_name);
analytics::track_event(
&w.app_handle(),
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
)
.await;
let mut imported_resources = WorkspaceExportResources::default();
let mut id_map: HashMap<String, String> = HashMap::new();
fn maybe_gen_id(id: &str, model: ModelType, ids: &mut HashMap<String, String>) -> String {
if !id.starts_with("GENERATE_ID::") {
return id.to_string();
}
let unique_key = id.replace("GENERATE_ID", "");
if let Some(existing) = ids.get(unique_key.as_str()) {
existing.to_string()
} else {
let new_id = generate_model_id(model);
ids.insert(unique_key, new_id.clone());
new_id
}
}
fn maybe_gen_id_opt(
id: Option<String>,
model: ModelType,
ids: &mut HashMap<String, String>,
) -> Option<String> {
match id {
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
None => None,
}
}
for mut v in import_result.resources.workspaces {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.workspaces.push(x.clone());
}
info!(
"Imported {} workspaces",
imported_resources.workspaces.len()
);
for mut v in import_result.resources.environments {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.environments.push(x.clone());
}
info!(
"Imported {} environments",
imported_resources.environments.len()
);
for mut v in import_result.resources.folders {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.folders.push(x.clone());
}
info!("Imported {} folders", imported_resources.folders.len());
for mut v in import_result.resources.http_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_http_request(&w, v)
.await
.map_err(|e| e.to_string())?;
if let Some(r) = v {
info!("Imported data using {}", plugin.name);
analytics::track_event(
&w.app_handle(),
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin.name })),
)
.await;
result = Some(r);
break;
}
imported_resources.http_requests.push(x.clone());
}
info!(
"Imported {} http_requests",
imported_resources.http_requests.len()
);
match result {
None => Err("No import handlers found".to_string()),
Some(r) => {
let mut imported_resources = WorkspaceExportResources::default();
let mut id_map: HashMap<String, String> = HashMap::new();
let maybe_gen_id =
|id: &str, model: ModelType, ids: &mut HashMap<String, String>| -> String {
if !id.starts_with("GENERATE_ID::") {
return id.to_string();
}
let unique_key = id.replace("GENERATE_ID", "");
if let Some(existing) = ids.get(unique_key.as_str()) {
existing.to_string()
} else {
let new_id = generate_model_id(model);
ids.insert(unique_key, new_id.clone());
new_id
}
};
let maybe_gen_id_opt = |id: Option<String>,
model: ModelType,
ids: &mut HashMap<String, String>|
-> Option<String> {
match id {
Some(id) => Some(maybe_gen_id(id.as_str(), model, ids)),
None => None,
}
};
for mut v in r.resources.workspaces {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map);
let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.workspaces.push(x.clone());
}
info!(
"Imported {} workspaces",
imported_resources.workspaces.len()
);
for mut v in r.resources.environments {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.environments.push(x.clone());
}
info!(
"Imported {} environments",
imported_resources.environments.len()
);
for mut v in r.resources.folders {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
imported_resources.folders.push(x.clone());
}
info!("Imported {} folders", imported_resources.folders.len());
for mut v in r.resources.http_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeHttpRequest, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_http_request(&w, v)
.await
.map_err(|e| e.to_string())?;
imported_resources.http_requests.push(x.clone());
}
info!(
"Imported {} http_requests",
imported_resources.http_requests.len()
);
for mut v in r.resources.grpc_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_grpc_request(&w, &v)
.await
.map_err(|e| e.to_string())?;
imported_resources.grpc_requests.push(x.clone());
}
info!(
"Imported {} grpc_requests",
imported_resources.grpc_requests.len()
);
Ok(imported_resources)
}
for mut v in import_result.resources.grpc_requests {
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeGrpcRequest, &mut id_map);
v.workspace_id = maybe_gen_id(
v.workspace_id.as_str(),
ModelType::TypeWorkspace,
&mut id_map,
);
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
let x = upsert_grpc_request(&w, &v)
.await
.map_err(|e| e.to_string())?;
imported_resources.grpc_requests.push(x.clone());
}
info!(
"Imported {} grpc_requests",
imported_resources.grpc_requests.len()
);
Ok(imported_resources)
}
#[tauri::command]
@@ -917,7 +894,11 @@ async fn cmd_request_to_curl(
.await
.map_err(|e| e.to_string())?;
let rendered = render_request(&request, &workspace, environment.as_ref());
Ok(run_plugin_export_curl(&app, &rendered)?)
let request_json = serde_json::to_string(&rendered).map_err(|e| e.to_string())?;
let manager: State<PluginManager> = app.state();
let import_response = manager.inner().run_export_curl(request_json.as_str()).await?;
Ok(import_response.data)
}
#[tauri::command]
@@ -926,27 +907,21 @@ async fn cmd_curl_to_request(
command: &str,
workspace_id: &str,
) -> Result<HttpRequest, String> {
let plugin = match get_plugin(&app_handle, "importer-curl").map_err(|e| e.to_string())? {
None => return Err("Failed to find plugin".into()),
Some(p) => p,
};
let v = run_plugin_import(&plugin, command).await;
match v {
Ok(Some(r)) => r
.resources
.http_requests
.get(0)
.ok_or("No curl command found".to_string())
.map(|r| {
let mut request = r.clone();
request.workspace_id = workspace_id.into();
request.id = "".to_string();
request
}),
Ok(None) => Err("Did not find curl request".to_string()),
Err(e) => Err(e),
}
let manager: State<PluginManager> = app_handle.state();
let import_response = manager.inner().run_import(command).await?;
let import_result: ImportResult =
serde_json::from_str(import_response.data.as_str()).map_err(|e| e.to_string())?;
import_result
.resources
.http_requests
.get(0)
.ok_or("No curl command found".to_string())
.map(|r| {
let mut request = r.clone();
request.workspace_id = workspace_id.into();
request.id = "".to_string();
request
})
}
#[tauri::command]
@@ -1595,25 +1570,6 @@ async fn cmd_check_for_updates(
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_fs::init());
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_plugin_mac_window::init());
}
#[cfg(target_os = "linux")]
{
builder = builder; // Don't complain about not being mut
}
builder
.plugin(
tauri_plugin_log::Builder::default()
.targets([
@@ -1621,6 +1577,7 @@ pub fn run() {
Target::new(TargetKind::LogDir { file_name: None }),
Target::new(TargetKind::Webview),
])
.level_for("plugin_runtime", log::LevelFilter::Info)
.level_for("cookie_store", log::LevelFilter::Info)
.level_for("h2", log::LevelFilter::Info)
.level_for("hyper", log::LevelFilter::Info)
@@ -1642,6 +1599,26 @@ pub fn run() {
})
.build(),
)
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(plugin_runtime::init())
.plugin(tauri_plugin_fs::init());
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_plugin_mac_window::init());
}
#[cfg(target_os = "linux")]
{
builder = builder; // Don't complain about not being mut
}
builder
.setup(|app| {
let app_data_dir = app.path().app_data_dir().unwrap();
let app_config_dir = app.path().app_config_dir().unwrap();
@@ -1650,13 +1627,13 @@ pub fn run() {
app_config_dir.as_path().to_string_lossy(),
);
info!("App Data Dir: {}", app_data_dir.as_path().to_string_lossy());
let dir = match is_dev() {
let app_data_dir = match is_dev() {
true => current_dir().unwrap(),
false => app_data_dir,
};
create_dir_all(dir.clone()).expect("Problem creating App directory!");
let p = dir.join("db.sqlite");
create_dir_all(app_data_dir.clone()).expect("Problem creating App directory!");
let p = app_data_dir.join("db.sqlite");
File::options()
.write(true)
.create(true)
@@ -1679,6 +1656,10 @@ 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));
// Add DB handle
tauri::async_runtime::block_on(async move {
let opts = SqliteConnectOptions::from_str(p.to_str().unwrap()).unwrap();