Queries now use AppHandle instead of Window (#189)

This commit is contained in:
Gregory Schier
2025-03-20 09:43:14 -07:00
committed by GitHub
parent cf8f8743bb
commit 4c4eaba7d2
19 changed files with 1063 additions and 740 deletions

View File

@@ -1,8 +1,7 @@
use tauri::{Manager, Runtime, WebviewWindow}; use tauri::{AppHandle, Runtime};
use yaak_models::queries::{ use yaak_models::queries::{
get_key_value_int, get_key_value_string, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string, UpdateSource,
set_key_value_int, set_key_value_string, UpdateSource,
}; };
const NAMESPACE: &str = "analytics"; const NAMESPACE: &str = "analytics";
@@ -16,14 +15,15 @@ pub struct LaunchEventInfo {
pub num_launches: i32, pub num_launches: i32,
} }
pub async fn store_launch_history<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEventInfo { pub async fn store_launch_history<R: Runtime>(app_handle: &AppHandle<R>) -> LaunchEventInfo {
let last_tracked_version_key = "last_tracked_version"; let last_tracked_version_key = "last_tracked_version";
let mut info = LaunchEventInfo::default(); let mut info = LaunchEventInfo::default();
info.num_launches = get_num_launches(w).await + 1; info.num_launches = get_num_launches(app_handle).await + 1;
info.previous_version = get_key_value_string(w, NAMESPACE, last_tracked_version_key, "").await; info.previous_version =
info.current_version = w.package_info().version.to_string(); get_key_value_string(app_handle, NAMESPACE, last_tracked_version_key, "").await;
info.current_version = app_handle.package_info().version.to_string();
if info.previous_version.is_empty() { if info.previous_version.is_empty() {
} else { } else {
@@ -33,16 +33,22 @@ pub async fn store_launch_history<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEve
// Update key values // Update key values
set_key_value_string( set_key_value_string(
w, app_handle,
NAMESPACE, NAMESPACE,
last_tracked_version_key, last_tracked_version_key,
info.current_version.as_str(), info.current_version.as_str(),
&UpdateSource::Background, &UpdateSource::Background,
) )
.await; .await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, &UpdateSource::Background) set_key_value_int(
.await; app_handle,
NAMESPACE,
NUM_LAUNCHES_KEY,
info.num_launches,
&UpdateSource::Background,
)
.await;
info info
} }
@@ -59,6 +65,6 @@ pub fn get_os() -> &'static str {
} }
} }
pub async fn get_num_launches<R: Runtime>(w: &WebviewWindow<R>) -> i32 { pub async fn get_num_launches<R: Runtime>(app_handle: &AppHandle<R>) -> i32 {
get_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, 0).await get_key_value_int(app_handle, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
} }

View File

@@ -46,18 +46,22 @@ pub async fn send_http_request<R: Runtime>(
cookie_jar: Option<CookieJar>, cookie_jar: Option<CookieJar>,
cancelled_rx: &mut Receiver<bool>, cancelled_rx: &mut Receiver<bool>,
) -> Result<HttpResponse> { ) -> Result<HttpResponse> {
let plugin_manager = window.state::<PluginManager>(); let app_handle = window.app_handle().clone();
let workspace = get_workspace(window, &unrendered_request.workspace_id).await?; let plugin_manager = app_handle.state::<PluginManager>();
let base_environment = get_base_environment(window, &unrendered_request.workspace_id).await?; let workspace = get_workspace(&app_handle, &unrendered_request.workspace_id).await?;
let settings = get_or_create_settings(window).await; let base_environment =
get_base_environment(&app_handle, &unrendered_request.workspace_id).await?;
let settings = get_or_create_settings(&app_handle).await;
let response_id = og_response.id.clone();
let response = Arc::new(Mutex::new(og_response.clone()));
let cb = PluginTemplateCallback::new( let cb = PluginTemplateCallback::new(
window.app_handle(), window.app_handle(),
&WindowContext::from_window(window), &WindowContext::from_window(window),
RenderPurpose::Send, RenderPurpose::Send,
); );
let update_source = UpdateSource::from_window(window);
let response_id = og_response.id.clone();
let response = Arc::new(Mutex::new(og_response.clone()));
let request = match render_http_request( let request = match render_http_request(
&unrendered_request, &unrendered_request,
@@ -68,7 +72,15 @@ pub async fn send_http_request<R: Runtime>(
.await .await
{ {
Ok(r) => r, Ok(r) => r,
Err(e) => return Ok(response_err(&*response.lock().await, e.to_string(), window).await), Err(e) => {
return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
)
.await)
}
}; };
let mut url_string = request.url; let mut url_string = request.url;
@@ -178,9 +190,10 @@ pub async fn send_http_request<R: Runtime>(
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
&app_handle,
&*response.lock().await, &*response.lock().await,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()), format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window, &update_source,
) )
.await); .await);
} }
@@ -190,9 +203,10 @@ pub async fn send_http_request<R: Runtime>(
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
&app_handle,
&*response.lock().await, &*response.lock().await,
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()), format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
window, &update_source,
) )
.await); .await);
} }
@@ -296,7 +310,13 @@ pub async fn send_http_request<R: Runtime>(
request_builder = request_builder.body(f); request_builder = request_builder.body(f);
} }
Err(e) => { Err(e) => {
return Ok(response_err(&*response.lock().await, e, window).await); return Ok(response_err(
&app_handle,
&*response.lock().await,
e,
&update_source,
)
.await);
} }
} }
} else if body_type == "multipart/form-data" && request_body.contains_key("form") { } else if body_type == "multipart/form-data" && request_body.contains_key("form") {
@@ -323,9 +343,10 @@ pub async fn send_http_request<R: Runtime>(
Ok(f) => multipart::Part::bytes(f), Ok(f) => multipart::Part::bytes(f),
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
&app_handle,
&*response.lock().await, &*response.lock().await,
e.to_string(), e.to_string(),
window, &update_source,
) )
.await); .await);
} }
@@ -340,9 +361,10 @@ pub async fn send_http_request<R: Runtime>(
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
&app_handle,
&*response.lock().await, &*response.lock().await,
format!("Invalid mime for multi-part entry {e:?}"), format!("Invalid mime for multi-part entry {e:?}"),
window, &update_source,
) )
.await); .await);
} }
@@ -356,9 +378,10 @@ pub async fn send_http_request<R: Runtime>(
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
&app_handle,
&*response.lock().await, &*response.lock().await,
format!("Invalid mime for multi-part entry {e:?}"), format!("Invalid mime for multi-part entry {e:?}"),
window, &update_source,
) )
.await); .await);
} }
@@ -397,7 +420,13 @@ pub async fn send_http_request<R: Runtime>(
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
warn!("Failed to build request builder {e:?}"); warn!("Failed to build request builder {e:?}");
return Ok(response_err(&*response.lock().await, e.to_string(), window).await); return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
)
.await);
} }
}; };
@@ -423,7 +452,13 @@ pub async fn send_http_request<R: Runtime>(
let plugin_result = match auth_result { let plugin_result = match auth_result {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Ok(response_err(&*response.lock().await, e.to_string(), window).await); return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
)
.await);
} }
}; };
@@ -449,21 +484,23 @@ pub async fn send_http_request<R: Runtime>(
Ok(r) = resp_rx => r, Ok(r) = resp_rx => r,
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
debug!("Request cancelled"); debug!("Request cancelled");
return Ok(response_err(&*response.lock().await, "Request was cancelled".to_string(), window).await); return Ok(response_err(&app_handle, &*response.lock().await, "Request was cancelled".to_string(), &update_source).await);
} }
}; };
{ {
let app_handle = app_handle.clone();
let window = window.clone(); let window = window.clone();
let cancelled_rx = cancelled_rx.clone(); let cancelled_rx = cancelled_rx.clone();
let response_id = response_id.clone(); let response_id = response_id.clone();
let response = response.clone(); let response = response.clone();
let update_source = update_source.clone();
tokio::spawn(async move { tokio::spawn(async move {
match raw_response { match raw_response {
Ok(mut v) => { Ok(mut v) => {
let content_length = v.content_length(); let content_length = v.content_length();
let response_headers = v.headers().clone(); let response_headers = v.headers().clone();
let dir = window.app_handle().path().app_data_dir().unwrap(); let dir = app_handle.path().app_data_dir().unwrap();
let base_dir = dir.join("responses"); let base_dir = dir.join("responses");
create_dir_all(base_dir.clone()).await.expect("Failed to create responses dir"); create_dir_all(base_dir.clone()).await.expect("Failed to create responses dir");
let body_path = if response_id.is_empty() { let body_path = if response_id.is_empty() {
@@ -497,7 +534,7 @@ pub async fn send_http_request<R: Runtime>(
}; };
r.state = HttpResponseState::Connected; r.state = HttpResponseState::Connected;
update_response_if_id(&window, &r, &UpdateSource::Window) update_response_if_id(&app_handle, &r, &update_source)
.await .await
.expect("Failed to update response after connected"); .expect("Failed to update response after connected");
} }
@@ -526,7 +563,7 @@ pub async fn send_http_request<R: Runtime>(
f.flush().await.expect("Failed to flush file"); f.flush().await.expect("Failed to flush file");
written_bytes += bytes.len(); written_bytes += bytes.len();
r.content_length = Some(written_bytes as i32); r.content_length = Some(written_bytes as i32);
update_response_if_id(&window, &r, &UpdateSource::Window) update_response_if_id(&app_handle, &r, &update_source)
.await .await
.expect("Failed to update response"); .expect("Failed to update response");
} }
@@ -534,7 +571,13 @@ pub async fn send_http_request<R: Runtime>(
break; break;
} }
Err(e) => { Err(e) => {
response_err(&*response.lock().await, e.to_string(), &window).await; response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
)
.await;
break; break;
} }
} }
@@ -548,7 +591,7 @@ pub async fn send_http_request<R: Runtime>(
None => Some(written_bytes as i32), None => Some(written_bytes as i32),
}; };
r.state = HttpResponseState::Closed; r.state = HttpResponseState::Closed;
update_response_if_id(&window, &r, &UpdateSource::Window) update_response_if_id(&app_handle, &r, &UpdateSource::from_window(&window))
.await .await
.expect("Failed to update response"); .expect("Failed to update response");
}; };
@@ -574,8 +617,12 @@ pub async fn send_http_request<R: Runtime>(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
cookie_jar.cookies = json_cookies; cookie_jar.cookies = json_cookies;
if let Err(e) = if let Err(e) = upsert_cookie_jar(
upsert_cookie_jar(&window, &cookie_jar, &UpdateSource::Window).await &app_handle,
&cookie_jar,
&UpdateSource::from_window(&window),
)
.await
{ {
error!("Failed to update cookie jar: {}", e); error!("Failed to update cookie jar: {}", e);
}; };
@@ -583,7 +630,13 @@ pub async fn send_http_request<R: Runtime>(
} }
Err(e) => { Err(e) => {
warn!("Failed to execute request {e}"); warn!("Failed to execute request {e}");
response_err(&*response.lock().await, format!("{e}{e:?}"), &window).await; response_err(
&app_handle,
&*response.lock().await,
format!("{e}{e:?}"),
&update_source,
)
.await;
} }
}; };
@@ -592,16 +645,17 @@ pub async fn send_http_request<R: Runtime>(
}); });
}; };
let app_handle = app_handle.clone();
Ok(tokio::select! { Ok(tokio::select! {
Ok(r) = done_rx => r, Ok(r) = done_rx => r,
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
match get_http_response(window, response_id.as_str()).await { match get_http_response(&app_handle, response_id.as_str()).await {
Ok(mut r) => { Ok(mut r) => {
r.state = HttpResponseState::Closed; r.state = HttpResponseState::Closed;
update_response_if_id(&window, &r, &UpdateSource::Window).await.expect("Failed to update response") update_response_if_id(&app_handle, &r, &UpdateSource::from_window(window)).await.expect("Failed to update response")
}, },
_ => { _ => {
response_err(&*response.lock().await, "Ephemeral request was cancelled".to_string(), &window).await response_err(&app_handle, &*response.lock().await, "Ephemeral request was cancelled".to_string(), &update_source).await
}.clone(), }.clone(),
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ use log::debug;
use reqwest::Method; use reqwest::Method;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use tauri::{Emitter, Manager, Runtime, WebviewWindow}; use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
use yaak_models::queries::{get_key_value_raw, set_key_value_raw, UpdateSource}; use yaak_models::queries::{get_key_value_raw, set_key_value_raw, UpdateSource};
// Check for updates every hour // Check for updates every hour
@@ -43,16 +43,18 @@ impl YaakNotifier {
} }
} }
pub async fn seen<R: Runtime>(&mut self, w: &WebviewWindow<R>, id: &str) -> Result<(), String> { pub async fn seen<R: Runtime>(&mut self, window: &WebviewWindow<R>, id: &str) -> Result<(), String> {
let mut seen = get_kv(w).await?; let app_handle = window.app_handle();
let mut seen = get_kv(app_handle).await?;
seen.push(id.to_string()); seen.push(id.to_string());
debug!("Marked notification as seen {}", id); debug!("Marked notification as seen {}", id);
let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?; let seen_json = serde_json::to_string(&seen).map_err(|e| e.to_string())?;
set_key_value_raw(w, KV_NAMESPACE, KV_KEY, seen_json.as_str(), &UpdateSource::Window).await; set_key_value_raw(app_handle, KV_NAMESPACE, KV_KEY, seen_json.as_str(), &UpdateSource::from_window(window)).await;
Ok(()) Ok(())
} }
pub async fn check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<(), String> { pub async fn check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<(), String> {
let app_handle = window.app_handle();
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS; let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
if ignore_check { if ignore_check {
@@ -61,8 +63,8 @@ impl YaakNotifier {
self.last_check = SystemTime::now(); self.last_check = SystemTime::now();
let num_launches = get_num_launches(window).await; let num_launches = get_num_launches(app_handle).await;
let info = window.app_handle().package_info().clone(); let info = app_handle.package_info().clone();
let req = reqwest::Client::default() let req = reqwest::Client::default()
.request(Method::GET, "https://notify.yaak.app/notifications") .request(Method::GET, "https://notify.yaak.app/notifications")
.query(&[ .query(&[
@@ -90,14 +92,14 @@ impl YaakNotifier {
for notification in notifications { for notification in notifications {
let age = notification.timestamp.signed_duration_since(Utc::now()); let age = notification.timestamp.signed_duration_since(Utc::now());
let seen = get_kv(window).await?; let seen = get_kv(app_handle).await?;
if seen.contains(&notification.id) || (age > Duration::days(2)) { if seen.contains(&notification.id) || (age > Duration::days(2)) {
debug!("Already seen notification {}", notification.id); debug!("Already seen notification {}", notification.id);
continue; continue;
} }
debug!("Got notification {:?}", notification); debug!("Got notification {:?}", notification);
let _ = window.emit_to(window.label(), "notification", notification.clone()); let _ = app_handle.emit_to(window.label(), "notification", notification.clone());
break; // Only show one notification break; // Only show one notification
} }
@@ -105,8 +107,8 @@ impl YaakNotifier {
} }
} }
async fn get_kv<R: Runtime>(w: &WebviewWindow<R>) -> Result<Vec<String>, String> { async fn get_kv<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Vec<String>, String> {
match get_key_value_raw(w, "notifications", "seen").await { match get_key_value_raw(app_handle, "notifications", "seen").await {
None => Ok(Vec::new()), None => Ok(Vec::new()),
Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()), Some(v) => serde_json::from_str(&v.value).map_err(|e| e.to_string()),
} }

View File

@@ -18,8 +18,8 @@ use yaak_models::queries::{
use yaak_plugins::events::{ use yaak_plugins::events::{
Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse, Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse,
GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload,
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, RenderHttpRequestResponse, RenderPurpose, SendHttpRequestResponse, SetKeyValueResponse,
TemplateRenderResponse, WindowContext, WindowNavigateEvent, ShowToastRequest, TemplateRenderResponse, WindowContext, WindowNavigateEvent,
}; };
use yaak_plugins::manager::PluginManager; use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle; use yaak_plugins::plugin_handle::PluginHandle;
@@ -80,7 +80,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
.await .await
.expect("Failed to get workspace_id from window URL"); .expect("Failed to get workspace_id from window URL");
let environment = environment_from_window(&window).await; let environment = environment_from_window(&window).await;
let base_environment = get_base_environment(&window, workspace.id.as_str()) let base_environment = get_base_environment(app_handle, workspace.id.as_str())
.await .await
.expect("Failed to get base environment"); .expect("Failed to get base environment");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
@@ -104,7 +104,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
.await .await
.expect("Failed to get workspace_id from window URL"); .expect("Failed to get workspace_id from window URL");
let environment = environment_from_window(&window).await; let environment = environment_from_window(&window).await;
let base_environment = get_base_environment(&window, workspace.id.as_str()) let base_environment = get_base_environment(app_handle, workspace.id.as_str())
.await .await
.expect("Failed to get base environment"); .expect("Failed to get base environment");
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
@@ -145,7 +145,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
}; };
upsert_plugin(&window, new_plugin, &UpdateSource::Plugin).await.unwrap(); upsert_plugin(app_handle, new_plugin, &UpdateSource::Plugin).await.unwrap();
} }
let toast_event = plugin_handle.build_event_to_send( let toast_event = plugin_handle.build_event_to_send(
&WindowContext::from_window(&window), &WindowContext::from_window(&window),
@@ -177,7 +177,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
HttpResponse::new() HttpResponse::new()
} else { } else {
create_default_http_response( create_default_http_response(
&window, app_handle,
http_request.id.as_str(), http_request.id.as_str(),
&UpdateSource::Plugin, &UpdateSource::Plugin,
) )

View File

@@ -81,11 +81,11 @@ pub async fn activate_license<R: Runtime>(
let body: ActivateLicenseResponsePayload = response.json().await?; let body: ActivateLicenseResponsePayload = response.json().await?;
yaak_models::queries::set_key_value_string( yaak_models::queries::set_key_value_string(
window, window.app_handle(),
KV_ACTIVATION_ID_KEY, KV_ACTIVATION_ID_KEY,
KV_NAMESPACE, KV_NAMESPACE,
body.activation_id.as_str(), body.activation_id.as_str(),
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await; .await;
@@ -100,7 +100,8 @@ pub async fn deactivate_license<R: Runtime>(
window: &WebviewWindow<R>, window: &WebviewWindow<R>,
p: DeactivateLicenseRequestPayload, p: DeactivateLicenseRequestPayload,
) -> Result<()> { ) -> Result<()> {
let activation_id = get_activation_id(window).await; let app_handle = window.app_handle();
let activation_id = get_activation_id(app_handle).await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let path = format!("/licenses/activations/{}/deactivate", activation_id); let path = format!("/licenses/activations/{}/deactivate", activation_id);
@@ -119,14 +120,14 @@ pub async fn deactivate_license<R: Runtime>(
} }
yaak_models::queries::delete_key_value( yaak_models::queries::delete_key_value(
window, app_handle,
KV_ACTIVATION_ID_KEY, KV_ACTIVATION_ID_KEY,
KV_NAMESPACE, KV_NAMESPACE,
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await; .await;
if let Err(e) = window.emit("license-deactivated", true) { if let Err(e) = app_handle.emit("license-deactivated", true) {
warn!("Failed to emit deactivate-license event: {}", e); warn!("Failed to emit deactivate-license event: {}", e);
} }
@@ -143,7 +144,10 @@ pub enum LicenseCheckStatus {
Trialing { end: NaiveDateTime }, Trialing { end: NaiveDateTime },
} }
pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>, payload: CheckActivationRequestPayload) -> Result<LicenseCheckStatus> { pub async fn check_license<R: Runtime>(
app_handle: &AppHandle<R>,
payload: CheckActivationRequestPayload,
) -> Result<LicenseCheckStatus> {
let activation_id = get_activation_id(app_handle).await; let activation_id = get_activation_id(app_handle).await;
let settings = yaak_models::queries::get_or_create_settings(app_handle).await; let settings = yaak_models::queries::get_or_create_settings(app_handle).await;
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS)); let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
@@ -195,7 +199,7 @@ fn build_url(path: &str) -> String {
} }
} }
pub async fn get_activation_id<R: Runtime>(mgr: &impl Manager<R>) -> String { pub async fn get_activation_id<R: Runtime>(app_handle: &AppHandle<R>) -> String {
yaak_models::queries::get_key_value_string(mgr, KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "") yaak_models::queries::get_key_value_string(app_handle, KV_ACTIVATION_ID_KEY, KV_NAMESPACE, "")
.await .await
} }

View File

@@ -44,7 +44,7 @@ export type HttpUrlParameter = { enabled?: boolean, name: string, value: string,
export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
export type ModelPayload = { model: AnyModel, windowLabel: string, updateSource: UpdateSource, }; export type ModelPayload = { model: AnyModel, updateSource: UpdateSource, };
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, }; export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, };
@@ -60,7 +60,7 @@ export type SyncHistory = { model: "sync_history", id: string, workspaceId: stri
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
export type UpdateSource = "sync" | "window" | "plugin" | "background" | "import"; export type UpdateSource = { "type": "sync" } | { "type": "window", label: string, } | { "type": "plugin" } | { "type": "background" } | { "type": "import" };
export type WebsocketConnection = { model: "websocket_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, headers: Array<HttpResponseHeader>, state: WebsocketConnectionState, status: number, url: string, }; export type WebsocketConnection = { model: "websocket_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, headers: Array<HttpResponseHeader>, state: WebsocketConnectionState, status: number, url: string, };

View File

@@ -4,10 +4,16 @@ use thiserror::Error;
pub enum Error { pub enum Error {
#[error("SQL error: {0}")] #[error("SQL error: {0}")]
SqlError(#[from] rusqlite::Error), SqlError(#[from] rusqlite::Error),
#[error("SQL Pool error: {0}")]
SqlPoolError(#[from] r2d2::Error),
#[error("JSON error: {0}")] #[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error), JsonError(#[from] serde_json::Error),
#[error("Model not found {0}")] #[error("Model not found {0}")]
ModelNotFound(String), ModelNotFound(String),
#[error("unknown error")] #[error("unknown error")]
Unknown, Unknown,
} }

View File

@@ -4,3 +4,4 @@ pub mod queries;
pub mod plugin; pub mod plugin;
pub mod render; pub mod render;
pub mod manager;

View File

@@ -0,0 +1,40 @@
use crate::error::Result;
use crate::models::{Workspace, WorkspaceIden};
use crate::plugin::SqliteConnection;
use r2d2::{Pool, PooledConnection};
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::Connection;
use sea_query::{Asterisk, Order, Query, SqliteQueryBuilder};
use sea_query_rusqlite::RusqliteBinder;
use std::future::Future;
use std::ops::Deref;
use tauri::{AppHandle, Manager, Runtime};
pub struct QueryManager {
pool: Pool<SqliteConnectionManager>,
}
pub trait DBConnection {
fn connect(
&self,
) -> impl Future<Output = Result<PooledConnection<SqliteConnectionManager>>> + Send;
}
impl<R: Runtime> DBConnection for AppHandle<R> {
async fn connect(&self) -> Result<PooledConnection<SqliteConnectionManager>> {
let dbm = &*self.state::<SqliteConnection>();
let db = dbm.0.lock().await.get()?;
Ok(db)
}
}
pub async fn list_workspaces<T: Deref<Target = Connection>>(c: &T) -> Result<Vec<Workspace>> {
let (sql, params) = Query::select()
.from(WorkspaceIden::Table)
.column(Asterisk)
.order_by(WorkspaceIden::Name, Order::Asc)
.build_rusqlite(SqliteQueryBuilder);
let mut stmt = c.prepare(sql.as_str())?;
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
Ok(items.map(|v| v.unwrap()).collect())
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,17 +9,17 @@ use log::warn;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::Path; use std::path::Path;
use tauri::ipc::Channel; use tauri::ipc::Channel;
use tauri::{command, Listener, Runtime, WebviewWindow}; use tauri::{command, AppHandle, Listener, Runtime};
use tokio::sync::watch; use tokio::sync::watch;
use ts_rs::TS; use ts_rs::TS;
#[command] #[command]
pub async fn calculate<R: Runtime>( pub async fn calculate<R: Runtime>(
window: WebviewWindow<R>, app_handle: AppHandle<R>,
workspace_id: &str, workspace_id: &str,
sync_dir: &Path, sync_dir: &Path,
) -> Result<Vec<SyncOp>> { ) -> Result<Vec<SyncOp>> {
let db_candidates = get_db_candidates(&window, workspace_id, sync_dir).await?; let db_candidates = get_db_candidates(&app_handle, workspace_id, sync_dir).await?;
let fs_candidates = get_fs_candidates(sync_dir) let fs_candidates = get_fs_candidates(sync_dir)
.await? .await?
.into_iter() .into_iter()
@@ -40,13 +40,13 @@ pub async fn calculate_fs(dir: &Path) -> Result<Vec<SyncOp>> {
#[command] #[command]
pub async fn apply<R: Runtime>( pub async fn apply<R: Runtime>(
window: WebviewWindow<R>, app_handle: AppHandle<R>,
sync_ops: Vec<SyncOp>, sync_ops: Vec<SyncOp>,
sync_dir: &Path, sync_dir: &Path,
workspace_id: &str, workspace_id: &str,
) -> Result<()> { ) -> Result<()> {
let sync_state_ops = apply_sync_ops(&window, &workspace_id, sync_dir, sync_ops).await?; let sync_state_ops = apply_sync_ops(&app_handle, &workspace_id, sync_dir, sync_ops).await?;
apply_sync_state_ops(&window, workspace_id, sync_dir, sync_state_ops).await apply_sync_state_ops(&app_handle, workspace_id, sync_dir, sync_state_ops).await
} }
#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Serialize, Deserialize, TS)]
@@ -58,7 +58,7 @@ pub(crate) struct WatchResult {
#[command] #[command]
pub async fn watch<R: Runtime>( pub async fn watch<R: Runtime>(
window: WebviewWindow<R>, app_handle: AppHandle<R>,
sync_dir: &Path, sync_dir: &Path,
workspace_id: &str, workspace_id: &str,
channel: Channel<WatchEvent>, channel: Channel<WatchEvent>,
@@ -67,16 +67,16 @@ pub async fn watch<R: Runtime>(
watch_directory(&sync_dir, channel, cancel_rx).await?; watch_directory(&sync_dir, channel, cancel_rx).await?;
let window_inner = window.clone(); let app_handle_inner = app_handle.clone();
let unlisten_event = let unlisten_event =
format!("watch-unlisten-{}-{}", workspace_id, Utc::now().timestamp_millis()); format!("watch-unlisten-{}-{}", workspace_id, Utc::now().timestamp_millis());
// TODO: Figure out a way to unlisten when the client window refreshes or closes. Perhaps with // TODO: Figure out a way to unlisten when the client app_handle refreshes or closes. Perhaps with
// a heartbeat mechanism, or ensuring only a single subscription per workspace (at least // a heartbeat mechanism, or ensuring only a single subscription per workspace (at least
// this won't create `n` subs). We could also maybe have a global fs watcher that we keep // this won't create `n` subs). We could also maybe have a global fs watcher that we keep
// adding to here. // adding to here.
window.listen_any(unlisten_event.clone(), move |event| { app_handle.listen_any(unlisten_event.clone(), move |event| {
window_inner.unlisten(event.id()); app_handle_inner.unlisten(event.id());
if let Err(e) = cancel_tx.send(()) { if let Err(e) = cancel_tx.send(()) {
warn!("Failed to send cancel signal to watcher {e:?}"); warn!("Failed to send cancel signal to watcher {e:?}");
} }

View File

@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tauri::{Manager, Runtime, WebviewWindow}; use tauri::{AppHandle, Runtime};
use tokio::fs; use tokio::fs;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncWriteExt; use tokio::io::AsyncWriteExt;
@@ -106,17 +106,21 @@ pub(crate) struct FsCandidate {
} }
pub(crate) async fn get_db_candidates<R: Runtime>( pub(crate) async fn get_db_candidates<R: Runtime>(
mgr: &impl Manager<R>, app_handle: &AppHandle<R>,
workspace_id: &str, workspace_id: &str,
sync_dir: &Path, sync_dir: &Path,
) -> Result<Vec<DbCandidate>> { ) -> Result<Vec<DbCandidate>> {
let models: HashMap<_, _> = let models: HashMap<_, _> = workspace_models(app_handle, workspace_id)
workspace_models(mgr, workspace_id).await?.into_iter().map(|m| (m.id(), m)).collect();
let sync_states: HashMap<_, _> = list_sync_states_for_workspace(mgr, workspace_id, sync_dir)
.await? .await?
.into_iter() .into_iter()
.map(|s| (s.model_id.clone(), s)) .map(|m| (m.id(), m))
.collect(); .collect();
let sync_states: HashMap<_, _> =
list_sync_states_for_workspace(app_handle, workspace_id, sync_dir)
.await?
.into_iter()
.map(|s| (s.model_id.clone(), s))
.collect();
// 1. Add candidates for models (created/modified/unmodified) // 1. Add candidates for models (created/modified/unmodified)
let mut candidates: Vec<DbCandidate> = models let mut candidates: Vec<DbCandidate> = models
@@ -223,7 +227,7 @@ pub(crate) fn compute_sync_ops(
model: model.to_owned(), model: model.to_owned(),
state: sync_state.to_owned(), state: sync_state.to_owned(),
} }
}, }
(Some(DbCandidate::Modified(model, sync_state)), None) => SyncOp::FsUpdate { (Some(DbCandidate::Modified(model, sync_state)), None) => SyncOp::FsUpdate {
model: model.to_owned(), model: model.to_owned(),
state: sync_state.to_owned(), state: sync_state.to_owned(),
@@ -285,10 +289,11 @@ pub(crate) fn compute_sync_ops(
} }
async fn workspace_models<R: Runtime>( async fn workspace_models<R: Runtime>(
mgr: &impl Manager<R>, app_handle: &AppHandle<R>,
workspace_id: &str, workspace_id: &str,
) -> Result<Vec<SyncModel>> { ) -> Result<Vec<SyncModel>> {
let resources = get_workspace_export_resources(mgr, vec![workspace_id], true).await?.resources; let resources =
get_workspace_export_resources(app_handle, vec![workspace_id], true).await?.resources;
let workspace = resources.workspaces.iter().find(|w| w.id == workspace_id); let workspace = resources.workspaces.iter().find(|w| w.id == workspace_id);
let workspace = match workspace { let workspace = match workspace {
@@ -318,7 +323,7 @@ async fn workspace_models<R: Runtime>(
} }
pub(crate) async fn apply_sync_ops<R: Runtime>( pub(crate) async fn apply_sync_ops<R: Runtime>(
window: &WebviewWindow<R>, app_handle: &AppHandle<R>,
workspace_id: &str, workspace_id: &str,
sync_dir: &Path, sync_dir: &Path,
sync_ops: Vec<SyncOp>, sync_ops: Vec<SyncOp>,
@@ -429,7 +434,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
} }
} }
SyncOp::DbDelete { model, state } => { SyncOp::DbDelete { model, state } => {
delete_model(window, &model).await?; delete_model(app_handle, &model).await?;
SyncStateOp::Delete { SyncStateOp::Delete {
state: state.to_owned(), state: state.to_owned(),
} }
@@ -438,7 +443,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
} }
let upserted_models = batch_upsert( let upserted_models = batch_upsert(
window, app_handle,
workspaces_to_upsert, workspaces_to_upsert,
environments_to_upsert, environments_to_upsert,
folders_to_upsert, folders_to_upsert,
@@ -452,7 +457,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
// Ensure we creat WorkspaceMeta models for each new workspace, with the appropriate sync dir // Ensure we creat WorkspaceMeta models for each new workspace, with the appropriate sync dir
let sync_dir_string = sync_dir.to_string_lossy().to_string(); let sync_dir_string = sync_dir.to_string_lossy().to_string();
for workspace in upserted_models.workspaces { for workspace in upserted_models.workspaces {
let r = match get_workspace_meta(window, &workspace).await { let r = match get_workspace_meta(app_handle, &workspace).await {
Ok(Some(m)) => { Ok(Some(m)) => {
if m.setting_sync_dir == Some(sync_dir_string.clone()) { if m.setting_sync_dir == Some(sync_dir_string.clone()) {
// We don't need to update if unchanged // We don't need to update if unchanged
@@ -462,7 +467,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()), setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()),
..m ..m
}; };
upsert_workspace_meta(window, wm, &UpdateSource::Sync).await upsert_workspace_meta(app_handle, wm, &UpdateSource::Sync).await
} }
Ok(None) => { Ok(None) => {
let wm = WorkspaceMeta { let wm = WorkspaceMeta {
@@ -470,7 +475,7 @@ pub(crate) async fn apply_sync_ops<R: Runtime>(
setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()), setting_sync_dir: Some(sync_dir.to_string_lossy().to_string()),
..Default::default() ..Default::default()
}; };
upsert_workspace_meta(window, wm, &UpdateSource::Sync).await upsert_workspace_meta(app_handle, wm, &UpdateSource::Sync).await
} }
Err(e) => Err(e), Err(e) => Err(e),
}; };
@@ -501,7 +506,7 @@ pub(crate) enum SyncStateOp {
} }
pub(crate) async fn apply_sync_state_ops<R: Runtime>( pub(crate) async fn apply_sync_state_ops<R: Runtime>(
window: &WebviewWindow<R>, app_handle: &AppHandle<R>,
workspace_id: &str, workspace_id: &str,
sync_dir: &Path, sync_dir: &Path,
ops: Vec<SyncStateOp>, ops: Vec<SyncStateOp>,
@@ -522,7 +527,7 @@ pub(crate) async fn apply_sync_state_ops<R: Runtime>(
flushed_at: Utc::now().naive_utc(), flushed_at: Utc::now().naive_utc(),
..Default::default() ..Default::default()
}; };
upsert_sync_state(window, sync_state).await?; upsert_sync_state(app_handle, sync_state).await?;
} }
SyncStateOp::Update { SyncStateOp::Update {
state: sync_state, state: sync_state,
@@ -536,10 +541,10 @@ pub(crate) async fn apply_sync_state_ops<R: Runtime>(
flushed_at: Utc::now().naive_utc(), flushed_at: Utc::now().naive_utc(),
..sync_state ..sync_state
}; };
upsert_sync_state(window, sync_state).await?; upsert_sync_state(app_handle, sync_state).await?;
} }
SyncStateOp::Delete { state } => { SyncStateOp::Delete { state } => {
delete_sync_state(window, state.id.as_str()).await?; delete_sync_state(app_handle, state.id.as_str()).await?;
} }
} }
} }
@@ -551,25 +556,25 @@ fn derive_model_filename(m: &SyncModel) -> PathBuf {
Path::new(&rel).to_path_buf() Path::new(&rel).to_path_buf()
} }
async fn delete_model<R: Runtime>(window: &WebviewWindow<R>, model: &SyncModel) -> Result<()> { async fn delete_model<R: Runtime>(app_handle: &AppHandle<R>, model: &SyncModel) -> Result<()> {
match model { match model {
SyncModel::Workspace(m) => { SyncModel::Workspace(m) => {
delete_workspace(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_workspace(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
SyncModel::Environment(m) => { SyncModel::Environment(m) => {
delete_environment(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_environment(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
SyncModel::Folder(m) => { SyncModel::Folder(m) => {
delete_folder(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_folder(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
SyncModel::HttpRequest(m) => { SyncModel::HttpRequest(m) => {
delete_http_request(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_http_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
SyncModel::GrpcRequest(m) => { SyncModel::GrpcRequest(m) => {
delete_grpc_request(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_grpc_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
SyncModel::WebsocketRequest(m) => { SyncModel::WebsocketRequest(m) => {
delete_websocket_request(window, m.id.as_str(), &UpdateSource::Sync).await?; delete_websocket_request(app_handle, m.id.as_str(), &UpdateSource::Sync).await?;
} }
}; };
Ok(()) Ok(())

View File

@@ -1,7 +1,6 @@
use crate::error::Error::{RenderStackExceededError, VariableNotFound}; use crate::error::Error::{RenderStackExceededError, VariableNotFound};
use crate::error::Result; use crate::error::Result;
use crate::{Parser, Token, Tokens, Val}; use crate::{Parser, Token, Tokens, Val};
use log::warn;
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use std::collections::HashMap;
use std::future::Future; use std::future::Future;
@@ -113,13 +112,7 @@ async fn render_value<T: TemplateCallback>(
let v = Box::pin(render_value(a.value, vars, cb, depth)).await?; let v = Box::pin(render_value(a.value, vars, cb, depth)).await?;
resolved_args.insert(a.name, v); resolved_args.insert(a.name, v);
} }
match cb.run(name.as_str(), resolved_args.clone()).await { cb.run(name.as_str(), resolved_args.clone()).await?
Ok(s) => s,
Err(e) => {
warn!("Failed to run template callback {}({:?}): {}", name, resolved_args, e);
"".to_string()
}
}
} }
Val::Null => "".into(), Val::Null => "".into(),
}; };
@@ -324,7 +317,7 @@ mod parse_and_render_tests {
#[tokio::test] #[tokio::test]
async fn render_fn_err() -> Result<()> { async fn render_fn_err() -> Result<()> {
let vars = HashMap::new(); let vars = HashMap::new();
let template = r#"${[ error() ]}"#; let template = r#"hello ${[ error() ]}"#;
struct CB {} struct CB {}
impl TemplateCallback for CB { impl TemplateCallback for CB {

View File

@@ -5,7 +5,7 @@ use crate::render::render_request;
use log::{info, warn}; use log::{info, warn};
use std::str::FromStr; use std::str::FromStr;
use tauri::http::{HeaderMap, HeaderName}; use tauri::http::{HeaderMap, HeaderName};
use tauri::{AppHandle, Manager, Runtime, State, Url, WebviewWindow}; use tauri::{AppHandle, Runtime, State, Url, WebviewWindow};
use tokio::sync::{mpsc, Mutex}; use tokio::sync::{mpsc, Mutex};
use tokio_tungstenite::tungstenite::http::HeaderValue; use tokio_tungstenite::tungstenite::http::HeaderValue;
use tokio_tungstenite::tungstenite::Message; use tokio_tungstenite::tungstenite::Message;
@@ -28,41 +28,54 @@ use yaak_plugins::template_callback::PluginTemplateCallback;
#[tauri::command] #[tauri::command]
pub(crate) async fn upsert_request<R: Runtime>( pub(crate) async fn upsert_request<R: Runtime>(
request: WebsocketRequest, request: WebsocketRequest,
w: WebviewWindow<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<WebsocketRequest> { ) -> Result<WebsocketRequest> {
Ok(queries::upsert_websocket_request(&w, request, &UpdateSource::Window).await?) Ok(queries::upsert_websocket_request(&app_handle, request, &UpdateSource::from_window(&window))
.await?)
} }
#[tauri::command] #[tauri::command]
pub(crate) async fn duplicate_request<R: Runtime>( pub(crate) async fn duplicate_request<R: Runtime>(
request_id: &str, request_id: &str,
w: WebviewWindow<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<WebsocketRequest> { ) -> Result<WebsocketRequest> {
Ok(queries::duplicate_websocket_request(&w, request_id, &UpdateSource::Window).await?) Ok(queries::duplicate_websocket_request(
&app_handle,
request_id,
&UpdateSource::from_window(&window),
)
.await?)
} }
#[tauri::command] #[tauri::command]
pub(crate) async fn delete_request<R: Runtime>( pub(crate) async fn delete_request<R: Runtime>(
request_id: &str, request_id: &str,
w: WebviewWindow<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<WebsocketRequest> { ) -> Result<WebsocketRequest> {
Ok(queries::delete_websocket_request(&w, request_id, &UpdateSource::Window).await?) Ok(queries::delete_websocket_request(&app_handle, request_id, &UpdateSource::from_window(&window)).await?)
} }
#[tauri::command] #[tauri::command]
pub(crate) async fn delete_connection<R: Runtime>( pub(crate) async fn delete_connection<R: Runtime>(
connection_id: &str, connection_id: &str,
w: WebviewWindow<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<WebsocketConnection> { ) -> Result<WebsocketConnection> {
Ok(queries::delete_websocket_connection(&w, connection_id, &UpdateSource::Window).await?) Ok(queries::delete_websocket_connection(&app_handle, connection_id, &UpdateSource::from_window(&window))
.await?)
} }
#[tauri::command] #[tauri::command]
pub(crate) async fn delete_connections<R: Runtime>( pub(crate) async fn delete_connections<R: Runtime>(
request_id: &str, request_id: &str,
w: WebviewWindow<R>, app_handle: AppHandle<R>,
window: WebviewWindow<R>,
) -> Result<()> { ) -> Result<()> {
Ok(queries::delete_all_websocket_connections(&w, request_id, &UpdateSource::Window).await?) Ok(queries::delete_all_websocket_connections(&app_handle, request_id, &UpdateSource::from_window(&window))
.await?)
} }
#[tauri::command] #[tauri::command]
@@ -93,24 +106,26 @@ pub(crate) async fn list_connections<R: Runtime>(
pub(crate) async fn send<R: Runtime>( pub(crate) async fn send<R: Runtime>(
connection_id: &str, connection_id: &str,
environment_id: Option<&str>, environment_id: Option<&str>,
app_handle: AppHandle<R>,
window: WebviewWindow<R>, window: WebviewWindow<R>,
ws_manager: State<'_, Mutex<WebsocketManager>>, ws_manager: State<'_, Mutex<WebsocketManager>>,
) -> Result<WebsocketConnection> { ) -> Result<WebsocketConnection> {
let connection = get_websocket_connection(&window, connection_id).await?; let connection = get_websocket_connection(&app_handle, connection_id).await?;
let unrendered_request = get_websocket_request(&window, &connection.request_id) let unrendered_request = get_websocket_request(&app_handle, &connection.request_id)
.await? .await?
.ok_or(GenericError("WebSocket Request not found".to_string()))?; .ok_or(GenericError("WebSocket Request not found".to_string()))?;
let environment = match environment_id { let environment = match environment_id {
Some(id) => Some(get_environment(&window, id).await?), Some(id) => Some(get_environment(&app_handle, id).await?),
None => None, None => None,
}; };
let base_environment = get_base_environment(&window, &unrendered_request.workspace_id).await?; let base_environment =
get_base_environment(&app_handle, &unrendered_request.workspace_id).await?;
let request = render_request( let request = render_request(
&unrendered_request, &unrendered_request,
&base_environment, &base_environment,
environment.as_ref(), environment.as_ref(),
&PluginTemplateCallback::new( &PluginTemplateCallback::new(
window.app_handle(), &app_handle,
&WindowContext::from_window(&window), &WindowContext::from_window(&window),
RenderPurpose::Send, RenderPurpose::Send,
), ),
@@ -121,7 +136,7 @@ pub(crate) async fn send<R: Runtime>(
ws_manager.send(&connection.id, Message::Text(request.message.clone().into())).await?; ws_manager.send(&connection.id, Message::Text(request.message.clone().into())).await?;
upsert_websocket_event( upsert_websocket_event(
&window, &app_handle,
WebsocketEvent { WebsocketEvent {
connection_id: connection.id.clone(), connection_id: connection.id.clone(),
request_id: request.id.clone(), request_id: request.id.clone(),
@@ -131,7 +146,7 @@ pub(crate) async fn send<R: Runtime>(
message: request.message.into(), message: request.message.into(),
..Default::default() ..Default::default()
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();
@@ -142,17 +157,18 @@ pub(crate) async fn send<R: Runtime>(
#[tauri::command] #[tauri::command]
pub(crate) async fn close<R: Runtime>( pub(crate) async fn close<R: Runtime>(
connection_id: &str, connection_id: &str,
app_handle: AppHandle<R>,
window: WebviewWindow<R>, window: WebviewWindow<R>,
ws_manager: State<'_, Mutex<WebsocketManager>>, ws_manager: State<'_, Mutex<WebsocketManager>>,
) -> Result<WebsocketConnection> { ) -> Result<WebsocketConnection> {
let connection = get_websocket_connection(&window, connection_id).await?; let connection = get_websocket_connection(&app_handle, connection_id).await?;
let connection = upsert_websocket_connection( let connection = upsert_websocket_connection(
&window, &app_handle,
&WebsocketConnection { &WebsocketConnection {
state: WebsocketConnectionState::Closing, state: WebsocketConnectionState::Closing,
..connection ..connection
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();
@@ -170,24 +186,26 @@ pub(crate) async fn connect<R: Runtime>(
request_id: &str, request_id: &str,
environment_id: Option<&str>, environment_id: Option<&str>,
cookie_jar_id: Option<&str>, cookie_jar_id: Option<&str>,
app_handle: AppHandle<R>,
window: WebviewWindow<R>, window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>, plugin_manager: State<'_, PluginManager>,
ws_manager: State<'_, Mutex<WebsocketManager>>, ws_manager: State<'_, Mutex<WebsocketManager>>,
) -> Result<WebsocketConnection> { ) -> Result<WebsocketConnection> {
let unrendered_request = get_websocket_request(&window, request_id) let unrendered_request = get_websocket_request(&app_handle, request_id)
.await? .await?
.ok_or(GenericError("Failed to find GRPC request".to_string()))?; .ok_or(GenericError("Failed to find GRPC request".to_string()))?;
let environment = match environment_id { let environment = match environment_id {
Some(id) => Some(get_environment(&window, id).await?), Some(id) => Some(get_environment(&app_handle, id).await?),
None => None, None => None,
}; };
let base_environment = get_base_environment(&window, &unrendered_request.workspace_id).await?; let base_environment =
get_base_environment(&app_handle, &unrendered_request.workspace_id).await?;
let request = render_request( let request = render_request(
&unrendered_request, &unrendered_request,
&base_environment, &base_environment,
environment.as_ref(), environment.as_ref(),
&PluginTemplateCallback::new( &PluginTemplateCallback::new(
window.app_handle(), &app_handle,
&WindowContext::from_window(&window), &WindowContext::from_window(&window),
RenderPurpose::Send, RenderPurpose::Send,
), ),
@@ -224,18 +242,18 @@ pub(crate) async fn connect<R: Runtime>(
// TODO: Handle cookies // TODO: Handle cookies
let _cookie_jar = match cookie_jar_id { let _cookie_jar = match cookie_jar_id {
Some(id) => Some(get_cookie_jar(&window, id).await?), Some(id) => Some(get_cookie_jar(&app_handle, id).await?),
None => None, None => None,
}; };
let connection = upsert_websocket_connection( let connection = upsert_websocket_connection(
&window, &app_handle,
&WebsocketConnection { &WebsocketConnection {
workspace_id: request.workspace_id.clone(), workspace_id: request.workspace_id.clone(),
request_id: request_id.to_string(), request_id: request_id.to_string(),
..Default::default() ..Default::default()
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await?; .await?;
@@ -261,20 +279,20 @@ pub(crate) async fn connect<R: Runtime>(
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
return Ok(upsert_websocket_connection( return Ok(upsert_websocket_connection(
&window, &app_handle,
&WebsocketConnection { &WebsocketConnection {
error: Some(format!("{e:?}")), error: Some(format!("{e:?}")),
state: WebsocketConnectionState::Closed, state: WebsocketConnectionState::Closed,
..connection ..connection
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await?); .await?);
} }
}; };
upsert_websocket_event( upsert_websocket_event(
&window, &app_handle,
WebsocketEvent { WebsocketEvent {
connection_id: connection.id.clone(), connection_id: connection.id.clone(),
request_id: request.id.clone(), request_id: request.id.clone(),
@@ -283,7 +301,7 @@ pub(crate) async fn connect<R: Runtime>(
message_type: WebsocketEventType::Open, message_type: WebsocketEventType::Open,
..Default::default() ..Default::default()
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();
@@ -298,7 +316,7 @@ pub(crate) async fn connect<R: Runtime>(
.collect::<Vec<HttpResponseHeader>>(); .collect::<Vec<HttpResponseHeader>>();
let connection = upsert_websocket_connection( let connection = upsert_websocket_connection(
&window, &app_handle,
&WebsocketConnection { &WebsocketConnection {
state: WebsocketConnectionState::Connected, state: WebsocketConnectionState::Connected,
headers: response_headers, headers: response_headers,
@@ -306,7 +324,7 @@ pub(crate) async fn connect<R: Runtime>(
url: request.url.clone(), url: request.url.clone(),
..connection ..connection
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await?; .await?;
@@ -314,7 +332,6 @@ pub(crate) async fn connect<R: Runtime>(
let connection_id = connection.id.clone(); let connection_id = connection.id.clone();
let request_id = request.id.to_string(); let request_id = request.id.to_string();
let workspace_id = request.workspace_id.clone(); let workspace_id = request.workspace_id.clone();
let window = window.clone();
let connection = connection.clone(); let connection = connection.clone();
let mut has_written_close = false; let mut has_written_close = false;
tokio::spawn(async move { tokio::spawn(async move {
@@ -324,7 +341,7 @@ pub(crate) async fn connect<R: Runtime>(
} }
upsert_websocket_event( upsert_websocket_event(
&window, &app_handle,
WebsocketEvent { WebsocketEvent {
connection_id: connection_id.clone(), connection_id: connection_id.clone(),
request_id: request_id.clone(), request_id: request_id.clone(),
@@ -342,7 +359,7 @@ pub(crate) async fn connect<R: Runtime>(
message: message.into_data().into(), message: message.into_data().into(),
..Default::default() ..Default::default()
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();
@@ -350,7 +367,7 @@ pub(crate) async fn connect<R: Runtime>(
info!("Websocket connection closed"); info!("Websocket connection closed");
if !has_written_close { if !has_written_close {
upsert_websocket_event( upsert_websocket_event(
&window, &app_handle,
WebsocketEvent { WebsocketEvent {
connection_id: connection_id.clone(), connection_id: connection_id.clone(),
request_id: request_id.clone(), request_id: request_id.clone(),
@@ -359,20 +376,20 @@ pub(crate) async fn connect<R: Runtime>(
message_type: WebsocketEventType::Close, message_type: WebsocketEventType::Close,
..Default::default() ..Default::default()
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();
} }
upsert_websocket_connection( upsert_websocket_connection(
&window, &app_handle,
&WebsocketConnection { &WebsocketConnection {
workspace_id: request.workspace_id.clone(), workspace_id: request.workspace_id.clone(),
request_id: request_id.to_string(), request_id: request_id.to_string(),
state: WebsocketConnectionState::Closed, state: WebsocketConnectionState::Closed,
..connection ..connection
}, },
&UpdateSource::Window, &UpdateSource::from_window(&window),
) )
.await .await
.unwrap(); .unwrap();

View File

@@ -1,12 +1,11 @@
import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp-internal/models'; import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp-internal/models';
import classNames from 'classnames'; import classNames from 'classnames';
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useRef } from 'react'; import React, { useCallback, useMemo, useRef } from 'react';
import { useContainerSize } from '../hooks/useContainerQuery'; import { useContainerSize } from '../hooks/useContainerQuery';
import type { ReflectResponseService } from '../hooks/useGrpc'; import type { ReflectResponseService } from '../hooks/useGrpc';
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication'; import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
import { useKeyValue } from '../hooks/useKeyValue';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
@@ -52,8 +51,6 @@ const TAB_METADATA = 'metadata';
const TAB_AUTH = 'auth'; const TAB_AUTH = 'auth';
const TAB_DESCRIPTION = 'description'; const TAB_DESCRIPTION = 'description';
const tabsAtom = atomWithStorage<Record<string, string>>('grpcRequestPaneActiveTabs', {});
export function GrpcConnectionSetupPane({ export function GrpcConnectionSetupPane({
style, style,
services, services,
@@ -70,7 +67,11 @@ export function GrpcConnectionSetupPane({
}: Props) { }: Props) {
const updateRequest = useUpdateAnyGrpcRequest(); const updateRequest = useUpdateAnyGrpcRequest();
const authentication = useHttpAuthenticationSummaries(); const authentication = useHttpAuthenticationSummaries();
const [activeTabs, setActiveTabs] = useAtom(tabsAtom); const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
namespace: 'no_sync',
key: 'grpcRequestActiveTabs',
fallback: {},
});
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
const urlContainerEl = useRef<HTMLDivElement>(null); const urlContainerEl = useRef<HTMLDivElement>(null);
@@ -183,8 +184,8 @@ export function GrpcConnectionSetupPane({
const activeTab = activeTabs?.[activeRequest.id]; const activeTab = activeTabs?.[activeRequest.id];
const setActiveTab = useCallback( const setActiveTab = useCallback(
(tab: string) => { async (tab: string) => {
setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); await setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab }));
}, },
[activeRequest.id, setActiveTabs], [activeRequest.id, setActiveTabs],
); );

View File

@@ -1,8 +1,7 @@
import type { HttpRequest } from '@yaakapp-internal/models'; import type { HttpRequest } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins'; import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import classNames from 'classnames'; import classNames from 'classnames';
import { atom, useAtom, useAtomValue } from 'jotai'; import { atom, useAtomValue } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { activeRequestIdAtom } from '../hooks/useActiveRequestId'; import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
@@ -11,6 +10,7 @@ import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication'; import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
import { httpRequestsAtom } from '../hooks/useHttpRequests'; import { httpRequestsAtom } from '../hooks/useHttpRequests';
import { useImportCurl } from '../hooks/useImportCurl'; import { useImportCurl } from '../hooks/useImportCurl';
import { useKeyValue } from '../hooks/useKeyValue';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor'; import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
@@ -27,7 +27,8 @@ import {
BODY_TYPE_JSON, BODY_TYPE_JSON,
BODY_TYPE_NONE, BODY_TYPE_NONE,
BODY_TYPE_OTHER, BODY_TYPE_OTHER,
BODY_TYPE_XML, getContentTypeFromHeaders, BODY_TYPE_XML,
getContentTypeFromHeaders,
} from '../lib/model_util'; } from '../lib/model_util';
import { prepareImportQuerystring } from '../lib/prepareImportQuerystring'; import { prepareImportQuerystring } from '../lib/prepareImportQuerystring';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
@@ -64,8 +65,6 @@ const TAB_HEADERS = 'headers';
const TAB_AUTH = 'auth'; const TAB_AUTH = 'auth';
const TAB_DESCRIPTION = 'description'; const TAB_DESCRIPTION = 'description';
const tabsAtom = atomWithStorage<Record<string, string>>('requestPaneActiveTabs', {});
const nonActiveRequestUrlsAtom = atom((get) => { const nonActiveRequestUrlsAtom = atom((get) => {
const activeRequestId = get(activeRequestIdAtom); const activeRequestId = get(activeRequestIdAtom);
const requests = [...get(httpRequestsAtom), ...get(grpcRequestsAtom)]; const requests = [...get(httpRequestsAtom), ...get(grpcRequestsAtom)];
@@ -79,7 +78,11 @@ const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
export function HttpRequestPane({ style, fullHeight, className, activeRequest }: Props) { export function HttpRequestPane({ style, fullHeight, className, activeRequest }: Props) {
const activeRequestId = activeRequest.id; const activeRequestId = activeRequest.id;
const { mutateAsync: updateRequestAsync, mutate: updateRequest } = useUpdateAnyHttpRequest(); const { mutateAsync: updateRequestAsync, mutate: updateRequest } = useUpdateAnyHttpRequest();
const [activeTabs, setActiveTabs] = useAtom(tabsAtom); const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
namespace: 'no_sync',
key: 'httpRequestActiveTabs',
fallback: {},
});
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0); const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor(); const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
@@ -285,14 +288,14 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
const activeTab = activeTabs?.[activeRequestId]; const activeTab = activeTabs?.[activeRequestId];
const setActiveTab = useCallback( const setActiveTab = useCallback(
(tab: string) => { async (tab: string) => {
setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); await setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab }));
}, },
[activeRequest.id, setActiveTabs], [activeRequest.id, setActiveTabs],
); );
useRequestEditorEvent('request_pane.focus_tab', () => { useRequestEditorEvent('request_pane.focus_tab', async () => {
setActiveTab(TAB_PARAMS); await setActiveTab(TAB_PARAMS);
}); });
const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom); const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom);

View File

@@ -2,8 +2,7 @@ import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins'; import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws'; import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws';
import classNames from 'classnames'; import classNames from 'classnames';
import { atom, useAtom, useAtomValue } from 'jotai'; import { atom, useAtomValue } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest'; import { upsertWebsocketRequest } from '../commands/upsertWebsocketRequest';
@@ -12,6 +11,7 @@ import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
import { activeRequestIdAtom } from '../hooks/useActiveRequestId'; import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse'; import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication'; import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
import { useKeyValue } from '../hooks/useKeyValue';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor'; import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
import { requestsAtom } from '../hooks/useRequests'; import { requestsAtom } from '../hooks/useRequests';
@@ -50,8 +50,6 @@ const TAB_HEADERS = 'headers';
const TAB_AUTH = 'auth'; const TAB_AUTH = 'auth';
const TAB_DESCRIPTION = 'description'; const TAB_DESCRIPTION = 'description';
const tabsAtom = atomWithStorage<Record<string, string>>('requestPaneActiveTabs', {});
const nonActiveRequestUrlsAtom = atom((get) => { const nonActiveRequestUrlsAtom = atom((get) => {
const activeRequestId = get(activeRequestIdAtom); const activeRequestId = get(activeRequestIdAtom);
const requests = get(requestsAtom); const requests = get(requestsAtom);
@@ -64,7 +62,11 @@ const memoNotActiveRequestUrlsAtom = deepEqualAtom(nonActiveRequestUrlsAtom);
export function WebsocketRequestPane({ style, fullHeight, className, activeRequest }: Props) { export function WebsocketRequestPane({ style, fullHeight, className, activeRequest }: Props) {
const activeRequestId = activeRequest.id; const activeRequestId = activeRequest.id;
const [activeTabs, setActiveTabs] = useAtom(tabsAtom); const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
namespace: 'no_sync',
key: 'websocketRequestActiveTabs',
fallback: {},
});
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor(); const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
const authentication = useHttpAuthenticationSummaries(); const authentication = useHttpAuthenticationSummaries();
@@ -157,14 +159,14 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
const activeTab = activeTabs?.[activeRequestId]; const activeTab = activeTabs?.[activeRequestId];
const setActiveTab = useCallback( const setActiveTab = useCallback(
(tab: string) => { async (tab: string) => {
setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab })); await setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab }));
}, },
[activeRequest.id, setActiveTabs], [activeRequest.id, setActiveTabs],
); );
useRequestEditorEvent('request_pane.focus_tab', () => { useRequestEditorEvent('request_pane.focus_tab', async () => {
setActiveTab(TAB_PARAMS); await setActiveTab(TAB_PARAMS);
}); });
const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom); const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom);

View File

@@ -5,7 +5,7 @@ import type { AnyModel, KeyValue, ModelPayload } from '@yaakapp-internal/models'
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
import { buildKeyValueKey } from '../lib/keyValueStore'; import { buildKeyValueKey } from '../lib/keyValueStore';
import { modelsEq } from '../lib/model_util'; import { modelsEq } from '../lib/model_util';
import { useActiveWorkspace } from './useActiveWorkspace'; import { getActiveWorkspaceId } from './useActiveWorkspace';
import { cookieJarsAtom } from './useCookieJars'; import { cookieJarsAtom } from './useCookieJars';
import { environmentsAtom } from './useEnvironments'; import { environmentsAtom } from './useEnvironments';
import { foldersAtom } from './useFolders'; import { foldersAtom } from './useFolders';
@@ -26,7 +26,6 @@ import { workspaceMetaAtom } from './useWorkspaceMeta';
import { workspacesAtom } from './useWorkspaces'; import { workspacesAtom } from './useWorkspaces';
export function useSyncModelStores() { export function useSyncModelStores() {
const activeWorkspace = useActiveWorkspace();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { wasUpdatedExternally } = useRequestUpdateKey(null); const { wasUpdatedExternally } = useRequestUpdateKey(null);
@@ -45,16 +44,13 @@ export function useSyncModelStores() {
(payload.model.model === 'http_request' || (payload.model.model === 'http_request' ||
payload.model.model === 'grpc_request' || payload.model.model === 'grpc_request' ||
payload.model.model === 'websocket_request') && payload.model.model === 'websocket_request') &&
(payload.windowLabel !== getCurrentWebviewWindow().label || payload.updateSource !== 'window') ((payload.updateSource.type === 'window' &&
payload.updateSource.label !== getCurrentWebviewWindow().label) ||
payload.updateSource.type !== 'window')
) { ) {
wasUpdatedExternally(payload.model.id); wasUpdatedExternally(payload.model.id);
} }
// Only sync models that belong to this workspace, if a workspace ID is present
if ('workspaceId' in payload.model && payload.model.workspaceId !== activeWorkspace?.id) {
return;
}
if (shouldIgnoreModel(payload)) return; if (shouldIgnoreModel(payload)) return;
if (payload.model.model === 'workspace') { if (payload.model.model === 'workspace') {
@@ -160,7 +156,7 @@ export function removeModelById<T extends { id: string }>(model: T) {
return (prevEntries: T[] | undefined) => { return (prevEntries: T[] | undefined) => {
const entries = prevEntries?.filter((e) => e.id !== model.id) ?? []; const entries = prevEntries?.filter((e) => e.id !== model.id) ?? [];
// Don't trigger an update if we didn't actually remove anything // Don't trigger an update if we didn't remove anything
if (entries.length === (prevEntries ?? []).length) { if (entries.length === (prevEntries ?? []).length) {
return prevEntries ?? []; return prevEntries ?? [];
} }
@@ -181,17 +177,24 @@ export function removeModelByKv(model: KeyValue) {
) ?? []; ) ?? [];
} }
function shouldIgnoreModel({ model, windowLabel, updateSource }: ModelPayload) { function shouldIgnoreModel({ model, updateSource }: ModelPayload) {
// Never ignore same-window updates console.log('HELLO', updateSource);
if (windowLabel === getCurrentWebviewWindow().label) { // Never ignore updates from non-user sources
if (updateSource.type !== 'window') {
return false; return false;
} }
// Never ignore updates from non-user sources // Never ignore same-window updates
if (updateSource !== 'window') { if (updateSource.label === getCurrentWebviewWindow().label) {
return false; return false;
} }
const activeWorkspaceId = getActiveWorkspaceId();
// Only sync models that belong to this workspace, if a workspace ID is present
if ('workspaceId' in model && model.workspaceId !== activeWorkspaceId) {
return;
}
if (model.model === 'key_value') { if (model.model === 'key_value') {
return model.namespace === 'no_sync'; return model.namespace === 'no_sync';
} }